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