Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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;
1441 ariadna 185
            this.listbox.setStyle('background', 'url(' + M.util.image_url('i/loading', 'moodle') + ') no-repeat center center');
186
            this.listbox.setStyle('background-size', '3em');
1 efrain 187
        },
188
        /**
189
         * Handle what happens when we get some data back from the search.
190
         * @param {int} requestid not used.
191
         * @param {object} response the list of users that was returned.
192
         */
193
        handle_response : function(requestid, response) {
194
            try {
195
                delete this.iotransactions[requestid];
196
                if (!Y.Object.isEmpty(this.iotransactions)) {
197
                    // More searches pending. Wait until they are all done.
198
                    return;
199
                }
200
                this.listbox.setStyle('background','');
201
                var data = Y.JSON.parse(response.responseText);
202
                if (data.error) {
203
                    this.searchfield.addClass('error');
204
                    return new M.core.ajaxException(data);
205
                }
206
                this.output_options(data);
207
 
208
                // If updated userSummaries are present, overwrite the global variable
209
                // that's output by group_non_members_selector::print_user_summaries() in user/selector/lib.php
210
                if (typeof data.userSummaries !== "undefined") {
211
                    /* global userSummaries:true */
212
                    /* exported userSummaries */
213
                    userSummaries = data.userSummaries;
214
                }
215
            } catch (e) {
216
                this.listbox.setStyle('background','');
217
                this.searchfield.addClass('error');
218
                return new M.core.exception(e);
219
            }
220
        },
221
        /**
222
         * This method should do the same sort of thing as the PHP method
223
         * user_selector_base::output_options.
224
         * @param {object} data the list of users to populate the list box with.
225
         */
226
        output_options : function(data) {
227
            // Clear out the existing options, keeping any ones that are already selected.
228
            var selectedusers = {};
229
            this.listbox.all('optgroup').each(function(optgroup){
230
                optgroup.all('option').each(function(option){
231
                    if (option.get('selected')) {
232
                        selectedusers[option.get('value')] = {
233
                            id : option.get('value'),
234
                            name : option.get('innerText') || option.get('textContent'),
235
                            disabled: option.get('disabled')
236
                        }
237
                    }
238
                    option.remove();
239
                }, this);
240
                optgroup.remove();
241
            }, this);
242
 
243
            // Output each optgroup.
244
            var count = 0;
245
            for (var key in data.results) {
246
                var groupdata = data.results[key];
247
                this.output_group(groupdata.name, groupdata.users, selectedusers, true);
248
                count ++;
249
            }
250
            if (!count) {
251
                var searchstr = (this.lastsearch != '') ? this.insert_search_into_str(M.util.get_string('nomatchingusers', 'moodle'), this.lastsearch) : M.util.get_string('none', 'moodle');
252
                this.output_group(searchstr, {}, selectedusers, true)
253
            }
254
 
255
            // If there were previously selected users who do not match the search, show them too.
256
            if (this.get_option('preserveselected') && selectedusers) {
257
                this.output_group(this.insert_search_into_str(M.util.get_string('previouslyselectedusers', 'moodle'), this.lastsearch), selectedusers, true, false);
258
            }
259
            this.handle_selection_change();
260
        },
261
        /**
262
         * This method should do the same sort of thing as the PHP method
263
         * user_selector_base::output_optgroup.
264
         *
265
         * @param {string} groupname the label for this optgroup.v
266
         * @param {object} users the users to put in this optgroup.
267
         * @param {boolean|object} selectedusers if true, select the users in this group.
268
         * @param {boolean} processsingle
269
         */
