MDL-22805, new renderer component moodle_file_tree_viewer, remove file_tree_viewer...
[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
38224dcb
PS
430//=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
431
b48fd7b5 432function popupchecker(msg) {
496e3ccd 433 var testwindow = window.open('', '', 'width=1,height=1,left=0,top=0,scrollbars=no');
f6b6861d 434 if (!testwindow) {
435 alert(msg);
436 } else {
b48fd7b5 437 testwindow.close();
438 }
439}
440
63d28811 441function checkall() {
214f5850 442 var inputs = document.getElementsByTagName('input');
443 for (var i = 0; i < inputs.length; i++) {
444 if (inputs[i].type == 'checkbox') {
445 inputs[i].checked = true;
446 }
d2ce367f 447 }
63d28811 448}
449
03f9425f 450function checknone() {
214f5850 451 var inputs = document.getElementsByTagName('input');
452 for (var i = 0; i < inputs.length; i++) {
453 if (inputs[i].type == 'checkbox') {
454 inputs[i].checked = false;
455 }
d2ce367f 456 }
03f9425f 457}
458
d2ce367f 459function lockoptions(formid, master, subitems) {
027d0485 460 // Subitems is an array of names of sub items.
461 // Optionally, each item in subitems may have a
63d28811 462 // companion hidden item in the form with the
027d0485 463 // same name but prefixed by "h".
d2ce367f 464 var form = document.forms[formid];
465
466 if (eval("form."+master+".checked")) {
63d28811 467 for (i=0; i<subitems.length; i++) {
468 unlockoption(form, subitems[i]);
469 }
470 } else {
471 for (i=0; i<subitems.length; i++) {
472 lockoption(form, subitems[i]);
473 }
474 }
475 return(true);
476}
477
f07b9627 478function lockoption(form,item) {
d2ce367f 479 eval("form."+item+".disabled=true");/* IE thing */
480 if(form.elements['h'+item]) {
481 eval("form.h"+item+".value=1");
f07b9627 482 }
483}
484
485function unlockoption(form,item) {
d2ce367f 486 eval("form."+item+".disabled=false");/* IE thing */
487 if(form.elements['h'+item]) {
488 eval("form.h"+item+".value=0");
f07b9627 489 }
490}
dd07bbac 491
b51709c1 492/**
493 * Get the value of the 'virtual form element' with a particular name. That is,
494 * abstracts away the difference between a normal form element, like a select
495 * which is a single HTML element with a .value property, and a set of radio
496 * buttons, which is several HTML elements.
497 *
498 * @param form a HTML form.
499 * @param master the name of an element in that form.
500 * @return the value of that element.
501 */
502function get_form_element_value(form, name) {
503 var element = form[name];
504 if (!element) {
505 return null;
506 }
507 if (element.tagName) {
508 // Ordinarly thing like a select box.
509 return element.value;
510 }
511 // Array of things, like radio buttons.
512 for (var j = 0; j < element.length; j++) {
513 var el = element[j];
514 if (el.checked) {
515 return el.value;
516 }
517 }
518 return null;
519}
520
521
522/**
523 * Set the disabled state of the 'virtual form element' with a particular name.
524 * This abstracts away the difference between a normal form element, like a select
525 * which is a single HTML element with a .value property, and a set of radio
526 * buttons, which is several HTML elements.
527 *
528 * @param form a HTML form.
529 * @param master the name of an element in that form.
530 * @param disabled the disabled state to set.
531 */
532function set_form_element_disabled(form, name, disabled) {
533 var element = form[name];
534 if (!element) {
535 return;
536 }
537 if (element.tagName) {
538 // Ordinarly thing like a select box.
539 element.disabled = disabled;
540 }
541 // Array of things, like radio buttons.
542 for (var j = 0; j < element.length; j++) {
543 var el = element[j];
544 el.disabled = disabled;
545 }
546}
dd07bbac 547
41f23791 548/**
549 * Set the hidden state of the 'virtual form element' with a particular name.
550 * This abstracts away the difference between a normal form element, like a select
551 * which is a single HTML element with a .value property, and a set of radio
552 * buttons, which is several HTML elements.
553 *
554 * @param form a HTML form.
555 * @param master the name of an element in that form.
556 * @param hidden the hidden state to set.
557 */
558function set_form_element_hidden(form, name, hidden) {
559 var element = form[name];
560 if (!element) {
561 return;
562 }
563 if (element.tagName) {
564 var el = findParentNode(element, 'DIV', 'fitem', false);
565 if (el!=null) {
566 el.style.display = hidden ? 'none' : '';
567 el.style.visibility = hidden ? 'hidden' : '';
568 }
569 }
570 // Array of things, like radio buttons.
571 for (var j = 0; j < element.length; j++) {
572 var el = findParentNode(element[j], 'DIV', 'fitem', false);
573 if (el!=null) {
574 el.style.display = hidden ? 'none' : '';
575 el.style.visibility = hidden ? 'hidden' : '';
576 }
577 }
578}
579
50ef8eb9 580function lockoptionsall(formid) {
dd07bbac 581 var form = document.forms[formid];
b51709c1 582 var dependons = eval(formid + 'items');
583 var tolock = [];
41f23791 584 var tohide = [];
dd07bbac 585 for (var dependon in dependons) {
d63ef3b8 586 // change for MooTools compatibility
587 if (!dependons.propertyIsEnumerable(dependon)) {
588 continue;
589 }
b51709c1 590 if (!form[dependon]) {
4bc24867 591 continue;
592 }
dd07bbac 593 for (var condition in dependons[dependon]) {
594 for (var value in dependons[dependon][condition]) {
595 var lock;
41f23791 596 var hide = false;
dd07bbac 597 switch (condition) {
598 case 'notchecked':
f855a5d2 599 lock = !form[dependon].checked;break;
dd07bbac 600 case 'checked':
f855a5d2 601 lock = form[dependon].checked;break;
dd07bbac 602 case 'noitemselected':
f855a5d2 603 lock = form[dependon].selectedIndex == -1;break;
dd07bbac 604 case 'eq':
f855a5d2 605 lock = get_form_element_value(form, dependon) == value;break;
41f23791 606 case 'hide':
607 // hide as well as disable
f855a5d2 608 hide = true;break;
dd07bbac 609 default:
f855a5d2 610 lock = get_form_element_value(form, dependon) != value;break;
dd07bbac 611 }
612 for (var ei in dependons[dependon][condition][value]) {
632b88d5 613 var eltolock = dependons[dependon][condition][value][ei];
41f23791 614 if (hide) {
615 tohide[eltolock] = true;
616 }
b51709c1 617 if (tolock[eltolock] != null) {
618 tolock[eltolock] = lock || tolock[eltolock];
632b88d5 619 } else {
620 tolock[eltolock] = lock;
621 }
dd07bbac 622 }
623 }
50ef8eb9 624 }
dd07bbac 625 }
b51709c1 626 for (var el in tolock) {
d63ef3b8 627 // change for MooTools compatibility
628 if (!tolock.propertyIsEnumerable(el)) {
629 continue;
630 }
b51709c1 631 set_form_element_disabled(form, el, tolock[el]);
41f23791 632 if (tohide.propertyIsEnumerable(el)) {
633 set_form_element_hidden(form, el, tolock[el]);
634 }
632b88d5 635 }
dd07bbac 636 return true;
50ef8eb9 637}
638
d01a38cb 639function lockoptionsallsetup(formid) {
dd07bbac 640 var form = document.forms[formid];
641 var dependons = eval(formid+'items');
642 for (var dependon in dependons) {
d63ef3b8 643 // change for MooTools compatibility
644 if (!dependons.propertyIsEnumerable(dependon)) {
645 continue;
646 }
b51709c1 647 var masters = form[dependon];
648 if (!masters) {
4bc24867 649 continue;
650 }
a4db2e7c 651 if (masters.tagName) {
b51709c1 652 // If master is radio buttons, we get an array, otherwise we don't.
653 // Convert both cases to an array for convinience.
654 masters = [masters];
655 }
656 for (var j = 0; j < masters.length; j++) {
657 master = masters[j];
658 master.formid = formid;
659 master.onclick = function() {return lockoptionsall(this.formid);};
660 master.onblur = function() {return lockoptionsall(this.formid);};
661 master.onchange = function() {return lockoptionsall(this.formid);};
662 }
dd07bbac 663 }
b51709c1 664 for (var i = 0; i < form.elements.length; i++) {
dd07bbac 665 var formelement = form.elements[i];
666 if (formelement.type=='reset') {
c0056e22 667 formelement.formid = formid;
668 formelement.onclick = function() {this.form.reset();return lockoptionsall(this.formid);};
669 formelement.onblur = function() {this.form.reset();return lockoptionsall(this.formid);};
670 formelement.onchange = function() {this.form.reset();return lockoptionsall(this.formid);};
dd07bbac 671 }
672 }
673 return lockoptionsall(formid);
d01a38cb 674}
50ef8eb9 675
45caa363 676/**
677 * Either check, or uncheck, all checkboxes inside the element with id is
678 * @param id the id of the container
679 * @param checked the new state, either '' or 'checked'.
680 */
681function select_all_in_element_with_id(id, checked) {
682 var container = document.getElementById(id);
683 if (!container) {
684 return;
685 }
686 var inputs = container.getElementsByTagName('input');
687 for (var i = 0; i < inputs.length; ++i) {
688 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
689 inputs[i].checked = checked;
690 }
691 }
692}
693
8ceb09e0 694function select_all_in(elTagName, elClass, elId) {
d2ce367f 695 var inputs = document.getElementsByTagName('input');
8ceb09e0 696 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 697 for(var i = 0; i < inputs.length; ++i) {
bee40515 698 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 699 inputs[i].checked = 'checked';
700 }
701 }
702}
703
8ceb09e0 704function deselect_all_in(elTagName, elClass, elId) {
363cb62c 705 var inputs = document.getElementsByTagName('INPUT');
8ceb09e0 706 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 707 for(var i = 0; i < inputs.length; ++i) {
bee40515 708 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 709 inputs[i].checked = '';
710 }
711 }
712}
713
714function confirm_if(expr, message) {
715 if(!expr) {
716 return true;
717 }
718 return confirm(message);
719}
47aa42e2 720
721
722/*
723 findParentNode (start, elementName, elementClass, elementID)
50ef8eb9 724
47aa42e2 725 Travels up the DOM hierarchy to find a parent element with the
726 specified tag name, class, and id. All conditions must be met,
727 but any can be ommitted. Returns the BODY element if no match
728 found.
729*/
730function findParentNode(el, elName, elClass, elId) {
45caa363 731 while (el.nodeName.toUpperCase() != 'BODY') {
732 if ((!elName || el.nodeName.toUpperCase() == elName) &&
47aa42e2 733 (!elClass || el.className.indexOf(elClass) != -1) &&
45caa363 734 (!elId || el.id == elId)) {
47aa42e2 735 break;
736 }
737 el = el.parentNode;
738 }
739 return el;
740}
19194f82 741/*
742 findChildNode (start, elementName, elementClass, elementID)
743
744 Travels down the DOM hierarchy to find all child elements with the
745 specified tag name, class, and id. All conditions must be met,
746 but any can be ommitted.
a23f0aaf 747 Doesn't examine children of matches.
19194f82 748*/
749function findChildNodes(start, tagName, elementClass, elementID, elementName) {
750 var children = new Array();
83c9a8a2 751 for (var i = 0; i < start.childNodes.length; i++) {
a23f0aaf 752 var classfound = false;
83c9a8a2 753 var child = start.childNodes[i];
a23f0aaf 754 if((child.nodeType == 1) &&//element node type
b51709c1 755 (elementClass && (typeof(child.className)=='string'))) {
a23f0aaf 756 var childClasses = child.className.split(/\s+/);
b51709c1 757 for (var childClassIndex in childClasses) {
758 if (childClasses[childClassIndex]==elementClass) {
a23f0aaf 759 classfound = true;
760 break;
761 }
762 }
763 }
f07b9627 764 if(child.nodeType == 1) { //element node type
765 if ( (!tagName || child.nodeName == tagName) &&
766 (!elementClass || classfound)&&
767 (!elementID || child.id == elementID) &&
768 (!elementName || child.name == elementName))
769 {
770 children = children.concat(child);
771 } else {
772 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
773 }
19194f82 774 }
19194f82 775 }
776 return children;
777}
778/*
779 elementSetHide (elements, hide)
780
781 Adds or removes the "hide" class for the specified elements depending on boolean hide.
782*/
783function elementShowAdvanced(elements, show) {
b51709c1 784 for (var elementIndex in elements) {
19194f82 785 element = elements[elementIndex];
786 element.className = element.className.replace(new RegExp(' ?hide'), '')
787 if(!show) {
788 element.className += ' hide';
789 }
790 }
791}
792
cd350b53 793function showAdvancedInit(addBefore, nameAttr, buttonLabel, hideText, showText) {
794 var showHideButton = document.createElement("input");
795 showHideButton.type = 'button';
796 showHideButton.value = buttonLabel;
797 showHideButton.name = nameAttr;
798 showHideButton.moodle = {
2b728cb5
PS
799 hideLabel: M.str.form.hideadvanced,
800 showLabel: M.str.form.showadvanced
cd350b53 801 };
802 YAHOO.util.Event.addListener(showHideButton, 'click', showAdvancedOnClick);
803 el = document.getElementById(addBefore);
804 el.parentNode.insertBefore(showHideButton, el);
805}
806
807function showAdvancedOnClick(e) {
808 var button = e.target ? e.target : e.srcElement;
809
19194f82 810 var toSet=findChildNodes(button.form, null, 'advanced');
811 var buttontext = '';
74d15d35 812 if (button.form.elements['mform_showadvanced_last'].value == '0' || button.form.elements['mform_showadvanced_last'].value == '' ) {
19194f82 813 elementShowAdvanced(toSet, true);
cd350b53 814 buttontext = button.moodle.hideLabel;
19194f82 815 button.form.elements['mform_showadvanced_last'].value = '1';
816 } else {
817 elementShowAdvanced(toSet, false);
cd350b53 818 buttontext = button.moodle.showLabel;
19194f82 819 button.form.elements['mform_showadvanced_last'].value = '0';
820 }
821 var formelements = button.form.elements;
4ea054ec 822 // Fixed MDL-10506
b51709c1 823 for (var i = 0; i < formelements.length; i++) {
824 if (formelements[i] && formelements[i].name && (formelements[i].name=='mform_showadvanced')) {
19194f82 825 formelements[i].value = buttontext;
826 }
827 }
828 //never submit the form if js is enabled.
829 return false;
830}
47aa42e2 831
54bb33eb 832function unmaskPassword(id) {
239ade45 833 var pw = document.getElementById(id);
54bb33eb 834 var chb = document.getElementById(id+'unmask');
239ade45 835
836 try {
837 // first try IE way - it can not set name attribute later
838 if (chb.checked) {
839 var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
840 } else {
841 var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
842 }
eba8cd63 843 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
239ade45 844 } catch (e) {
845 var newpw = document.createElement('input');
846 newpw.setAttribute('name', pw.name);
847 if (chb.checked) {
848 newpw.setAttribute('type', 'text');
849 } else {
850 newpw.setAttribute('type', 'password');
851 }
eba8cd63 852 newpw.setAttribute('class', pw.getAttribute('class'));
239ade45 853 }
854 newpw.id = pw.id;
855 newpw.size = pw.size;
856 newpw.onblur = pw.onblur;
857 newpw.onchange = pw.onchange;
858 newpw.value = pw.value;
859 pw.parentNode.replaceChild(newpw, pw);
860}
861
8e7cebb0 862/**
863 * Search a Moodle form to find all the fdate_time_selector and fdate_selector
864 * elements, and add date_selector_calendar instance to each.
865 */
866function init_date_selectors(firstdayofweek) {
8e7cebb0 867 var els = YAHOO.util.Dom.getElementsByClassName('fdate_time_selector', 'fieldset');
868 for (var i = 0; i < els.length; i++) {
869 new date_selector_calendar(els[i], firstdayofweek);
870 }
871 els = YAHOO.util.Dom.getElementsByClassName('fdate_selector', 'fieldset');
872 for (i = 0; i < els.length; i++) {
873 new date_selector_calendar(els[i], firstdayofweek);
874 }
875}
876
877/**
9bad31f5 878 * Constructor for a JavaScript object that connects to a fdate_time_selector
8e7cebb0 879 * or a fdate_selector in a Moodle form, and shows a popup calendar whenever
880 * that element has keyboard focus.
881 * @param el the fieldset class="fdate_time_selector" or "fdate_selector".
882 */
883function date_selector_calendar(el, firstdayofweek) {
884 // Ensure that the shared div and calendar exist.
885 if (!date_selector_calendar.panel) {
886 date_selector_calendar.panel = new YAHOO.widget.Panel('date_selector_calendar_panel',
b3a49376 887 {visible: false, draggable: false});
8e7cebb0 888 var div = document.createElement('div');
889 date_selector_calendar.panel.setBody(div);
890 date_selector_calendar.panel.render(document.body);
891
892 YAHOO.util.Event.addListener(document, 'click', date_selector_calendar.document_click);
893 date_selector_calendar.panel.showEvent.subscribe(function() {
894 date_selector_calendar.panel.fireEvent('changeContent');
895 });
b3a49376 896 date_selector_calendar.panel.hideEvent.subscribe(date_selector_calendar.release_current);
8e7cebb0 897
898 date_selector_calendar.calendar = new YAHOO.widget.Calendar(div,
899 {iframe: false, hide_blank_weeks: true, start_weekday: firstdayofweek});
900 date_selector_calendar.calendar.renderEvent.subscribe(function() {
901 date_selector_calendar.panel.fireEvent('changeContent');
bd55319b 902 date_selector_calendar.delayed_reposition();
8e7cebb0 903 });
8e7cebb0 904 }
905
906 this.fieldset = el;
907 var controls = el.getElementsByTagName('select');
908 for (var i = 0; i < controls.length; i++) {
8e7cebb0 909 if (/\[year\]$/.test(controls[i].name)) {
910 this.yearselect = controls[i];
9bad31f5 911 } else if (/\[month\]$/.test(controls[i].name)) {
8e7cebb0 912 this.monthselect = controls[i];
9bad31f5 913 } else if (/\[day\]$/.test(controls[i].name)) {
8e7cebb0 914 this.dayselect = controls[i];
9bad31f5 915 } else {
bd55319b 916 YAHOO.util.Event.addFocusListener(controls[i], date_selector_calendar.cancel_any_timeout, this);
9bad31f5 917 YAHOO.util.Event.addBlurListener(controls[i], this.blur_event, this);
8e7cebb0 918 }
919 }
920 if (!(this.yearselect && this.monthselect && this.dayselect)) {
921 throw 'Failed to initialise calendar.';
922 }
9bad31f5 923 YAHOO.util.Event.addFocusListener([this.yearselect, this.monthselect, this.dayselect], this.focus_event, this);
924 YAHOO.util.Event.addBlurListener([this.yearselect, this.monthselect, this.dayselect], this.blur_event, this);
8e7cebb0 925
926 this.enablecheckbox = el.getElementsByTagName('input')[0];
927 if (this.enablecheckbox) {
928 YAHOO.util.Event.addFocusListener(this.enablecheckbox, this.focus_event, this);
929 YAHOO.util.Event.addListener(this.enablecheckbox, 'change', this.focus_event, this);
930 YAHOO.util.Event.addBlurListener(this.enablecheckbox, this.blur_event, this);
931 }
932}
933
934/** The pop-up calendar that contains the calendar. */
935date_selector_calendar.panel = null;
936
937/** The shared YAHOO.widget.Calendar used by all date_selector_calendars. */
938date_selector_calendar.calendar = null;
939
940/** The date_selector_calendar that currently owns the shared stuff. */
941date_selector_calendar.currentowner = null;
942
943/** Used as a timeout when hiding the calendar on blur - so we don't hide the calendar
944 * if we are just jumping from on of our controls to another. */
945date_selector_calendar.hidetimeout = null;
946
bd55319b 947/** Timeout for repositioning after a delay after a change of months. */
948date_selector_calendar.repositiontimeout = null;
8e7cebb0 949
bd55319b 950/** Member variables. Pointers to various bits of the DOM. */
8e7cebb0 951date_selector_calendar.prototype.fieldset = null;
952date_selector_calendar.prototype.yearselect = null;
953date_selector_calendar.prototype.monthselect = null;
954date_selector_calendar.prototype.dayselect = null;
955date_selector_calendar.prototype.enablecheckbox = null;
956
bd55319b 957date_selector_calendar.cancel_any_timeout = function() {
8e7cebb0 958 if (date_selector_calendar.hidetimeout) {
959 clearTimeout(date_selector_calendar.hidetimeout);
960 date_selector_calendar.hidetimeout = null;
961 }
bd55319b 962 if (date_selector_calendar.repositiontimeout) {
963 clearTimeout(date_selector_calendar.repositiontimeout);
964 date_selector_calendar.repositiontimeout = null;
965 }
966}
967
968date_selector_calendar.delayed_reposition = function() {
969 if (date_selector_calendar.repositiontimeout) {
970 clearTimeout(date_selector_calendar.repositiontimeout);
971 date_selector_calendar.repositiontimeout = null;
972 }
b3a49376 973 date_selector_calendar.repositiontimeout = setTimeout(date_selector_calendar.fix_position, 500);
bd55319b 974}
975
976date_selector_calendar.fix_position = function() {
977 if (date_selector_calendar.currentowner) {
978 date_selector_calendar.panel.cfg.setProperty('context', [date_selector_calendar.currentowner.fieldset, 'bl', 'tl']);
979 }
8e7cebb0 980}
981
b3a49376 982date_selector_calendar.release_current = function() {
983 if (date_selector_calendar.currentowner) {
984 date_selector_calendar.currentowner.release_calendar();
985 }
986}
987
8e7cebb0 988date_selector_calendar.prototype.focus_event = function(e, me) {
bd55319b 989 date_selector_calendar.cancel_any_timeout();
8e7cebb0 990 if (me.enablecheckbox == null || me.enablecheckbox.checked) {
991 me.claim_calendar();
992 } else {
993 if (date_selector_calendar.currentowner) {
994 date_selector_calendar.currentowner.release_calendar();
995 }
996 }
997}
998
999date_selector_calendar.prototype.blur_event = function(e, me) {
b3a49376 1000 date_selector_calendar.hidetimeout = setTimeout(date_selector_calendar.release_current, 300);
8e7cebb0 1001}
1002
1003date_selector_calendar.prototype.handle_select_change = function(e, me) {
1004 me.set_date_from_selects();
1005}
1006
1007date_selector_calendar.document_click = function(event) {
1008 if (date_selector_calendar.currentowner) {
1009 var currentcontainer = date_selector_calendar.currentowner.fieldset;
1010 var eventarget = YAHOO.util.Event.getTarget(event);
b3a49376 1011 if (YAHOO.util.Dom.isAncestor(currentcontainer, eventarget)) {
1012 setTimeout(function() {date_selector_calendar.cancel_any_timeout()}, 100);
1013 } else {
8e7cebb0 1014 date_selector_calendar.currentowner.release_calendar();
1015 }
1016 }
f8065dd2 1017}
8e7cebb0 1018
1019date_selector_calendar.prototype.claim_calendar = function() {
bd55319b 1020 date_selector_calendar.cancel_any_timeout();
8e7cebb0 1021 if (date_selector_calendar.currentowner == this) {
1022 return;
1023 }
1024 if (date_selector_calendar.currentowner) {
1025 date_selector_calendar.currentowner.release_calendar();
1026 }
1027
bd55319b 1028 if (date_selector_calendar.currentowner != this) {
1029 this.connect_handlers();
1030 }
1031 date_selector_calendar.currentowner = this;
1032
8e7cebb0 1033 date_selector_calendar.calendar.cfg.setProperty('mindate', new Date(this.yearselect.options[0].value, 0, 1));
1034 date_selector_calendar.calendar.cfg.setProperty('maxdate', new Date(this.yearselect.options[this.yearselect.options.length - 1].value, 11, 31));
bd55319b 1035 this.fieldset.insertBefore(date_selector_calendar.panel.element, this.yearselect.nextSibling);
8e7cebb0 1036 this.set_date_from_selects();
1037 date_selector_calendar.panel.show();
1038 var me = this;
bd55319b 1039 setTimeout(function() {date_selector_calendar.cancel_any_timeout()}, 100);
8e7cebb0 1040}
1041
1042date_selector_calendar.prototype.set_date_from_selects = function() {
1043 var year = parseInt(this.yearselect.value);
1044 var month = parseInt(this.monthselect.value) - 1;
1045 var day = parseInt(this.dayselect.value);
1046 date_selector_calendar.calendar.select(new Date(year, month, day));
1047 date_selector_calendar.calendar.setMonth(month);
1048 date_selector_calendar.calendar.setYear(year);
bd55319b 1049 date_selector_calendar.calendar.render();
1050 date_selector_calendar.fix_position();
8e7cebb0 1051}
1052
1053date_selector_calendar.prototype.set_selects_from_date = function(eventtype, args) {
1054 var date = args[0][0];
1055 var newyear = date[0];
1056 var newindex = newyear - this.yearselect.options[0].value;
1057 this.yearselect.selectedIndex = newindex;
1058 this.monthselect.selectedIndex = date[1] - this.monthselect.options[0].value;
1059 this.dayselect.selectedIndex = date[2] - this.dayselect.options[0].value;
1060}
1061
1062date_selector_calendar.prototype.connect_handlers = function() {
1063 YAHOO.util.Event.addListener([this.yearselect, this.monthselect, this.dayselect], 'change', this.handle_select_change, this);
1064 date_selector_calendar.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true);
1065}
1066
1067date_selector_calendar.prototype.release_calendar = function() {
1068 date_selector_calendar.panel.hide();
1069 date_selector_calendar.currentowner = null;
1070 YAHOO.util.Event.removeListener([this.yearselect, this.monthselect, this.dayselect], this.handle_select_change);
1071 date_selector_calendar.calendar.selectEvent.unsubscribe(this.set_selects_from_date, this);
1072}
1073
47aa42e2 1074function filterByParent(elCollection, parentFinder) {
1075 var filteredCollection = [];
45caa363 1076 for (var i = 0; i < elCollection.length; ++i) {
47aa42e2 1077 var findParent = parentFinder(elCollection[i]);
45caa363 1078 if (findParent.nodeName.toUpperCase != 'BODY') {
47aa42e2 1079 filteredCollection.push(elCollection[i]);
1080 }
1081 }
1082 return filteredCollection;
1083}
1084
7979105c 1085/*
1086 All this is here just so that IE gets to handle oversized blocks
1087 in a visually pleasing manner. It does a browser detect. So sue me.
1088*/
1089
1090function fix_column_widths() {
1091 var agt = navigator.userAgent.toLowerCase();
1092 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1093 fix_column_width('left-column');
1094 fix_column_width('right-column');
1095 }
1096}
1097
1098function fix_column_width(colName) {
1099 if(column = document.getElementById(colName)) {
1100 if(!column.offsetWidth) {
1101 setTimeout("fix_column_width('" + colName + "')", 20);
1102 return;
1103 }
1104
1105 var width = 0;
1106 var nodes = column.childNodes;
1107
1108 for(i = 0; i < nodes.length; ++i) {
6605ff8c 1109 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 1110 if(width < nodes[i].offsetWidth) {
1111 width = nodes[i].offsetWidth;
1112 }
1113 }
1114 }
1115
1116 for(i = 0; i < nodes.length; ++i) {
6605ff8c 1117 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 1118 nodes[i].style.width = width + 'px';
1119 }
1120 }
1121 }
1122}
d13c5938 1123
1124
1125/*
9f439b17 1126 Insert myValue at current cursor position
1127 */
d13c5938 1128function insertAtCursor(myField, myValue) {
9f439b17 1129 // IE support
1130 if (document.selection) {
1131 myField.focus();
1132 sel = document.selection.createRange();
1133 sel.text = myValue;
1134 }
1135 // Mozilla/Netscape support
1136 else if (myField.selectionStart || myField.selectionStart == '0') {
1137 var startPos = myField.selectionStart;
1138 var endPos = myField.selectionEnd;
1139 myField.value = myField.value.substring(0, startPos)
1140 + myValue + myField.value.substring(endPos, myField.value.length);
1141 } else {
1142 myField.value += myValue;
1143 }
d13c5938 1144}
7470d6de 1145
1146
1147/*
c0056e22 1148 Call instead of setting window.onload directly or setting body onload=.
1149 Adds your function to a chain of functions rather than overwriting anything
1150 that exists.
1151*/
7470d6de 1152function addonload(fn) {
1153 var oldhandler=window.onload;
1154 window.onload=function() {
1155 if(oldhandler) oldhandler();
c0056e22 1156 fn();
7470d6de 1157 }
1158}
7d2a0492 1159/**
1160 * Replacement for getElementsByClassName in browsers that aren't cool enough
6f5e0852 1161 *
7d2a0492 1162 * Relying on the built-in getElementsByClassName is far, far faster than
1163 * using YUI.
6f5e0852 1164 *
7d2a0492 1165 * Note: the third argument used to be an object with odd behaviour. It now
1166 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1167 * mimicked if you pass an object.
1168 *
1169 * @param {Node} oElm The top-level node for searching. To search a whole
1170 * document, use `document`.
1171 * @param {String} strTagName filter by tag names
1172 * @param {String} name same as HTML5 spec
1173 */
1174function getElementsByClassName(oElm, strTagName, name) {
1175 // for backwards compatibility
1176 if(typeof name == "object") {
1177 var names = new Array();
1178 for(var i=0; i<name.length; i++) names.push(names[i]);
1179 name = names.join('');
1180 }
1181 // use native implementation if possible
1182 if (oElm.getElementsByClassName && Array.filter) {
1183 if (strTagName == '*') {
1184 return oElm.getElementsByClassName(name);
1185 } else {
1186 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1187 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1188 });
1189 }
1190 }
1191 // native implementation unavailable, fall back to slow method
c849ed1e 1192 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1193 var arrReturnElements = new Array();
1194 var arrRegExpClassNames = new Array();
7d2a0492 1195 var names = name.split(' ');
1196 for(var i=0; i<names.length; i++) {
1197 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
c849ed1e 1198 }
1199 var oElement;
1200 var bMatchesAll;
b51709c1 1201 for(var j=0; j<arrElements.length; j++) {
c849ed1e 1202 oElement = arrElements[j];
1203 bMatchesAll = true;
b51709c1 1204 for(var k=0; k<arrRegExpClassNames.length; k++) {
1205 if(!arrRegExpClassNames[k].test(oElement.className)) {
c849ed1e 1206 bMatchesAll = false;
1207 break;
1208 }
1209 }
b51709c1 1210 if(bMatchesAll) {
c849ed1e 1211 arrReturnElements.push(oElement);
1212 }
1213 }
1214 return (arrReturnElements)
1215}
77241d9b 1216
f8065dd2 1217function openpopup(event, args) {
1218
2cf81209
SH
1219 if (event) {
1220 YAHOO.util.Event.preventDefault(event);
1221 }
36282d85 1222
2cf81209
SH
1223 var fullurl = args.url;
1224 if (!args.url.match(/https?:\/\//)) {
1225 fullurl = M.cfg.wwwroot + args.url;
77241d9b 1226 }
2cf81209
SH
1227 var windowobj = window.open(fullurl,args.name,args.options);
1228 if (!windowobj) {
1229 return true;
1230 }
1231 if (args.fullscreen) {
1232 windowobj.moveTo(0,0);
1233 windowobj.resizeTo(screen.availWidth,screen.availHeight);
1234 }
1235 windowobj.focus();
1236
1237 return false;
77241d9b 1238}
740939ec 1239
1240/* This is only used on a few help pages. */
1241emoticons_help = {
1242 inputarea: null,
1243
1244 init: function(formname, fieldname, listid) {
1245 if (!opener || !opener.document.forms[formname]) {
1246 return;
1247 }
1248 emoticons_help.inputarea = opener.document.forms[formname][fieldname];
1249 if (!emoticons_help.inputarea) {
1250 return;
1251 }
1252 var emoticons = document.getElementById(listid).getElementsByTagName('li');
1253 for (var i = 0; i < emoticons.length; i++) {
1254 var text = emoticons[i].getElementsByTagName('img')[0].alt;
1255 YAHOO.util.Event.addListener(emoticons[i], 'click', emoticons_help.inserttext, text);
1256 }
1257 },
1258
1259 inserttext: function(e, text) {
1260 text = ' ' + text + ' ';
1261 if (emoticons_help.inputarea.createTextRange && emoticons_help.inputarea.caretPos) {
1262 var caretPos = emoticons_help.inputarea.caretPos;
1263 caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
1264 } else {
1265 emoticons_help.inputarea.value += text;
1266 }
1267 emoticons_help.inputarea.focus();
1268 }
bd1884fe 1269}
1270
d4a03c00 1271/**
1272 * Oject to handle expanding and collapsing blocks when an icon is clicked on.
1273 * @constructor
1274 * @param String id the HTML id for the div.
1275 * @param String userpref the user preference that records the state of this block.
1276 * @param String visibletooltip tool tip/alt to show when the block is visible.
1277 * @param String hiddentooltip tool tip/alt to show when the block is hidden.
1278 * @param String visibleicon URL of the icon to show when the block is visible.
1279 * @param String hiddenicon URL of the icon to show when the block is hidden.
1280 */
1281function block_hider(id, userpref, visibletooltip, hiddentooltip, visibleicon, hiddenicon) {
1282 // Find the elemen that is the block.
1283 this.block = document.getElementById(id);
1284 var title_div = YAHOO.util.Dom.getElementsByClassName('title', 'div', this.block);
1285 if (!title_div || !title_div[0]) {
1286 return this;
1287 }
1288 title_div = title_div[0];
1289 this.ishidden = YAHOO.util.Dom.hasClass(this.block, 'hidden');
1290
1291 // Record the pref name
1292 this.userpref = userpref;
1293 this.visibletooltip = visibletooltip;
1294 this.hiddentooltip = hiddentooltip;
1295 this.visibleicon = visibleicon;
1296 this.hiddenicon = hiddenicon;
1297
1298 // Add the icon.
1299 this.icon = document.createElement('input');
1300 this.icon.type = 'image';
d4a03c00 1301 this.update_state();
46de77b6
SH
1302
1303 var blockactions = YAHOO.util.Dom.getElementsByClassName('block_action', 'div', title_div);
1304 if (blockactions && blockactions[0]) {
1305 blockactions[0].insertBefore(this.icon, blockactions[0].firstChild);
1306 }
d4a03c00 1307
1308 // Hook up the event handler.
1309 YAHOO.util.Event.addListener(this.icon, 'click', this.handle_click, null, this);
1310}
1311
1312/** Handle click on a block show/hide icon. */
1313block_hider.prototype.handle_click = function(e) {
1314 YAHOO.util.Event.stopEvent(e);
1315 this.ishidden = !this.ishidden;
1316 this.update_state();
41912a26 1317 M.util.set_user_preference(this.userpref, this.ishidden);
d4a03c00 1318}
1319
1320/** Set the state of the block show/hide icon to this.ishidden. */
1321block_hider.prototype.update_state = function () {
1322 if (this.ishidden) {
1323 YAHOO.util.Dom.addClass(this.block, 'hidden');
1324 this.icon.alt = this.hiddentooltip;
1325 this.icon.title = this.hiddentooltip;
1326 this.icon.src = this.hiddenicon;
1327 } else {
1328 YAHOO.util.Dom.removeClass(this.block, 'hidden');
1329 this.icon.alt = this.visibletooltip;
1330 this.icon.title = this.visibletooltip;
1331 this.icon.src = this.visibleicon;
1332 }
1333}
1334
b166403f 1335/** Close the current browser window. */
7a5c78e0 1336function close_window(e) {
1337 YAHOO.util.Event.preventDefault(e);
1338 self.close();
b166403f 1339}
1340
1341/**
1342 * Close the current browser window, forcing the window/tab that opened this
1343 * popup to reload itself. */
1344function close_window_reloading_opener() {
1345 if (window.opener) {
1346 window.opener.location.reload(1);
1347 close_window();
1348 // Intentionally, only try to close the window if there is some evidence we are in a popup.
1349 }
8e7cebb0 1350}
9f319372 1351
1352/**
1353 * Used in a couple of modules to hide navigation areas when using AJAX
1354 */
34a2777c 1355
2538037f 1356function show_item(itemid) {
1357 var item = document.getElementById(itemid);
1358 if (item) {
1359 item.style.display = "";
1360 }
1361}
1362
1363function destroy_item(itemid) {
1364 var item = document.getElementById(itemid);
1365 if (item) {
1366 item.parentNode.removeChild(item);
1367 }
1368}
34a2777c 1369/**
1370 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1371 * @param controlid the control id.
1372 */
1373function focuscontrol(controlid) {
1374 var control = document.getElementById(controlid);
1375 if (control) {
1376 control.focus();
1377 }
e29380f3 1378}
1379
e11a8328 1380/**
1381 * Transfers keyboard focus to an HTML element based on the old style style of focus
1382 * This function should be removed as soon as it is no longer used
1383 */
428acddb 1384function old_onload_focus(formid, controlname) {
5f8bce50 1385 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
428acddb 1386 document.forms[formid].elements[controlname].focus();
e11a8328 1387 }
1388}
1389
1bcb7eb5 1390function build_querystring(obj) {
1391 if (typeof obj !== 'object') {
1392 return null;
1393 }
1394 var list = [];
1395 for(var k in obj) {
1396 k = encodeURIComponent(k);
1397 var value = obj[k];
1398 if(obj[k] instanceof Array) {
1399 for(var i in value) {
1400 list.push(k+'[]='+encodeURIComponent(value[i]));
1401 }
1402 } else {
1403 list.push(k+'='+encodeURIComponent(value));
1404 }
1405 }
1406 return list.join('&');
1407}
d25e2ca3 1408
1409function stripHTML(str) {
1410 var re = /<\S[^><]*>/g;
1411 var ret = str.replace(re, "");
1412 return ret;
1413}
1414
fd4faf98 1415Number.prototype.fixed=function(n){
1416 with(Math)
1417 return round(Number(this)*pow(10,n))/pow(10,n);
1418}
1419function update_progress_bar (id, width, pt, msg, es){
1420 var percent = pt*100;
1421 var status = document.getElementById("status_"+id);
1422 var percent_indicator = document.getElementById("pt_"+id);
1423 var progress_bar = document.getElementById("progress_"+id);
1424 var time_es = document.getElementById("time_"+id);
1425 status.innerHTML = msg;
1426 percent_indicator.innerHTML = percent.fixed(2) + '%';
1427 if(percent == 100) {
1428 progress_bar.style.background = "green";
1429 time_es.style.display = "none";
1430 } else {
1431 progress_bar.style.background = "#FFCC66";
1432 if (es == Infinity){
1433 time_es.innerHTML = "Initializing...";
1434 }else {
1435 time_es.innerHTML = es.fixed(2)+" sec";
1436 time_es.style.display
1437 = "block";
1438 }
1439 }
1440 progress_bar.style.width = width + "px";
1441
1442}
bf6c37c7 1443
1444function frame_breakout(e, properties) {
1445 this.setAttribute('target', properties.framename);
41f23791 1446}
78946b9b 1447
73b62703
PS
1448
1449// ===== Deprecated core Javascript functions for Moodle ====
1450// DO NOT USE!!!!!!!
e50b4c89 1451// Do not put this stuff in separate file because it only adds extra load on servers!
73b62703 1452
2d65597b
PS
1453/**
1454 * Used in a couple of modules to hide navigation areas when using AJAX
1455 */
1456function hide_item(itemid) {
3b044ba3 1457 // use class='hiddenifjs' instead
2d65597b
PS
1458 var item = document.getElementById(itemid);
1459 if (item) {
1460 item.style.display = "none";
1461 }
1462}
36282d85 1463
2cf81209
SH
1464M.util.help_icon = {
1465 Y : null,
36282d85 1466 instance : null,
2cf81209
SH
1467 add : function(Y, properties) {
1468 this.Y = Y;
61ddb953
RW
1469 properties.node = Y.one('#'+properties.id);
1470 if (properties.node) {
1471 properties.node.on('click', this.display, this, properties);
f855a5d2 1472 }
2cf81209
SH
1473 },
1474 display : function(event, args) {
1475 event.preventDefault();
1476 if (M.util.help_icon.instance === null) {
1477 var Y = M.util.help_icon.Y;
61ddb953 1478 Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
2cf81209 1479 var help_content_overlay = {
61ddb953 1480 helplink : null,
2cf81209
SH
1481 overlay : null,
1482 init : function() {
1483
61ddb953 1484 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
2cf81209
SH
1485 // Create an overlay from markup
1486 this.overlay = new Y.Overlay({
1487 headerContent: closebtn,
1488 bodyContent: '',
61ddb953
RW
1489 id: 'helppopupbox',
1490 width:'400px',
2cf81209 1491 visible : false,
61ddb953 1492 constrain : true
2cf81209
SH
1493 });
1494 this.overlay.render(Y.one(document.body));
1495
1496 closebtn.on('click', this.overlay.hide, this.overlay);
61ddb953 1497
2cf81209 1498 var boundingBox = this.overlay.get("boundingBox");
2cf81209
SH
1499
1500 // Hide the menu if the user clicks outside of its content
1501 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1502 var oTarget = event.target;
1503 var menuButton = Y.one("#"+args.id);
61ddb953 1504
2cf81209
SH
1505 if (!oTarget.compareTo(menuButton) &&
1506 !menuButton.contains(oTarget) &&
1507 !oTarget.compareTo(boundingBox) &&
1508 !boundingBox.contains(oTarget)) {
1509 this.overlay.hide();
1510 }
1511 }, this);
61ddb953
RW
1512
1513 Y.on("key", this.close, closebtn , "down:13", this);
1514 closebtn.on('click', this.close, this);
1515 },
1516
1517 close : function(e) {
1518 e.preventDefault();
1519 this.helplink.focus();
1520 this.overlay.hide();
2cf81209
SH
1521 },
1522
1523 display : function(event, args) {
61ddb953 1524 this.helplink = args.node;
2cf81209 1525 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
61ddb953 1526 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
2cf81209
SH
1527
1528 var fullurl = args.url;
1529 if (!args.url.match(/https?:\/\//)) {
1530 fullurl = M.cfg.wwwroot + args.url;
36282d85 1531 }
36282d85 1532
2cf81209
SH
1533 var ajaxurl = fullurl + '&ajax=1';
1534
1535 var cfg = {
1536 method: 'get',
1537 context : this,
1538 on: {
1539 success: function(id, o, node) {
1540 this.display_callback(o.responseText);
1541 },
1542 failure: function(id, o, node) {
1543 var debuginfo = o.statusText;
1544 if (M.cfg.developerdebug) {
1545 o.statusText += ' (' + ajaxurl + ')';
1546 }
1547 this.display_callback('bodyContent',debuginfo);
1548 }
1549 }
1550 };
36282d85 1551
2cf81209
SH
1552 Y.io(ajaxurl, cfg);
1553 this.overlay.show();
61ddb953
RW
1554
1555 Y.one('#closehelpbox').focus();
2cf81209 1556 },
36282d85 1557
2cf81209 1558 display_callback : function(content) {
61ddb953 1559 this.overlay.set('bodyContent', content);
2cf81209
SH
1560 },
1561
1562 hideContent : function() {
1563 help = this;
1564 help.overlay.hide();
36282d85 1565 }
2cf81209
SH
1566 }
1567 help_content_overlay.init();
1568 M.util.help_icon.instance = help_content_overlay;
1569 M.util.help_icon.instance.display(event, args);
1570 });
1571 } else {
1572 M.util.help_icon.instance.display(event, args);
1573 }
1574 },
1575 init : function(Y) {
61ddb953 1576 this.Y = Y;
36282d85
RW
1577 }
1578}
d2dbd0c0
SH
1579
1580/**
1581 * Custom menu namespace
1582 */
1583M.core_custom_menu = {
1584 /**
1585 * This method is used to initialise a custom menu given the id that belongs
1586 * to the custom menu's root node.
1587 *
1588 * @param {YUI} Y
1589 * @param {string} nodeid
1590 */
1591 init : function(Y, nodeid) {
242b78f7
SH
1592 var node = Y.one('#'+nodeid);
1593 if (node) {
1594 Y.use('node-menunav', function(Y) {
1595 // Get the node
1596 // Remove the javascript-disabled class.... obviously javascript is enabled.
1597 node.removeClass('javascript-disabled');
1598 // Initialise the menunav plugin
1599 node.plug(Y.Plugin.NodeMenuNav);
1600 });
1601 }
d2dbd0c0 1602 }
572dd8ec
SH
1603}
1604
1605/**
1606 * Used to store form manipulation methods and enhancments
1607 */
1608M.form = M.form || {};
1609
1610/**
1611 * Converts a nbsp indented select box into a multi drop down custom control much
1612 * like the custom menu. It also selectable categories on or off.
1613 *
1614 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1615 *
1616 * @param {YUI} Y
1617 * @param {string} id
1618 * @param {Array} options
1619 */
1620M.form.init_smartselect = function(Y, id, options) {
882e66fe
SH
1621 if (!id.match(/^id_/)) {
1622 id = 'id_'+id;
1623 }
1624 var select = Y.one('select#'+id);
572dd8ec
SH
1625 if (!select) {
1626 return false;
1627 }
1628 Y.use('event-delegate',function(){
1629 var smartselect = {
1630 id : id,
1631 structure : [],
1632 options : [],
1633 submenucount : 0,
1634 currentvalue : null,
1635 currenttext : null,
1636 shownevent : null,
1637 cfg : {
1638 selectablecategories : true,
1639 mode : null
1640 },
1641 nodes : {
1642 select : null,
1643 loading : null,
1644 menu : null
1645 },
1646 init : function(Y, id, args, nodes) {
1647 if (typeof(args)=='object') {
1648 for (var i in this.cfg) {
1649 if (args[i] || args[i]===false) {
1650 this.cfg[i] = args[i];
1651 }
1652 }
1653 }
1654
1655 // Display a loading message first up
1656 this.nodes.select = nodes.select;
1657
1658 this.currentvalue = this.nodes.select.get('selectedIndex');
1659 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1660
1661 var options = Array();
1662 options[''] = {text:this.currenttext,value:'',depth:0,children:[]}
1663 this.nodes.select.all('option').each(function(option, index) {
1664 var rawtext = option.get('innerHTML');
1665 var text = rawtext.replace(/^(&nbsp;)*/, '');
1666 if (rawtext === text) {
1667 text = rawtext.replace(/^(\s)*/, '');
1668 var depth = (rawtext.length - text.length ) + 1;
1669 } else {
1670 var depth = ((rawtext.length - text.length )/12)+1;
1671 }
1672 option.set('innerHTML', text);
1673 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1674 }, this);
1675
1676 this.structure = [];
1677 var structcount = 0;
1678 for (var i in options) {
1679 var o = options[i];
1680 if (o.depth == 0) {
1681 this.structure.push(o);
1682 structcount++;
1683 } else {
1684 var d = o.depth;
1685 var current = this.structure[structcount-1];
1686 for (var j = 0; j < o.depth-1;j++) {
1687 if (current && current.children) {
1688 current = current.children[current.children.length-1];
1689 }
1690 }
1691 if (current && current.children) {
1692 current.children.push(o);
1693 }
1694 }
1695 }
1696
1697 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1698 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1699 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1700 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1701
1702 if (this.cfg.mode == null) {
1703 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1704 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1705 this.cfg.mode = 'compact';
1706 } else {
1707 this.cfg.mode = 'spanning';
1708 }
1709 }
1710
1711 if (this.cfg.mode == 'compact') {
1712 this.nodes.menu.addClass('compactmenu');
1713 } else {
1714 this.nodes.menu.addClass('spanningmenu');
1715 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1716 }
1717
1718 Y.one(document.body).append(this.nodes.menu);
1719 var pos = this.nodes.select.getXY();
1720 pos[0] += 1;
1721 this.nodes.menu.setXY(pos);
1722 this.nodes.menu.on('click', this.handle_click, this);
32690ac2
SH
1723
1724 Y.one(window).on('resize', function(){
1725 var pos = this.nodes.select.getXY();
1726 pos[0] += 1;
1727 this.nodes.menu.setXY(pos);
1728 }, this);
572dd8ec
SH
1729 },
1730 generate_menu_content : function() {
1731 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1732 content += this.generate_submenu_content(this.structure[0], true);
1733 content += '</ul></div>';
1734 return content;
1735 },
1736 generate_submenu_content : function(item, rootelement) {
1737 this.submenucount++;
1738 var content = '';
1739 if (item.children.length > 0) {
1740 if (rootelement) {
1741 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1742 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1743 content += '<div class="smartselect_menu_content">';
1744 } else {
1745 content += '<li class="smartselect_submenuitem">';
1746 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1747 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1748 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1749 content += '<div class="smartselect_submenu_content">';
1750 }
1751 content += '<ul>';
1752 for (var i in item.children) {
1753 content += this.generate_submenu_content(item.children[i],false);
1754 }
1755 content += '</ul>';
1756 content += '</div>';
1757 content += '</div>';
1758 if (rootelement) {
1759 } else {
1760 content += '</li>';
1761 }
1762 } else {
1763 content += '<li class="smartselect_menuitem">';
1764 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1765 content += '</li>';
1766 }
1767 return content;
1768 },
1769 select : function(e) {
1770 var t = e.target;
1771 e.halt();
1772 this.currenttext = t.get('innerHTML');
1773 this.currentvalue = t.getAttribute('value');
1774 this.nodes.select.set('selectedIndex', this.currentvalue);
1775 this.hide_menu();
1776 },
1777 handle_click : function(e) {
1778 var target = e.target;
1779 if (target.hasClass('smartselect_mask')) {
1780 this.show_menu(e);
1781 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1782 this.select(e);
1783 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1784 this.show_sub_menu(e);
1785 }
1786 },
1787 show_menu : function(e) {
1788 e.halt();
1789 var menu = e.target.ancestor().one('.smartselect_menu');
1790 menu.addClass('visible');
1791 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1792 },
1793 show_sub_menu : function(e) {
1794 e.halt();
1795 var target = e.target;
1796 if (!target.hasClass('smartselect_submenuitem')) {
1797 target = target.ancestor('.smartselect_submenuitem');
1798 }
1799 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1800 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1801 return;
1802 }
1803 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1804 target.one('.smartselect_submenu').addClass('visible');
1805 },
1806 hide_menu : function() {
1807 this.nodes.menu.all('.visible').removeClass('visible');
1808 if (this.shownevent) {
1809 this.shownevent.detach();
1810 }
1811 }
1812 }
1813 smartselect.init(Y, id, options, {select:select});
1814 });
d2dbd0c0 1815}