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