rating MDL-23814 made an assortment of fixes to the ratings code
[moodle.git] / lib / javascript-static.js
CommitLineData
47aa42e2 1// Miscellaneous core Javascript functions for Moodle
e50b4c89 2// Global M object is initilised in inline javascript
34f76e9a 3
e50b4c89
PS
4/**
5 * Add module to list of available modules that can be laoded from YUI.
126590b7 6 * @param {Array} modules
e50b4c89
PS
7 */
8M.yui.add_module = function(modules) {
126590b7 9 for (var modname in modules) {
fc367afb
PS
10 M.yui.loader.modules[modname] = modules[modname];
11 }
e50b4c89 12};
2a102b90
SH
13/**
14 * The gallery version to use when loading YUI modules from the gallery.
15 * Will be changed every time when using local galleries.
16 */
17M.yui.galleryversion = '2010.04.21-21-51';
38224dcb 18
bca09754
PS
19/**
20 * Various utility functions
21 */
d29b8f0f 22M.util = M.util || {};
38224dcb 23
2b728cb5
PS
24/**
25 * Language strings - initialised from page footer.
26 */
d29b8f0f 27M.str = M.str || {};
2b728cb5 28
38224dcb
PS
29/**
30 * Returns url for images.
126590b7
SH
31 * @param {String} imagename
32 * @param {String} component
33 * @return {String}
38224dcb
PS
34 */
35M.util.image_url = function(imagename, component) {
36 var url = M.cfg.wwwroot + '/theme/image.php?theme=' + M.cfg.theme + '&image=' + imagename;
37
38 if (M.cfg.themerev > 0) {
39 url = url + '&rev=' + M.cfg.themerev;
40 }
41
87ad1edc 42 if (component && component != '' && component != 'moodle' && component != 'core') {
38224dcb
PS
43 url = url + '&component=' + component;
44 }
45
46 return url;
47};
48
10eaeca8
PS
49M.util.create_UFO_object = function (eid, FO) {
50 UFO.create(FO, eid);
51}
52
b4430cd6
PS
53M.util.in_array = function(item, array){
54 for( var i = 0; i<array.length; i++){
55 if(item==array[i]){
56 return true;
57 }
58 }
59 return false;
60}
61
38224dcb
PS
62/**
63 * Init a collapsible region, see print_collapsible_region in weblib.php
126590b7
SH
64 * @param {YUI} Y YUI3 instance with all libraries loaded
65 * @param {String} id the HTML id for the div.
66 * @param {String} userpref the user preference that records the state of this box. false if none.
67 * @param {String} strtooltip
38224dcb
PS
68 */
69M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
70 Y.use('anim', function(Y) {
3b044ba3 71 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
38224dcb
PS
72 });
73};
74
75/**
126590b7 76 * Object to handle a collapsible region : instantiate and forget styled object
3b044ba3 77 *
126590b7 78 * @class
38224dcb 79 * @constructor
126590b7
SH
80 * @param {YUI} Y YUI3 instance with all libraries loaded
81 * @param {String} id The HTML id for the div.
82 * @param {String} userpref The user preference that records the state of this box. false if none.
83 * @param {String} strtooltip
38224dcb
PS
84 */
85M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
38224dcb
PS
86 // Record the pref name
87 this.userpref = userpref;
38224dcb
PS
88
89 // Find the divs in the document.
126590b7
SH
90 this.div = Y.one('#'+id);
91
92 // Get the caption for the collapsible region
93 var caption = this.div.one('#'+id + '_caption');
94 caption.setAttribute('title', strtooltip);
95
96 // Create a link
97 var a = Y.Node.create('<a href="#"></a>');
98 // Create a local scoped lamba function to move nodes to a new link
99 var movenode = function(node){
100 node.remove();
101 a.append(node);
102 };
103 // Apply the lamba function on each of the captions child nodes
104 caption.get('children').each(movenode, this);
105 caption.append(a);
106
107 // Get the height of the div at this point before we shrink it if required
108 var height = this.div.get('offsetHeight');
109 if (this.div.hasClass('collapsed')) {
110 // Add the correct image and record the YUI node created in the process
111 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/collapsed', 'moodle')+'" alt="" />');
112 // Shrink the div as it is collapsed by default
113 this.div.setStyle('height', caption.get('offsetHeight')+'px');
114 } else {
115 // Add the correct image and record the YUI node created in the process
116 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
38224dcb 117 }
126590b7 118 a.append(this.icon);
38224dcb
PS
119
120 // Create the animation.
126590b7 121 var animation = new Y.Anim({
38224dcb
PS
122 node: this.div,
123 duration: 0.3,
126590b7
SH
124 easing: Y.Easing.easeBoth,
125 to: {height:caption.get('offsetHeight')},
126 from: {height:height}
38224dcb 127 });
3b044ba3 128
38224dcb 129 // Handler for the animation finishing.
126590b7
SH
130 animation.on('end', function() {
131 this.div.toggleClass('collapsed');
132 if (this.div.hasClass('collapsed')) {
133 this.icon.set('src', M.util.image_url('t/collapsed', 'moodle'));
134 } else {
135 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
fc367afb 136 }
38224dcb 137 }, this);
126590b7
SH
138
139 // Hook up the event handler.
140 a.on('click', function(e, animation) {
141 e.preventDefault();
142 // Animate to the appropriate size.
143 if (animation.get('running')) {
144 animation.stop();
145 }
146 animation.set('reverse', this.div.hasClass('collapsed'));
147 // Update the user preference.
148 if (this.userpref) {
41912a26 149 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
126590b7
SH
150 }
151 animation.run();
152 }, this, animation);
38224dcb
PS
153};
154
155/**
156 * The user preference that stores the state of this box.
157 * @property userpref
158 * @type String
159 */
160M.util.CollapsibleRegion.prototype.userpref = null;
161
162/**
163 * The key divs that make up this
126590b7
SH
164 * @property div
165 * @type Y.Node
38224dcb
PS
166 */
167M.util.CollapsibleRegion.prototype.div = null;
38224dcb
PS
168
169/**
170 * The key divs that make up this
171 * @property icon
126590b7 172 * @type Y.Node
38224dcb
PS
173 */
174M.util.CollapsibleRegion.prototype.icon = null;
175
41912a26
PS
176/**
177 * Makes a best effort to connect back to Moodle to update a user preference,
178 * however, there is no mechanism for finding out if the update succeeded.
179 *
180 * Before you can use this function in your JavsScript, you must have called
181 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
182 * the udpate is allowed, and how to safely clean and submitted values.
183 *
184 * @param String name the name of the setting to udpate.
185 * @param String the value to set it to.
186 */
187M.util.set_user_preference = function(name, value) {
20fb563e 188 YUI(M.yui.loader).use('io', function(Y) {
41912a26
PS
189 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
190 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
191
192 // If we are a developer, ensure that failures are reported.
193 var cfg = {
20fb563e 194 method: 'get',
8dd14645 195 on: {}
41912a26
PS
196 };
197 if (M.cfg.developerdebug) {
198 cfg.on.failure = function(id, o, args) {
20fb563e 199 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
41912a26
PS
200 }
201 }
202
203 // Make the request.
204 Y.io(url, cfg);
205 });
206};
207
20fb563e
PS
208/**
209 * Prints a confirmation dialog in the style of DOM.confirm().
210 * @param object event A YUI DOM event or null if launched manually
211 * @param string message The message to show in the dialog
212 * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
213 * @param function fn A JS function to run if YES is clicked.
214 */
215M.util.show_confirm_dialog = function(e, args) {
216 var target = e.target;
217 if (e.preventDefault) {
218 e.preventDefault();
219 }
220
221 YUI(M.yui.loader).use('yui2-container', 'yui2-event', function(Y) {
222 var simpledialog = new YAHOO.widget.SimpleDialog('confirmdialog',
f855a5d2 223 {width: '300px',
20fb563e
PS
224 fixedcenter: true,
225 modal: true,
226 visible: false,
227 draggable: false
228 }
229 );
230
2b728cb5 231 simpledialog.setHeader(M.str.admin.confirmation);
20fb563e
PS
232 simpledialog.setBody(args.message);
233 simpledialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
234
235 var handle_cancel = function() {
236 simpledialog.hide();
237 };
238
239 var handle_yes = function() {
240 simpledialog.hide();
241
242 if (args.callback) {
243 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
244 var callback = null;
245 if (Y.Lang.isFunction(args.callback)) {
246 callback = args.callback;
247 } else {
248 callback = eval('('+args.callback+')');
249 }
250
251 if (Y.Lang.isObject(args.scope)) {
252 var sc = args.scope;
253 } else {
254 var sc = e.target;
255 }
256
257 if (args.callbackargs) {
258 callback.apply(sc, args.callbackargs);
259 } else {
260 callback.apply(sc);
261 }
262 return;
263 }
264
265 if (target.get('tagName').toLowerCase() == 'a') {
266 window.location = target.get('href');
267 } else if (target.get('tagName').toLowerCase() == 'input') {
268 var parentelement = target.get('parentNode');
269 while (parentelement.get('tagName').toLowerCase() != 'form' && parentelement.get('tagName').toLowerCase() != 'body') {
270 parentelement = parentelement.get('parentNode');
271 }
272 if (parentelement.get('tagName').toLowerCase() == 'form') {
273 parentelement.submit();
274 }
275 } else if (M.cfg.developerdebug) {
276 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A or INPUT");
277 }
278 };
279
f855a5d2
SH
280 var buttons = [ {text: M.str.moodle.cancel, handler: handle_cancel, isDefault: true},
281 {text: M.str.moodle.yes, handler: handle_yes} ];
20fb563e
PS
282
283 simpledialog.cfg.queueProperty('buttons', buttons);
284
285 simpledialog.render(document.body);
286 simpledialog.show();
287 });
288}
289
dd9b1882
PS
290/** Useful for full embedding of various stuff */
291M.util.init_maximised_embed = function(Y, id) {
292 var obj = Y.one('#'+id);
293 if (!obj) {
294 return;
295 }
296
297
298 var get_htmlelement_size = function(el, prop) {
299 if (Y.Lang.isString(el)) {
300 el = Y.one('#' + el);
301 }
302 var val = el.getStyle(prop);
303 if (val == 'auto') {
304 val = el.getComputedStyle(prop);
305 }
306 return parseInt(val);
307 };
308
309 var resize_object = function() {
310 obj.setStyle('width', '0px');
311 obj.setStyle('height', '0px');
312 var newwidth = get_htmlelement_size('content', 'width') - 15;
313
314 if (newwidth > 600) {
315 obj.setStyle('width', newwidth + 'px');
316 } else {
317 obj.setStyle('width', '600px');
318 }
319 var pageheight = get_htmlelement_size('page', 'height');
320 var objheight = get_htmlelement_size(obj, 'height');
321 var newheight = objheight + parseInt(obj.get('winHeight')) - pageheight - 30;
322 if (newheight > 400) {
323 obj.setStyle('height', newheight + 'px');
324 } else {
325 obj.setStyle('height', '400px');
326 }
327 };
328
329 resize_object();
330 // fix layout if window resized too
331 window.onresize = function() {
332 resize_object();
333 };
334};
335
a9967cf5
PS
336/**
337 * Attach handler to single_select
338 */
edc28287 339M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
c7bbb86f
SH
340 Y.use('event-key', function() {
341 var select = Y.one('#'+selectid);
342 if (select) {
343 // Try to get the form by id
344 var form = Y.one('#'+formid) || (function(){
345 // Hmmm the form's id may have been overriden by an internal input
346 // with the name id which will KILL IE.
347 // We need to manually iterate at this point because if the case
348 // above is true YUI's ancestor method will also kill IE!
349 var form = select;
350 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
351 form = form.ancestor();
352 }
353 return form;
354 })();
355 // Make sure we have the form
356 if (form) {
357 // Create a function to handle our change event
358 var processchange = function(e, lastindex) {
359 if ((nothing===false || select.get('value') != nothing) && lastindex != select.get('selectedIndex')) {
360 this.submit();
361 }
362 }
363 // Attach the change event to the keypress, blur, and click actions.
364 // We don't use the change event because IE fires it on every arrow up/down
365 // event.... usability
366 Y.on('key', processchange, select, 'press:13', form, select.get('selectedIndex'));
367 select.on('blur', processchange, form, select.get('selectedIndex'));
368 select.on('click', processchange, form, select.get('selectedIndex'));
369 }
4aea3cc7 370 }
4aea3cc7 371 });
a9967cf5 372};
dd9b1882 373
4d10e579
PS
374/**
375 * Attach handler to url_select
376 */
377M.util.init_url_select = function(Y, formid, selectid, nothing) {
4aea3cc7
PS
378 YUI(M.yui.loader).use('node', function(Y) {
379 Y.on('change', function() {
c7bbb86f
SH
380 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
381 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
382 }
4aea3cc7
PS
383 },
384 '#'+selectid);
385 });
386};
387
388/**
389 * Breaks out all links to the top frame - used in frametop page layout.
390 */
391M.util.init_frametop = function(Y) {
392 Y.all('a').each(function(node) {
393 node.set('target', '_top');
394 });
395 Y.all('form').each(function(node) {
396 node.set('target', '_top');
397 });
4d10e579
PS
398};
399
24e27ac0
SH
400/**
401 * Finds all nodes that match the given CSS selector and attaches events to them
402 * so that they toggle a given classname when clicked.
403 *
404 * @param {YUI} Y
405 * @param {string} id An id containing elements to target
406 * @param {string} cssselector A selector to use to find targets
407 * @param {string} toggleclassname A classname to toggle
408 */
409M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname) {
410 var node = Y.one('#'+id);
411 node.all(cssselector).each(function(node){
412 node.on('click', function(e){
413 e.stopPropagation();
414 if (e.target.get('nodeName')!='A' && e.target.get('nodeName')!='IMG') {
415 this.toggleClass(toggleclassname);
416 }
417 }, node);
418 });
419 // Attach this click event to the node rather than all selectors... will be much better
420 // for performance
421 node.on('click', function(e){
422 if (e.target.hasClass('addtoall')) {
423 node.all(cssselector).addClass(toggleclassname);
424 } else if (e.target.hasClass('removefromall')) {
425 node.all(cssselector+'.'+toggleclassname).removeClass(toggleclassname);
426 }
427 }, node);
428}
429
233b6ff9
SH
430/**
431 * Initialises a colour picker
432 *
433 * Designed to be used with admin_setting_configcolourpicker although could be used
434 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
435 * above or below the input (must have the same parent) and then call this with the
436 * id.
437 *
438 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
439 * contrib/blocks. For better docs refer to that.
440 *
441 * @param {YUI} Y
442 * @param {int} id
443 * @param {object} previewconf
444 */
445M.util.init_colour_picker = function(Y, id, previewconf) {
446 /**
447 * We need node and event-mouseenter
448 */
449 Y.use('node', 'event-mouseenter', function(){
450 /**
451 * The colour picker object
452 */
453 var colourpicker = {
454 box : null,
455 input : null,
456 image : null,
457 preview : null,
458 current : null,
459 eventClick : null,
460 eventMouseEnter : null,
461 eventMouseLeave : null,
462 eventMouseMove : null,
463 width : 300,
464 height : 100,
465 factor : 5,
466 /**
467 * Initalises the colour picker by putting everything together and wiring the events
468 */
469 init : function() {
470 this.input = Y.one('#'+id);
471 this.box = this.input.ancestor().one('.admin_colourpicker');
472 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
473 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
474 this.preview = Y.Node.create('<div class="previewcolour"></div>');
475 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
476 this.current = Y.Node.create('<div class="currentcolour"></div>');
477 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
478 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
479
480 if (typeof(previewconf) === 'object' && previewconf !== null) {
481 Y.one('#'+id+'_preview').on('click', function(e){
3c3a3c3c 482 if (Y.Lang.isString(previewconf.selector)) {
bb9e9362
SH
483 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
484 } else {
485 for (var i in previewconf.selector) {
486 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
487 }
488 }
233b6ff9
SH
489 }, this);
490 }
491
492 this.eventClick = this.image.on('click', this.pickColour, this);
493 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
494 },
495 /**
496 * Starts to follow the mouse once it enter the image
497 */
498 startFollow : function(e) {
499 this.eventMouseEnter.detach();
500 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
501 this.eventMouseMove = this.image.on('mousemove', function(e){
502 this.preview.setStyle('backgroundColor', this.determineColour(e));
503 }, this);
504 },
505 /**
506 * Stops following the mouse
507 */
508 endFollow : function(e) {
509 this.eventMouseMove.detach();
510 this.eventMouseLeave.detach();
511 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
512 },
513 /**
514 * Picks the colour the was clicked on
515 */
516 pickColour : function(e) {
517 var colour = this.determineColour(e);
518 this.input.set('value', colour);
519 this.current.setStyle('backgroundColor', colour);
520 },
521 /**
522 * Calculates the colour fromthe given co-ordinates
523 */
524 determineColour : function(e) {
525 var eventx = Math.floor(e.pageX-e.target.getX());
526 var eventy = Math.floor(e.pageY-e.target.getY());
527
528 var imagewidth = this.width;
529 var imageheight = this.height;
530 var factor = this.factor;
531 var colour = [255,0,0];
532
533 var matrices = [
534 [ 0, 1, 0],
535 [ -1, 0, 0],
536 [ 0, 0, 1],
537 [ 0, -1, 0],
538 [ 1, 0, 0],
539 [ 0, 0, -1]
540 ];
541
542 var matrixcount = matrices.length;
543 var limit = Math.round(imagewidth/matrixcount);
544 var heightbreak = Math.round(imageheight/2);
545
546 for (var x = 0; x < imagewidth; x++) {
547 var divisor = Math.floor(x / limit);
548 var matrix = matrices[divisor];
549
550 colour[0] += matrix[0]*factor;
551 colour[1] += matrix[1]*factor;
552 colour[2] += matrix[2]*factor;
553
554 if (eventx==x) {
555 break;
556 }
557 }
558
559 var pixel = [colour[0], colour[1], colour[2]];
560 if (eventy < heightbreak) {
561 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
562 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
563 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
564 } else if (eventy > heightbreak) {
565 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
566 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
567 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
568 }
569
570 return this.convert_rgb_to_hex(pixel);
571 },
572 /**
573 * Converts an RGB value to Hex
574 */
575 convert_rgb_to_hex : function(rgb) {
576 var hex = '#';
577 var hexchars = "0123456789ABCDEF";
578 for (var i=0; i<3; i++) {
579 var number = Math.abs(rgb[i]);
580 if (number == 0 || isNaN(number)) {
581 hex += '00';
582 } else {
583 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
584 }
585 }
586 return hex;
587 }
588 }
589 /**
590 * Initialise the colour picker :) Hoorah
591 */
592 colourpicker.init();
593 });
594}
595
38224dcb
PS
596//=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
597
b48fd7b5 598function popupchecker(msg) {
496e3ccd 599 var testwindow = window.open('', '', 'width=1,height=1,left=0,top=0,scrollbars=no');
f6b6861d 600 if (!testwindow) {
601 alert(msg);
602 } else {
b48fd7b5 603 testwindow.close();
604 }
605}
606
63d28811 607function checkall() {
214f5850 608 var inputs = document.getElementsByTagName('input');
609 for (var i = 0; i < inputs.length; i++) {
610 if (inputs[i].type == 'checkbox') {
611 inputs[i].checked = true;
612 }
d2ce367f 613 }
63d28811 614}
615
03f9425f 616function checknone() {
214f5850 617 var inputs = document.getElementsByTagName('input');
618 for (var i = 0; i < inputs.length; i++) {
619 if (inputs[i].type == 'checkbox') {
620 inputs[i].checked = false;
621 }
d2ce367f 622 }
03f9425f 623}
624
d2ce367f 625function lockoptions(formid, master, subitems) {
027d0485 626 // Subitems is an array of names of sub items.
627 // Optionally, each item in subitems may have a
63d28811 628 // companion hidden item in the form with the
027d0485 629 // same name but prefixed by "h".
d2ce367f 630 var form = document.forms[formid];
631
632 if (eval("form."+master+".checked")) {
63d28811 633 for (i=0; i<subitems.length; i++) {
634 unlockoption(form, subitems[i]);
635 }
636 } else {
637 for (i=0; i<subitems.length; i++) {
638 lockoption(form, subitems[i]);
639 }
640 }
641 return(true);
642}
643
f07b9627 644function lockoption(form,item) {
d2ce367f 645 eval("form."+item+".disabled=true");/* IE thing */
646 if(form.elements['h'+item]) {
647 eval("form.h"+item+".value=1");
f07b9627 648 }
649}
650
651function unlockoption(form,item) {
d2ce367f 652 eval("form."+item+".disabled=false");/* IE thing */
653 if(form.elements['h'+item]) {
654 eval("form.h"+item+".value=0");
f07b9627 655 }
656}
dd07bbac 657
b51709c1 658/**
659 * Get the value of the 'virtual form element' with a particular name. That is,
660 * abstracts away the difference between a normal form element, like a select
661 * which is a single HTML element with a .value property, and a set of radio
662 * buttons, which is several HTML elements.
663 *
664 * @param form a HTML form.
665 * @param master the name of an element in that form.
666 * @return the value of that element.
667 */
668function get_form_element_value(form, name) {
669 var element = form[name];
670 if (!element) {
671 return null;
672 }
673 if (element.tagName) {
674 // Ordinarly thing like a select box.
675 return element.value;
676 }
677 // Array of things, like radio buttons.
678 for (var j = 0; j < element.length; j++) {
679 var el = element[j];
680 if (el.checked) {
681 return el.value;
682 }
683 }
684 return null;
685}
686
687
688/**
689 * Set the disabled state of the 'virtual form element' with a particular name.
690 * This abstracts away the difference between a normal form element, like a select
691 * which is a single HTML element with a .value property, and a set of radio
692 * buttons, which is several HTML elements.
693 *
694 * @param form a HTML form.
695 * @param master the name of an element in that form.
696 * @param disabled the disabled state to set.
697 */
698function set_form_element_disabled(form, name, disabled) {
699 var element = form[name];
700 if (!element) {
701 return;
702 }
703 if (element.tagName) {
704 // Ordinarly thing like a select box.
705 element.disabled = disabled;
706 }
707 // Array of things, like radio buttons.
708 for (var j = 0; j < element.length; j++) {
709 var el = element[j];
710 el.disabled = disabled;
711 }
712}
dd07bbac 713
41f23791 714/**
715 * Set the hidden state of the 'virtual form element' with a particular name.
716 * This abstracts away the difference between a normal form element, like a select
717 * which is a single HTML element with a .value property, and a set of radio
718 * buttons, which is several HTML elements.
719 *
720 * @param form a HTML form.
721 * @param master the name of an element in that form.
722 * @param hidden the hidden state to set.
723 */
724function set_form_element_hidden(form, name, hidden) {
725 var element = form[name];
726 if (!element) {
727 return;
728 }
729 if (element.tagName) {
730 var el = findParentNode(element, 'DIV', 'fitem', false);
731 if (el!=null) {
732 el.style.display = hidden ? 'none' : '';
733 el.style.visibility = hidden ? 'hidden' : '';
734 }
735 }
736 // Array of things, like radio buttons.
737 for (var j = 0; j < element.length; j++) {
738 var el = findParentNode(element[j], 'DIV', 'fitem', false);
739 if (el!=null) {
740 el.style.display = hidden ? 'none' : '';
741 el.style.visibility = hidden ? 'hidden' : '';
742 }
743 }
744}
745
50ef8eb9 746function lockoptionsall(formid) {
dd07bbac 747 var form = document.forms[formid];
b51709c1 748 var dependons = eval(formid + 'items');
749 var tolock = [];
41f23791 750 var tohide = [];
dd07bbac 751 for (var dependon in dependons) {
d63ef3b8 752 // change for MooTools compatibility
753 if (!dependons.propertyIsEnumerable(dependon)) {
754 continue;
755 }
b51709c1 756 if (!form[dependon]) {
4bc24867 757 continue;
758 }
dd07bbac 759 for (var condition in dependons[dependon]) {
760 for (var value in dependons[dependon][condition]) {
761 var lock;
41f23791 762 var hide = false;
dd07bbac 763 switch (condition) {
764 case 'notchecked':
f855a5d2 765 lock = !form[dependon].checked;break;
dd07bbac 766 case 'checked':
f855a5d2 767 lock = form[dependon].checked;break;
dd07bbac 768 case 'noitemselected':
f855a5d2 769 lock = form[dependon].selectedIndex == -1;break;
dd07bbac 770 case 'eq':
f855a5d2 771 lock = get_form_element_value(form, dependon) == value;break;
41f23791 772 case 'hide':
773 // hide as well as disable
f855a5d2 774 hide = true;break;
dd07bbac 775 default:
f855a5d2 776 lock = get_form_element_value(form, dependon) != value;break;
dd07bbac 777 }
778 for (var ei in dependons[dependon][condition][value]) {
632b88d5 779 var eltolock = dependons[dependon][condition][value][ei];
41f23791 780 if (hide) {
781 tohide[eltolock] = true;
782 }
b51709c1 783 if (tolock[eltolock] != null) {
784 tolock[eltolock] = lock || tolock[eltolock];
632b88d5 785 } else {
786 tolock[eltolock] = lock;
787 }
dd07bbac 788 }
789 }
50ef8eb9 790 }
dd07bbac 791 }
b51709c1 792 for (var el in tolock) {
d63ef3b8 793 // change for MooTools compatibility
794 if (!tolock.propertyIsEnumerable(el)) {
795 continue;
796 }
b51709c1 797 set_form_element_disabled(form, el, tolock[el]);
41f23791 798 if (tohide.propertyIsEnumerable(el)) {
799 set_form_element_hidden(form, el, tolock[el]);
800 }
632b88d5 801 }
dd07bbac 802 return true;
50ef8eb9 803}
804
d01a38cb 805function lockoptionsallsetup(formid) {
dd07bbac 806 var form = document.forms[formid];
807 var dependons = eval(formid+'items');
808 for (var dependon in dependons) {
d63ef3b8 809 // change for MooTools compatibility
810 if (!dependons.propertyIsEnumerable(dependon)) {
811 continue;
812 }
b51709c1 813 var masters = form[dependon];
814 if (!masters) {
4bc24867 815 continue;
816 }
a4db2e7c 817 if (masters.tagName) {
b51709c1 818 // If master is radio buttons, we get an array, otherwise we don't.
819 // Convert both cases to an array for convinience.
820 masters = [masters];
821 }
822 for (var j = 0; j < masters.length; j++) {
823 master = masters[j];
824 master.formid = formid;
825 master.onclick = function() {return lockoptionsall(this.formid);};
826 master.onblur = function() {return lockoptionsall(this.formid);};
827 master.onchange = function() {return lockoptionsall(this.formid);};
828 }
dd07bbac 829 }
b51709c1 830 for (var i = 0; i < form.elements.length; i++) {
dd07bbac 831 var formelement = form.elements[i];
832 if (formelement.type=='reset') {
c0056e22 833 formelement.formid = formid;
834 formelement.onclick = function() {this.form.reset();return lockoptionsall(this.formid);};
835 formelement.onblur = function() {this.form.reset();return lockoptionsall(this.formid);};
836 formelement.onchange = function() {this.form.reset();return lockoptionsall(this.formid);};
dd07bbac 837 }
838 }
839 return lockoptionsall(formid);
d01a38cb 840}
50ef8eb9 841
45caa363 842/**
843 * Either check, or uncheck, all checkboxes inside the element with id is
844 * @param id the id of the container
845 * @param checked the new state, either '' or 'checked'.
846 */
847function select_all_in_element_with_id(id, checked) {
848 var container = document.getElementById(id);
849 if (!container) {
850 return;
851 }
852 var inputs = container.getElementsByTagName('input');
853 for (var i = 0; i < inputs.length; ++i) {
854 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
855 inputs[i].checked = checked;
856 }
857 }
858}
859
8ceb09e0 860function select_all_in(elTagName, elClass, elId) {
d2ce367f 861 var inputs = document.getElementsByTagName('input');
8ceb09e0 862 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 863 for(var i = 0; i < inputs.length; ++i) {
bee40515 864 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 865 inputs[i].checked = 'checked';
866 }
867 }
868}
869
8ceb09e0 870function deselect_all_in(elTagName, elClass, elId) {
363cb62c 871 var inputs = document.getElementsByTagName('INPUT');
8ceb09e0 872 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 873 for(var i = 0; i < inputs.length; ++i) {
bee40515 874 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 875 inputs[i].checked = '';
876 }
877 }
878}
879
880function confirm_if(expr, message) {
881 if(!expr) {
882 return true;
883 }
884 return confirm(message);
885}
47aa42e2 886
887
888/*
889 findParentNode (start, elementName, elementClass, elementID)
50ef8eb9 890
47aa42e2 891 Travels up the DOM hierarchy to find a parent element with the
892 specified tag name, class, and id. All conditions must be met,
893 but any can be ommitted. Returns the BODY element if no match
894 found.
895*/
896function findParentNode(el, elName, elClass, elId) {
45caa363 897 while (el.nodeName.toUpperCase() != 'BODY') {
898 if ((!elName || el.nodeName.toUpperCase() == elName) &&
47aa42e2 899 (!elClass || el.className.indexOf(elClass) != -1) &&
45caa363 900 (!elId || el.id == elId)) {
47aa42e2 901 break;
902 }
903 el = el.parentNode;
904 }
905 return el;
906}
19194f82 907/*
908 findChildNode (start, elementName, elementClass, elementID)
909
910 Travels down the DOM hierarchy to find all child elements with the
911 specified tag name, class, and id. All conditions must be met,
912 but any can be ommitted.
a23f0aaf 913 Doesn't examine children of matches.
19194f82 914*/
915function findChildNodes(start, tagName, elementClass, elementID, elementName) {
916 var children = new Array();
83c9a8a2 917 for (var i = 0; i < start.childNodes.length; i++) {
a23f0aaf 918 var classfound = false;
83c9a8a2 919 var child = start.childNodes[i];
a23f0aaf 920 if((child.nodeType == 1) &&//element node type
b51709c1 921 (elementClass && (typeof(child.className)=='string'))) {
a23f0aaf 922 var childClasses = child.className.split(/\s+/);
b51709c1 923 for (var childClassIndex in childClasses) {
924 if (childClasses[childClassIndex]==elementClass) {
a23f0aaf 925 classfound = true;
926 break;
927 }
928 }
929 }
f07b9627 930 if(child.nodeType == 1) { //element node type
931 if ( (!tagName || child.nodeName == tagName) &&
932 (!elementClass || classfound)&&
933 (!elementID || child.id == elementID) &&
934 (!elementName || child.name == elementName))
935 {
936 children = children.concat(child);
937 } else {
938 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
939 }
19194f82 940 }
19194f82 941 }
942 return children;
943}
944/*
945 elementSetHide (elements, hide)
946
947 Adds or removes the "hide" class for the specified elements depending on boolean hide.
948*/
949function elementShowAdvanced(elements, show) {
b51709c1 950 for (var elementIndex in elements) {
19194f82 951 element = elements[elementIndex];
952 element.className = element.className.replace(new RegExp(' ?hide'), '')
953 if(!show) {
954 element.className += ' hide';
955 }
956 }
957}
958
cd350b53 959function showAdvancedInit(addBefore, nameAttr, buttonLabel, hideText, showText) {
960 var showHideButton = document.createElement("input");
961 showHideButton.type = 'button';
962 showHideButton.value = buttonLabel;
963 showHideButton.name = nameAttr;
964 showHideButton.moodle = {
2b728cb5
PS
965 hideLabel: M.str.form.hideadvanced,
966 showLabel: M.str.form.showadvanced
cd350b53 967 };
968 YAHOO.util.Event.addListener(showHideButton, 'click', showAdvancedOnClick);
969 el = document.getElementById(addBefore);
970 el.parentNode.insertBefore(showHideButton, el);
971}
972
973function showAdvancedOnClick(e) {
974 var button = e.target ? e.target : e.srcElement;
975
19194f82 976 var toSet=findChildNodes(button.form, null, 'advanced');
977 var buttontext = '';
74d15d35 978 if (button.form.elements['mform_showadvanced_last'].value == '0' || button.form.elements['mform_showadvanced_last'].value == '' ) {
19194f82 979 elementShowAdvanced(toSet, true);
cd350b53 980 buttontext = button.moodle.hideLabel;
19194f82 981 button.form.elements['mform_showadvanced_last'].value = '1';
982 } else {
983 elementShowAdvanced(toSet, false);
cd350b53 984 buttontext = button.moodle.showLabel;
19194f82 985 button.form.elements['mform_showadvanced_last'].value = '0';
986 }
987 var formelements = button.form.elements;
4ea054ec 988 // Fixed MDL-10506
b51709c1 989 for (var i = 0; i < formelements.length; i++) {
990 if (formelements[i] && formelements[i].name && (formelements[i].name=='mform_showadvanced')) {
19194f82 991 formelements[i].value = buttontext;
992 }
993 }
994 //never submit the form if js is enabled.
995 return false;
996}
47aa42e2 997
54bb33eb 998function unmaskPassword(id) {
239ade45 999 var pw = document.getElementById(id);
54bb33eb 1000 var chb = document.getElementById(id+'unmask');
239ade45 1001
1002 try {
1003 // first try IE way - it can not set name attribute later
1004 if (chb.checked) {
1005 var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
1006 } else {
1007 var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
1008 }
eba8cd63 1009 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
239ade45 1010 } catch (e) {
1011 var newpw = document.createElement('input');
1012 newpw.setAttribute('name', pw.name);
1013 if (chb.checked) {
1014 newpw.setAttribute('type', 'text');
1015 } else {
1016 newpw.setAttribute('type', 'password');
1017 }
eba8cd63 1018 newpw.setAttribute('class', pw.getAttribute('class'));
239ade45 1019 }
1020 newpw.id = pw.id;
1021 newpw.size = pw.size;
1022 newpw.onblur = pw.onblur;
1023 newpw.onchange = pw.onchange;
1024 newpw.value = pw.value;
1025 pw.parentNode.replaceChild(newpw, pw);
1026}
1027
47aa42e2 1028function filterByParent(elCollection, parentFinder) {
1029 var filteredCollection = [];
45caa363 1030 for (var i = 0; i < elCollection.length; ++i) {
47aa42e2 1031 var findParent = parentFinder(elCollection[i]);
45caa363 1032 if (findParent.nodeName.toUpperCase != 'BODY') {
47aa42e2 1033 filteredCollection.push(elCollection[i]);
1034 }
1035 }
1036 return filteredCollection;
1037}
1038
7979105c 1039/*
1040 All this is here just so that IE gets to handle oversized blocks
1041 in a visually pleasing manner. It does a browser detect. So sue me.
1042*/
1043
1044function fix_column_widths() {
1045 var agt = navigator.userAgent.toLowerCase();
1046 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1047 fix_column_width('left-column');
1048 fix_column_width('right-column');
1049 }
1050}
1051
1052function fix_column_width(colName) {
1053 if(column = document.getElementById(colName)) {
1054 if(!column.offsetWidth) {
1055 setTimeout("fix_column_width('" + colName + "')", 20);
1056 return;
1057 }
1058
1059 var width = 0;
1060 var nodes = column.childNodes;
1061
1062 for(i = 0; i < nodes.length; ++i) {
6605ff8c 1063 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 1064 if(width < nodes[i].offsetWidth) {
1065 width = nodes[i].offsetWidth;
1066 }
1067 }
1068 }
1069
1070 for(i = 0; i < nodes.length; ++i) {
6605ff8c 1071 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 1072 nodes[i].style.width = width + 'px';
1073 }
1074 }
1075 }
1076}
d13c5938 1077
1078
1079/*
9f439b17 1080 Insert myValue at current cursor position
1081 */
d13c5938 1082function insertAtCursor(myField, myValue) {
9f439b17 1083 // IE support
1084 if (document.selection) {
1085 myField.focus();
1086 sel = document.selection.createRange();
1087 sel.text = myValue;
1088 }
1089 // Mozilla/Netscape support
1090 else if (myField.selectionStart || myField.selectionStart == '0') {
1091 var startPos = myField.selectionStart;
1092 var endPos = myField.selectionEnd;
1093 myField.value = myField.value.substring(0, startPos)
1094 + myValue + myField.value.substring(endPos, myField.value.length);
1095 } else {
1096 myField.value += myValue;
1097 }
d13c5938 1098}
7470d6de 1099
1100
1101/*
c0056e22 1102 Call instead of setting window.onload directly or setting body onload=.
1103 Adds your function to a chain of functions rather than overwriting anything
1104 that exists.
1105*/
7470d6de 1106function addonload(fn) {
1107 var oldhandler=window.onload;
1108 window.onload=function() {
1109 if(oldhandler) oldhandler();
c0056e22 1110 fn();
7470d6de 1111 }
1112}
7d2a0492 1113/**
1114 * Replacement for getElementsByClassName in browsers that aren't cool enough
6f5e0852 1115 *
7d2a0492 1116 * Relying on the built-in getElementsByClassName is far, far faster than
1117 * using YUI.
6f5e0852 1118 *
7d2a0492 1119 * Note: the third argument used to be an object with odd behaviour. It now
1120 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1121 * mimicked if you pass an object.
1122 *
1123 * @param {Node} oElm The top-level node for searching. To search a whole
1124 * document, use `document`.
1125 * @param {String} strTagName filter by tag names
1126 * @param {String} name same as HTML5 spec
1127 */
1128function getElementsByClassName(oElm, strTagName, name) {
1129 // for backwards compatibility
1130 if(typeof name == "object") {
1131 var names = new Array();
1132 for(var i=0; i<name.length; i++) names.push(names[i]);
1133 name = names.join('');
1134 }
1135 // use native implementation if possible
1136 if (oElm.getElementsByClassName && Array.filter) {
1137 if (strTagName == '*') {
1138 return oElm.getElementsByClassName(name);
1139 } else {
1140 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1141 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1142 });
1143 }
1144 }
1145 // native implementation unavailable, fall back to slow method
c849ed1e 1146 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1147 var arrReturnElements = new Array();
1148 var arrRegExpClassNames = new Array();
7d2a0492 1149 var names = name.split(' ');
1150 for(var i=0; i<names.length; i++) {
1151 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
c849ed1e 1152 }
1153 var oElement;
1154 var bMatchesAll;
b51709c1 1155 for(var j=0; j<arrElements.length; j++) {
c849ed1e 1156 oElement = arrElements[j];
1157 bMatchesAll = true;
b51709c1 1158 for(var k=0; k<arrRegExpClassNames.length; k++) {
1159 if(!arrRegExpClassNames[k].test(oElement.className)) {
c849ed1e 1160 bMatchesAll = false;
1161 break;
1162 }
1163 }
b51709c1 1164 if(bMatchesAll) {
c849ed1e 1165 arrReturnElements.push(oElement);
1166 }
1167 }
1168 return (arrReturnElements)
1169}
77241d9b 1170
f8065dd2 1171function openpopup(event, args) {
1172
2cf81209
SH
1173 if (event) {
1174 YAHOO.util.Event.preventDefault(event);
1175 }
36282d85 1176
2cf81209
SH
1177 var fullurl = args.url;
1178 if (!args.url.match(/https?:\/\//)) {
1179 fullurl = M.cfg.wwwroot + args.url;
77241d9b 1180 }
2cf81209
SH
1181 var windowobj = window.open(fullurl,args.name,args.options);
1182 if (!windowobj) {
1183 return true;
1184 }
1185 if (args.fullscreen) {
1186 windowobj.moveTo(0,0);
1187 windowobj.resizeTo(screen.availWidth,screen.availHeight);
1188 }
1189 windowobj.focus();
1190
1191 return false;
77241d9b 1192}
740939ec 1193
1194/* This is only used on a few help pages. */
1195emoticons_help = {
1196 inputarea: null,
1197
1198 init: function(formname, fieldname, listid) {
1199 if (!opener || !opener.document.forms[formname]) {
1200 return;
1201 }
1202 emoticons_help.inputarea = opener.document.forms[formname][fieldname];
1203 if (!emoticons_help.inputarea) {
1204 return;
1205 }
1206 var emoticons = document.getElementById(listid).getElementsByTagName('li');
1207 for (var i = 0; i < emoticons.length; i++) {
1208 var text = emoticons[i].getElementsByTagName('img')[0].alt;
1209 YAHOO.util.Event.addListener(emoticons[i], 'click', emoticons_help.inserttext, text);
1210 }
1211 },
1212
1213 inserttext: function(e, text) {
1214 text = ' ' + text + ' ';
1215 if (emoticons_help.inputarea.createTextRange && emoticons_help.inputarea.caretPos) {
1216 var caretPos = emoticons_help.inputarea.caretPos;
1217 caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
1218 } else {
1219 emoticons_help.inputarea.value += text;
1220 }
1221 emoticons_help.inputarea.focus();
1222 }
bd1884fe 1223}
1224
d4a03c00 1225/**
1226 * Oject to handle expanding and collapsing blocks when an icon is clicked on.
1227 * @constructor
1228 * @param String id the HTML id for the div.
1229 * @param String userpref the user preference that records the state of this block.
1230 * @param String visibletooltip tool tip/alt to show when the block is visible.
1231 * @param String hiddentooltip tool tip/alt to show when the block is hidden.
1232 * @param String visibleicon URL of the icon to show when the block is visible.
1233 * @param String hiddenicon URL of the icon to show when the block is hidden.
1234 */
1235function block_hider(id, userpref, visibletooltip, hiddentooltip, visibleicon, hiddenicon) {
1236 // Find the elemen that is the block.
1237 this.block = document.getElementById(id);
1238 var title_div = YAHOO.util.Dom.getElementsByClassName('title', 'div', this.block);
1239 if (!title_div || !title_div[0]) {
1240 return this;
1241 }
1242 title_div = title_div[0];
1243 this.ishidden = YAHOO.util.Dom.hasClass(this.block, 'hidden');
1244
1245 // Record the pref name
1246 this.userpref = userpref;
1247 this.visibletooltip = visibletooltip;
1248 this.hiddentooltip = hiddentooltip;
1249 this.visibleicon = visibleicon;
1250 this.hiddenicon = hiddenicon;
1251
1252 // Add the icon.
1253 this.icon = document.createElement('input');
1254 this.icon.type = 'image';
d4a03c00 1255 this.update_state();
46de77b6
SH
1256
1257 var blockactions = YAHOO.util.Dom.getElementsByClassName('block_action', 'div', title_div);
1258 if (blockactions && blockactions[0]) {
1259 blockactions[0].insertBefore(this.icon, blockactions[0].firstChild);
1260 }
d4a03c00 1261
1262 // Hook up the event handler.
1263 YAHOO.util.Event.addListener(this.icon, 'click', this.handle_click, null, this);
1264}
1265
1266/** Handle click on a block show/hide icon. */
1267block_hider.prototype.handle_click = function(e) {
1268 YAHOO.util.Event.stopEvent(e);
1269 this.ishidden = !this.ishidden;
1270 this.update_state();
41912a26 1271 M.util.set_user_preference(this.userpref, this.ishidden);
d4a03c00 1272}
1273
1274/** Set the state of the block show/hide icon to this.ishidden. */
1275block_hider.prototype.update_state = function () {
1276 if (this.ishidden) {
1277 YAHOO.util.Dom.addClass(this.block, 'hidden');
1278 this.icon.alt = this.hiddentooltip;
1279 this.icon.title = this.hiddentooltip;
1280 this.icon.src = this.hiddenicon;
1281 } else {
1282 YAHOO.util.Dom.removeClass(this.block, 'hidden');
1283 this.icon.alt = this.visibletooltip;
1284 this.icon.title = this.visibletooltip;
1285 this.icon.src = this.visibleicon;
1286 }
1287}
1288
b166403f 1289/** Close the current browser window. */
7a5c78e0 1290function close_window(e) {
1291 YAHOO.util.Event.preventDefault(e);
1292 self.close();
b166403f 1293}
1294
1295/**
1296 * Close the current browser window, forcing the window/tab that opened this
1297 * popup to reload itself. */
1298function close_window_reloading_opener() {
1299 if (window.opener) {
1300 window.opener.location.reload(1);
1301 close_window();
1302 // Intentionally, only try to close the window if there is some evidence we are in a popup.
1303 }
8e7cebb0 1304}
9f319372 1305
1306/**
1307 * Used in a couple of modules to hide navigation areas when using AJAX
1308 */
34a2777c 1309
2538037f 1310function show_item(itemid) {
1311 var item = document.getElementById(itemid);
1312 if (item) {
1313 item.style.display = "";
1314 }
1315}
1316
1317function destroy_item(itemid) {
1318 var item = document.getElementById(itemid);
1319 if (item) {
1320 item.parentNode.removeChild(item);
1321 }
1322}
34a2777c 1323/**
1324 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1325 * @param controlid the control id.
1326 */
1327function focuscontrol(controlid) {
1328 var control = document.getElementById(controlid);
1329 if (control) {
1330 control.focus();
1331 }
e29380f3 1332}
1333
e11a8328 1334/**
1335 * Transfers keyboard focus to an HTML element based on the old style style of focus
1336 * This function should be removed as soon as it is no longer used
1337 */
428acddb 1338function old_onload_focus(formid, controlname) {
5f8bce50 1339 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
428acddb 1340 document.forms[formid].elements[controlname].focus();
e11a8328 1341 }
1342}
1343
1bcb7eb5 1344function build_querystring(obj) {
aa281068
AD
1345 return convert_object_to_string(obj, '&');
1346}
1347
1348function build_windowoptionsstring(obj) {
1349 return convert_object_to_string(obj, ',');
1350}
1351
1352function convert_object_to_string(obj, separator) {
1bcb7eb5 1353 if (typeof obj !== 'object') {
1354 return null;
1355 }
1356 var list = [];
1357 for(var k in obj) {
1358 k = encodeURIComponent(k);
1359 var value = obj[k];
1360 if(obj[k] instanceof Array) {
1361 for(var i in value) {
1362 list.push(k+'[]='+encodeURIComponent(value[i]));
1363 }
1364 } else {
1365 list.push(k+'='+encodeURIComponent(value));
1366 }
1367 }
aa281068 1368 return list.join(separator);
1bcb7eb5 1369}
d25e2ca3 1370
1371function stripHTML(str) {
1372 var re = /<\S[^><]*>/g;
1373 var ret = str.replace(re, "");
1374 return ret;
1375}
1376
fd4faf98 1377Number.prototype.fixed=function(n){
1378 with(Math)
1379 return round(Number(this)*pow(10,n))/pow(10,n);
1380}
1381function update_progress_bar (id, width, pt, msg, es){
1382 var percent = pt*100;
1383 var status = document.getElementById("status_"+id);
1384 var percent_indicator = document.getElementById("pt_"+id);
1385 var progress_bar = document.getElementById("progress_"+id);
1386 var time_es = document.getElementById("time_"+id);
1387 status.innerHTML = msg;
1388 percent_indicator.innerHTML = percent.fixed(2) + '%';
1389 if(percent == 100) {
1390 progress_bar.style.background = "green";
1391 time_es.style.display = "none";
1392 } else {
1393 progress_bar.style.background = "#FFCC66";
1394 if (es == Infinity){
1395 time_es.innerHTML = "Initializing...";
1396 }else {
1397 time_es.innerHTML = es.fixed(2)+" sec";
1398 time_es.style.display
1399 = "block";
1400 }
1401 }
1402 progress_bar.style.width = width + "px";
1403
1404}
bf6c37c7 1405
1406function frame_breakout(e, properties) {
1407 this.setAttribute('target', properties.framename);
41f23791 1408}
78946b9b 1409
73b62703
PS
1410
1411// ===== Deprecated core Javascript functions for Moodle ====
1412// DO NOT USE!!!!!!!
e50b4c89 1413// Do not put this stuff in separate file because it only adds extra load on servers!
73b62703 1414
2d65597b
PS
1415/**
1416 * Used in a couple of modules to hide navigation areas when using AJAX
1417 */
1418function hide_item(itemid) {
3b044ba3 1419 // use class='hiddenifjs' instead
2d65597b
PS
1420 var item = document.getElementById(itemid);
1421 if (item) {
1422 item.style.display = "none";
1423 }
1424}
36282d85 1425
2cf81209
SH
1426M.util.help_icon = {
1427 Y : null,
36282d85 1428 instance : null,
2cf81209
SH
1429 add : function(Y, properties) {
1430 this.Y = Y;
61ddb953
RW
1431 properties.node = Y.one('#'+properties.id);
1432 if (properties.node) {
1433 properties.node.on('click', this.display, this, properties);
f855a5d2 1434 }
2cf81209
SH
1435 },
1436 display : function(event, args) {
1437 event.preventDefault();
1438 if (M.util.help_icon.instance === null) {
1439 var Y = M.util.help_icon.Y;
61ddb953 1440 Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
2cf81209 1441 var help_content_overlay = {
61ddb953 1442 helplink : null,
2cf81209
SH
1443 overlay : null,
1444 init : function() {
1445
61ddb953 1446 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
2cf81209
SH
1447 // Create an overlay from markup
1448 this.overlay = new Y.Overlay({
1449 headerContent: closebtn,
1450 bodyContent: '',
61ddb953
RW
1451 id: 'helppopupbox',
1452 width:'400px',
2cf81209 1453 visible : false,
61ddb953 1454 constrain : true
2cf81209
SH
1455 });
1456 this.overlay.render(Y.one(document.body));
1457
1458 closebtn.on('click', this.overlay.hide, this.overlay);
61ddb953 1459
2cf81209 1460 var boundingBox = this.overlay.get("boundingBox");
2cf81209
SH
1461
1462 // Hide the menu if the user clicks outside of its content
1463 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1464 var oTarget = event.target;
1465 var menuButton = Y.one("#"+args.id);
61ddb953 1466
2cf81209
SH
1467 if (!oTarget.compareTo(menuButton) &&
1468 !menuButton.contains(oTarget) &&
1469 !oTarget.compareTo(boundingBox) &&
1470 !boundingBox.contains(oTarget)) {
1471 this.overlay.hide();
1472 }
1473 }, this);
61ddb953
RW
1474
1475 Y.on("key", this.close, closebtn , "down:13", this);
1476 closebtn.on('click', this.close, this);
1477 },
1478
1479 close : function(e) {
1480 e.preventDefault();
1481 this.helplink.focus();
1482 this.overlay.hide();
2cf81209
SH
1483 },
1484
1485 display : function(event, args) {
61ddb953 1486 this.helplink = args.node;
2cf81209 1487 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
61ddb953 1488 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
2cf81209
SH
1489
1490 var fullurl = args.url;
1491 if (!args.url.match(/https?:\/\//)) {
1492 fullurl = M.cfg.wwwroot + args.url;
36282d85 1493 }
36282d85 1494
2cf81209
SH
1495 var ajaxurl = fullurl + '&ajax=1';
1496
1497 var cfg = {
1498 method: 'get',
1499 context : this,
1500 on: {
1501 success: function(id, o, node) {
1502 this.display_callback(o.responseText);
1503 },
1504 failure: function(id, o, node) {
1505 var debuginfo = o.statusText;
1506 if (M.cfg.developerdebug) {
1507 o.statusText += ' (' + ajaxurl + ')';
1508 }
1509 this.display_callback('bodyContent',debuginfo);
1510 }
1511 }
1512 };
36282d85 1513
2cf81209
SH
1514 Y.io(ajaxurl, cfg);
1515 this.overlay.show();
61ddb953
RW
1516
1517 Y.one('#closehelpbox').focus();
2cf81209 1518 },
36282d85 1519
2cf81209 1520 display_callback : function(content) {
61ddb953 1521 this.overlay.set('bodyContent', content);
2cf81209
SH
1522 },
1523
1524 hideContent : function() {
1525 help = this;
1526 help.overlay.hide();
36282d85 1527 }
2cf81209
SH
1528 }
1529 help_content_overlay.init();
1530 M.util.help_icon.instance = help_content_overlay;
1531 M.util.help_icon.instance.display(event, args);
1532 });
1533 } else {
1534 M.util.help_icon.instance.display(event, args);
1535 }
1536 },
1537 init : function(Y) {
61ddb953 1538 this.Y = Y;
36282d85
RW
1539 }
1540}
d2dbd0c0
SH
1541
1542/**
1543 * Custom menu namespace
1544 */
1545M.core_custom_menu = {
1546 /**
1547 * This method is used to initialise a custom menu given the id that belongs
1548 * to the custom menu's root node.
1549 *
1550 * @param {YUI} Y
1551 * @param {string} nodeid
1552 */
1553 init : function(Y, nodeid) {
242b78f7
SH
1554 var node = Y.one('#'+nodeid);
1555 if (node) {
1556 Y.use('node-menunav', function(Y) {
1557 // Get the node
1558 // Remove the javascript-disabled class.... obviously javascript is enabled.
1559 node.removeClass('javascript-disabled');
1560 // Initialise the menunav plugin
1561 node.plug(Y.Plugin.NodeMenuNav);
1562 });
1563 }
d2dbd0c0 1564 }
572dd8ec
SH
1565}
1566
1567/**
1568 * Used to store form manipulation methods and enhancments
1569 */
1570M.form = M.form || {};
1571
1572/**
1573 * Converts a nbsp indented select box into a multi drop down custom control much
1574 * like the custom menu. It also selectable categories on or off.
1575 *
1576 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1577 *
1578 * @param {YUI} Y
1579 * @param {string} id
1580 * @param {Array} options
1581 */
1582M.form.init_smartselect = function(Y, id, options) {
882e66fe
SH
1583 if (!id.match(/^id_/)) {
1584 id = 'id_'+id;
1585 }
1586 var select = Y.one('select#'+id);
572dd8ec
SH
1587 if (!select) {
1588 return false;
1589 }
1590 Y.use('event-delegate',function(){
1591 var smartselect = {
1592 id : id,
1593 structure : [],
1594 options : [],
1595 submenucount : 0,
1596 currentvalue : null,
1597 currenttext : null,
1598 shownevent : null,
1599 cfg : {
1600 selectablecategories : true,
1601 mode : null
1602 },
1603 nodes : {
1604 select : null,
1605 loading : null,
1606 menu : null
1607 },
1608 init : function(Y, id, args, nodes) {
1609 if (typeof(args)=='object') {
1610 for (var i in this.cfg) {
1611 if (args[i] || args[i]===false) {
1612 this.cfg[i] = args[i];
1613 }
1614 }
1615 }
1616
1617 // Display a loading message first up
1618 this.nodes.select = nodes.select;
1619
1620 this.currentvalue = this.nodes.select.get('selectedIndex');
1621 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1622
1623 var options = Array();
1624 options[''] = {text:this.currenttext,value:'',depth:0,children:[]}
1625 this.nodes.select.all('option').each(function(option, index) {
1626 var rawtext = option.get('innerHTML');
1627 var text = rawtext.replace(/^(&nbsp;)*/, '');
1628 if (rawtext === text) {
1629 text = rawtext.replace(/^(\s)*/, '');
1630 var depth = (rawtext.length - text.length ) + 1;
1631 } else {
1632 var depth = ((rawtext.length - text.length )/12)+1;
1633 }
1634 option.set('innerHTML', text);
1635 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1636 }, this);
1637
1638 this.structure = [];
1639 var structcount = 0;
1640 for (var i in options) {
1641 var o = options[i];
1642 if (o.depth == 0) {
1643 this.structure.push(o);
1644 structcount++;
1645 } else {
1646 var d = o.depth;
1647 var current = this.structure[structcount-1];
1648 for (var j = 0; j < o.depth-1;j++) {
1649 if (current && current.children) {
1650 current = current.children[current.children.length-1];
1651 }
1652 }
1653 if (current && current.children) {
1654 current.children.push(o);
1655 }
1656 }
1657 }
1658
1659 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1660 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1661 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1662 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1663
1664 if (this.cfg.mode == null) {
1665 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1666 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1667 this.cfg.mode = 'compact';
1668 } else {
1669 this.cfg.mode = 'spanning';
1670 }
1671 }
1672
1673 if (this.cfg.mode == 'compact') {
1674 this.nodes.menu.addClass('compactmenu');
1675 } else {
1676 this.nodes.menu.addClass('spanningmenu');
1677 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1678 }
1679
1680 Y.one(document.body).append(this.nodes.menu);
1681 var pos = this.nodes.select.getXY();
1682 pos[0] += 1;
1683 this.nodes.menu.setXY(pos);
1684 this.nodes.menu.on('click', this.handle_click, this);
32690ac2
SH
1685
1686 Y.one(window).on('resize', function(){
1687 var pos = this.nodes.select.getXY();
1688 pos[0] += 1;
1689 this.nodes.menu.setXY(pos);
1690 }, this);
572dd8ec
SH
1691 },
1692 generate_menu_content : function() {
1693 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1694 content += this.generate_submenu_content(this.structure[0], true);
1695 content += '</ul></div>';
1696 return content;
1697 },
1698 generate_submenu_content : function(item, rootelement) {
1699 this.submenucount++;
1700 var content = '';
1701 if (item.children.length > 0) {
1702 if (rootelement) {
1703 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1704 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1705 content += '<div class="smartselect_menu_content">';
1706 } else {
1707 content += '<li class="smartselect_submenuitem">';
1708 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1709 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1710 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1711 content += '<div class="smartselect_submenu_content">';
1712 }
1713 content += '<ul>';
1714 for (var i in item.children) {
1715 content += this.generate_submenu_content(item.children[i],false);
1716 }
1717 content += '</ul>';
1718 content += '</div>';
1719 content += '</div>';
1720 if (rootelement) {
1721 } else {
1722 content += '</li>';
1723 }
1724 } else {
1725 content += '<li class="smartselect_menuitem">';
1726 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1727 content += '</li>';
1728 }
1729 return content;
1730 },
1731 select : function(e) {
1732 var t = e.target;
1733 e.halt();
1734 this.currenttext = t.get('innerHTML');
1735 this.currentvalue = t.getAttribute('value');
1736 this.nodes.select.set('selectedIndex', this.currentvalue);
1737 this.hide_menu();
1738 },
1739 handle_click : function(e) {
1740 var target = e.target;
1741 if (target.hasClass('smartselect_mask')) {
1742 this.show_menu(e);
1743 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1744 this.select(e);
1745 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1746 this.show_sub_menu(e);
1747 }
1748 },
1749 show_menu : function(e) {
1750 e.halt();
1751 var menu = e.target.ancestor().one('.smartselect_menu');
1752 menu.addClass('visible');
1753 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1754 },
1755 show_sub_menu : function(e) {
1756 e.halt();
1757 var target = e.target;
1758 if (!target.hasClass('smartselect_submenuitem')) {
1759 target = target.ancestor('.smartselect_submenuitem');
1760 }
1761 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1762 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1763 return;
1764 }
1765 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1766 target.one('.smartselect_submenu').addClass('visible');
1767 },
1768 hide_menu : function() {
1769 this.nodes.menu.all('.visible').removeClass('visible');
1770 if (this.shownevent) {
1771 this.shownevent.detach();
1772 }
1773 }
1774 }
1775 smartselect.init(Y, id, options, {select:select});
1776 });
d2dbd0c0 1777}