270
        output_group : function(groupname, users, selectedusers, processsingle) {
271
            var optgroup = Y.Node.create('<optgroup></optgroup>');
272
            this.listbox.append(optgroup);
273
 
274
            var count = 0;
275
            for (var key in users) {
276
                var user = users[key];
277
                var option = Y.Node.create('<option value="' + user.id + '">' + user.name + '</option>');
278
                if (user.disabled) {
279
                    option.setAttribute('disabled', 'disabled');
280
                } else if (selectedusers === true || selectedusers[user.id]) {
281
                    option.setAttribute('selected', 'selected');
282
                    delete selectedusers[user.id];
283
                }
284
                optgroup.append(option);
285
                if (user.infobelow) {
286
                    extraoption = Y.Node.create('<option disabled="disabled" class="userselector-infobelow"/>');
287
                    extraoption.appendChild(document.createTextNode(user.infobelow));
288
                    optgroup.append(extraoption);
289
                }
290
                count ++;
291
            }
292
 
293
            if (count > 0) {
294
                optgroup.set('label', groupname + ' (' + count + ')');
295
                if (processsingle && count === 1 && this.get_option('autoselectunique') && option.get('disabled') == false) {
296
                    option.setAttribute('selected', 'selected');
297
                }
298
            } else {
299
                optgroup.set('label', groupname);
300
                optgroup.append(Y.Node.create('<option disabled="disabled">\u00A0</option>'));
301
            }
302
        },
303
        /**
304
         * Replace
305
         * @param {string} str
306
         * @param {string} search The search term
307
         * @return string
308
         */
309
        insert_search_into_str : function(str, search) {
310
            return str.replace("%%SEARCHTERM%%", search);
311
        },
312
        /**
313
         * Gets the search text
314
         * @return String the value to search for, with leading and trailing whitespace trimmed.
315
         */
316
        get_search_text : function() {
317
            return this.searchfield.get('value').toString().replace(/^ +| +$/, '');
318
        },
319
        /**
320
         * Returns true if the selection is empty (nothing is selected)
321
         * @return Boolean check all the options and return whether any are selected.
322
         */
323
        is_selection_empty : function() {
324
            var selection = false;
325
            this.listbox.all('option').each(function(){
326
                if (this.get('selected')) {
327
                    selection = true;
328
                }
329
            });
330
            return !(selection);
331
        },
332
        /**
333
         * Cancel the search delay timeout, if there is one.
334
         */
335
        cancel_timeout : function() {
336
            if (this.timeoutid) {
337
                clearTimeout(this.timeoutid);
338
                this.timeoutid = null;
339
            }
340
        },
341
        /**
342
         * @param {string} name The name of the option to retrieve
343
         * @return the value of one of the option checkboxes.
344
         */
345
        get_option : function(name) {
346
            var checkbox = Y.one('#userselector_' + name + 'id');
347
            if (checkbox) {
348
                return (checkbox.get('checked'));
349
            } else {
350
                return false;
351
            }
352
        }
353
    };
354
    // Augment the user selector with the EventTarget class so that we can use
355
    // custom events
356
    Y.augment(user_selector, Y.EventTarget, null, null, {});
357
    // Initialise the user selector
358
    user_selector.init();
359
    // Store the user selector so that it can be retrieved
360
    this.user_selectors[name] = user_selector;
361
    // Return the user selector
362
    return user_selector;
363
};
364
 
365
/**
366
 * Initialise a class that updates the user's preferences when they change one of
367
 * the options checkboxes.
368
 * @constructor
369
 * @param {YUI} Y
370
 * @return Tracker object
371
 */
372
M.core_user.init_user_selector_options_tracker = function(Y) {
373
    // Create a user selector options tracker
374
    var user_selector_options_tracker = {
375
        /**
376
         * Initlises the option tracker and gets everything going.
377
         * @constructor
378
         */
379
        init : function() {
380
            var settings = [
381
                'userselector_preserveselected',
382
                'userselector_autoselectunique',
383
            ];
384
            for (var s in settings) {
385
                var setting = settings[s];
386
                Y.one('#' + setting + 'id').on('click', this.set_user_preference, this, setting);
387
            }
388
        },
389
        /**
390
         * Sets a user preference for the options tracker
391
         * @param {Y.Event|null} e
392
         * @param {string} name The name of the preference to set
393
         */
394
        set_user_preference : function(e, name) {
395
            require(['core_user/repository'], function(UserRepository) {
396
                UserRepository.setUserPreference(name, Y.one('#' + name + 'id').get('checked'));
397
            });
398
        }
399
    };
400
    // Initialise the options tracker
401
    user_selector_options_tracker.init();
402
    // Return it just incase it is ever wanted
403
    return user_selector_options_tracker;
404
};