MDL-30660 javascript-static: M.util.show_confirm_dialog fixes
[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
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;
667c0ae1 56};
b4430cd6 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
712b55e1
SH
261 var targetancestor = null,
262 targetform = null;
263
264 if (target.test('a')) {
20fb563e 265 window.location = target.get('href');
e16c0b9d 266
712b55e1
SH
267 } else if ((targetancestor = target.ancestor('a')) !== null) {
268 window.location = targetancestor.get('href');
e16c0b9d 269
712b55e1
SH
270 } else if (target.test('input')) {
271 targetform = target.ancestor('form');
e16c0b9d
TH
272 if (!targetform) {
273 return;
274 }
275 if (target.get('name') && target.get('value')) {
276 targetform.append('<input type="hidden" name="' + target.get('name') +
277 '" value="' + target.get('value') + '">');
20fb563e 278 }
e16c0b9d
TH
279 targetform.submit();
280
281 } else if (target.get('tagName').toLowerCase() == 'form') {
282 // We cannot use target.test('form') on the previous line because of
283 // http://yuilibrary.com/projects/yui3/ticket/2531561
284 target.submit();
285
20fb563e
PS
286 } else if (M.cfg.developerdebug) {
287 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A or INPUT");
288 }
289 };
290
8f78cd5a
TH
291 if (!args.cancellabel) {
292 args.cancellabel = M.str.moodle.cancel;
293 }
294 if (!args.continuelabel) {
295 args.continuelabel = M.str.moodle.yes;
296 }
297
298 var buttons = [
299 {text: args.cancellabel, handler: handle_cancel, isDefault: true},
300 {text: args.continuelabel, handler: handle_yes}
301 ];
20fb563e
PS
302
303 simpledialog.cfg.queueProperty('buttons', buttons);
304
305 simpledialog.render(document.body);
306 simpledialog.show();
307 });
667c0ae1 308};
20fb563e 309
dd9b1882
PS
310/** Useful for full embedding of various stuff */
311M.util.init_maximised_embed = function(Y, id) {
312 var obj = Y.one('#'+id);
313 if (!obj) {
314 return;
315 }
316
dd9b1882
PS
317 var get_htmlelement_size = function(el, prop) {
318 if (Y.Lang.isString(el)) {
319 el = Y.one('#' + el);
320 }
321 var val = el.getStyle(prop);
322 if (val == 'auto') {
323 val = el.getComputedStyle(prop);
324 }
325 return parseInt(val);
326 };
327
328 var resize_object = function() {
329 obj.setStyle('width', '0px');
330 obj.setStyle('height', '0px');
fcd2cbaf 331 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
dd9b1882 332
fcd2cbaf 333 if (newwidth > 500) {
dd9b1882
PS
334 obj.setStyle('width', newwidth + 'px');
335 } else {
fcd2cbaf 336 obj.setStyle('width', '500px');
dd9b1882 337 }
f6fa64f0
PS
338
339 var headerheight = get_htmlelement_size('page-header', 'height');
340 var footerheight = get_htmlelement_size('page-footer', 'height');
fcd2cbaf 341 var newheight = parseInt(YAHOO.util.Dom.getViewportHeight()) - footerheight - headerheight - 100;
f6fa64f0
PS
342 if (newheight < 400) {
343 newheight = 400;
dd9b1882 344 }
f6fa64f0 345 obj.setStyle('height', newheight+'px');
dd9b1882
PS
346 };
347
348 resize_object();
349 // fix layout if window resized too
350 window.onresize = function() {
351 resize_object();
352 };
353};
354
a9967cf5
PS
355/**
356 * Attach handler to single_select
357 */
edc28287 358M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
c7bbb86f
SH
359 Y.use('event-key', function() {
360 var select = Y.one('#'+selectid);
361 if (select) {
362 // Try to get the form by id
363 var form = Y.one('#'+formid) || (function(){
364 // Hmmm the form's id may have been overriden by an internal input
365 // with the name id which will KILL IE.
366 // We need to manually iterate at this point because if the case
367 // above is true YUI's ancestor method will also kill IE!
368 var form = select;
369 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
370 form = form.ancestor();
371 }
372 return form;
373 })();
374 // Make sure we have the form
375 if (form) {
376 // Create a function to handle our change event
8d0845d3
AD
377 var processchange = function(e, paramobject) {
378 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
379 //prevent event bubbling and detach handlers to prevent multiple submissions caused by double clicking
380 e.halt();
381 paramobject.eventkeypress.detach();
382 paramobject.eventblur.detach();
383 paramobject.eventchangeorblur.detach();
384
c7bbb86f
SH
385 this.submit();
386 }
667c0ae1 387 };
c7bbb86f
SH
388 // Attach the change event to the keypress, blur, and click actions.
389 // We don't use the change event because IE fires it on every arrow up/down
390 // event.... usability
8d0845d3
AD
391 var paramobject = new Object();
392 paramobject.lastindex = select.get('selectedIndex');
393 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
394 paramobject.eventblur = select.on('blur', processchange, form, paramobject);
1ede3cb3 395 //little hack for chrome that need onChange event instead of onClick - see MDL-23224
396 if (Y.UA.webkit) {
8d0845d3 397 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
1ede3cb3 398 } else {
8d0845d3 399 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
1ede3cb3 400 }
c7bbb86f 401 }
4aea3cc7 402 }
4aea3cc7 403 });
a9967cf5 404};
dd9b1882 405
4d10e579
PS
406/**
407 * Attach handler to url_select
408 */
409M.util.init_url_select = function(Y, formid, selectid, nothing) {
4aea3cc7
PS
410 YUI(M.yui.loader).use('node', function(Y) {
411 Y.on('change', function() {
c7bbb86f
SH
412 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
413 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
414 }
4aea3cc7
PS
415 },
416 '#'+selectid);
417 });
418};
419
420/**
421 * Breaks out all links to the top frame - used in frametop page layout.
422 */
423M.util.init_frametop = function(Y) {
424 Y.all('a').each(function(node) {
425 node.set('target', '_top');
426 });
427 Y.all('form').each(function(node) {
428 node.set('target', '_top');
429 });
4d10e579
PS
430};
431
24e27ac0
SH
432/**
433 * Finds all nodes that match the given CSS selector and attaches events to them
434 * so that they toggle a given classname when clicked.
435 *
436 * @param {YUI} Y
437 * @param {string} id An id containing elements to target
438 * @param {string} cssselector A selector to use to find targets
439 * @param {string} toggleclassname A classname to toggle
440 */
c8ffba95
SH
441M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
442
443 if (togglecssselector == '') {
444 togglecssselector = cssselector;
445 }
446
24e27ac0 447 var node = Y.one('#'+id);
c8ffba95
SH
448 node.all(cssselector).each(function(n){
449 n.on('click', function(e){
24e27ac0 450 e.stopPropagation();
c8ffba95
SH
451 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
452 if (this.test(togglecssselector)) {
453 this.toggleClass(toggleclassname);
454 } else {
455 this.ancestor(togglecssselector).toggleClass(toggleclassname);
456 }
24e27ac0 457 }
c8ffba95 458 }, n);
24e27ac0
SH
459 });
460 // Attach this click event to the node rather than all selectors... will be much better
461 // for performance
462 node.on('click', function(e){
463 if (e.target.hasClass('addtoall')) {
c8ffba95 464 this.all(togglecssselector).addClass(toggleclassname);
24e27ac0 465 } else if (e.target.hasClass('removefromall')) {
c8ffba95 466 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
24e27ac0
SH
467 }
468 }, node);
667c0ae1 469};
24e27ac0 470
233b6ff9
SH
471/**
472 * Initialises a colour picker
473 *
474 * Designed to be used with admin_setting_configcolourpicker although could be used
475 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
476 * above or below the input (must have the same parent) and then call this with the
477 * id.
478 *
479 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
480 * contrib/blocks. For better docs refer to that.
481 *
482 * @param {YUI} Y
483 * @param {int} id
484 * @param {object} previewconf
485 */
486M.util.init_colour_picker = function(Y, id, previewconf) {
487 /**
488 * We need node and event-mouseenter
489 */
490 Y.use('node', 'event-mouseenter', function(){
491 /**
492 * The colour picker object
493 */
494 var colourpicker = {
495 box : null,
496 input : null,
497 image : null,
498 preview : null,
499 current : null,
500 eventClick : null,
501 eventMouseEnter : null,
502 eventMouseLeave : null,
503 eventMouseMove : null,
504 width : 300,
505 height : 100,
506 factor : 5,
507 /**
508 * Initalises the colour picker by putting everything together and wiring the events
509 */
510 init : function() {
511 this.input = Y.one('#'+id);
512 this.box = this.input.ancestor().one('.admin_colourpicker');
513 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
514 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
515 this.preview = Y.Node.create('<div class="previewcolour"></div>');
516 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
517 this.current = Y.Node.create('<div class="currentcolour"></div>');
518 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
519 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
520
521 if (typeof(previewconf) === 'object' && previewconf !== null) {
522 Y.one('#'+id+'_preview').on('click', function(e){
3c3a3c3c 523 if (Y.Lang.isString(previewconf.selector)) {
bb9e9362
SH
524 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
525 } else {
526 for (var i in previewconf.selector) {
527 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
528 }
529 }
233b6ff9
SH
530 }, this);
531 }
532
533 this.eventClick = this.image.on('click', this.pickColour, this);
534 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
535 },
536 /**
537 * Starts to follow the mouse once it enter the image
538 */
539 startFollow : function(e) {
540 this.eventMouseEnter.detach();
541 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
542 this.eventMouseMove = this.image.on('mousemove', function(e){
543 this.preview.setStyle('backgroundColor', this.determineColour(e));
544 }, this);
545 },
546 /**
547 * Stops following the mouse
548 */
549 endFollow : function(e) {
550 this.eventMouseMove.detach();
551 this.eventMouseLeave.detach();
552 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
553 },
554 /**
555 * Picks the colour the was clicked on
556 */
557 pickColour : function(e) {
558 var colour = this.determineColour(e);
559 this.input.set('value', colour);
560 this.current.setStyle('backgroundColor', colour);
561 },
562 /**
563 * Calculates the colour fromthe given co-ordinates
564 */
565 determineColour : function(e) {
566 var eventx = Math.floor(e.pageX-e.target.getX());
567 var eventy = Math.floor(e.pageY-e.target.getY());
568
569 var imagewidth = this.width;
570 var imageheight = this.height;
571 var factor = this.factor;
572 var colour = [255,0,0];
573
574 var matrices = [
575 [ 0, 1, 0],
576 [ -1, 0, 0],
577 [ 0, 0, 1],
578 [ 0, -1, 0],
579 [ 1, 0, 0],
580 [ 0, 0, -1]
581 ];
582
583 var matrixcount = matrices.length;
584 var limit = Math.round(imagewidth/matrixcount);
585 var heightbreak = Math.round(imageheight/2);
586
587 for (var x = 0; x < imagewidth; x++) {
588 var divisor = Math.floor(x / limit);
589 var matrix = matrices[divisor];
590
591 colour[0] += matrix[0]*factor;
592 colour[1] += matrix[1]*factor;
593 colour[2] += matrix[2]*factor;
594
595 if (eventx==x) {
596 break;
597 }
598 }
599
600 var pixel = [colour[0], colour[1], colour[2]];
601 if (eventy < heightbreak) {
602 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
603 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
604 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
605 } else if (eventy > heightbreak) {
606 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
607 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
608 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
609 }
610
611 return this.convert_rgb_to_hex(pixel);
612 },
613 /**
614 * Converts an RGB value to Hex
615 */
616 convert_rgb_to_hex : function(rgb) {
617 var hex = '#';
618 var hexchars = "0123456789ABCDEF";
619 for (var i=0; i<3; i++) {
620 var number = Math.abs(rgb[i]);
621 if (number == 0 || isNaN(number)) {
622 hex += '00';
623 } else {
624 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
625 }
626 }
627 return hex;
628 }
667c0ae1 629 };
233b6ff9
SH
630 /**
631 * Initialise the colour picker :) Hoorah
632 */
633 colourpicker.init();
634 });
667c0ae1 635};
233b6ff9 636
cbb54cce
SH
637M.util.init_block_hider = function(Y, config) {
638 Y.use('base', 'node', function(Y) {
639 M.util.block_hider = M.util.block_hider || (function(){
640 var blockhider = function() {
641 blockhider.superclass.constructor.apply(this, arguments);
667c0ae1 642 };
cbb54cce
SH
643 blockhider.prototype = {
644 initializer : function(config) {
667c0ae1 645 this.set('block', '#'+this.get('id'));
cbb54cce
SH
646 var b = this.get('block'),
647 t = b.one('.title'),
648 a = null;
649 if (t && (a = t.one('.block_action'))) {
7cf83f47 650 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
cbb54cce 651 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
7cf83f47
JB
652 hide.on('keypress', this.updateStateKey, this, true);
653 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
cbb54cce 654 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
7cf83f47 655 show.on('keypress', this.updateStateKey, this, false);
cbb54cce
SH
656 a.insert(show, 0).insert(hide, 0);
657 }
658 },
659 updateState : function(e, hide) {
660 M.util.set_user_preference(this.get('preference'), hide);
661 if (hide) {
662 this.get('block').addClass('hidden');
663 } else {
664 this.get('block').removeClass('hidden');
665 }
7cf83f47
JB
666 },
667 updateStateKey : function(e, hide) {
668 if (e.keyCode == 13) { //allow hide/show via enter key
669 this.updateState(this, hide);
670 }
cbb54cce 671 }
667c0ae1 672 };
cbb54cce
SH
673 Y.extend(blockhider, Y.Base, blockhider.prototype, {
674 NAME : 'blockhider',
675 ATTRS : {
676 id : {},
677 preference : {},
678 iconVisible : {
679 value : M.util.image_url('t/switch_minus', 'moodle')
680 },
681 iconHidden : {
682 value : M.util.image_url('t/switch_plus', 'moodle')
683 },
684 block : {
685 setter : function(node) {
686 return Y.one(node);
687 }
688 }
689 }
690 });
691 return blockhider;
692 })();
693 new M.util.block_hider(config);
694 });
667c0ae1 695};
cbb54cce 696
b532be2d
DM
697/**
698 * Returns a string registered in advance for usage in JavaScript
699 *
700 * If you do not pass the third parameter, the function will just return
701 * the corresponding value from the M.str object. If the third parameter is
702 * provided, the function performs {$a} placeholder substitution in the
703 * same way as PHP get_string() in Moodle does.
704 *
705 * @param {String} identifier string identifier
706 * @param {String} component the component providing the string
707 * @param {Object|String} a optional variable to populate placeholder with
708 */
709M.util.get_string = function(identifier, component, a) {
710 var stringvalue;
711
712 if (M.cfg.developerdebug) {
713 // creating new instance if YUI is not optimal but it seems to be better way then
714 // require the instance via the function API - note that it is used in rare cases
715 // for debugging only anyway
716 var Y = new YUI({ debug : true });
717 }
718
719 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
720 stringvalue = '[[' + identifier + ',' + component + ']]';
721 if (M.cfg.developerdebug) {
722 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
723 }
724 return stringvalue;
725 }
726
727 stringvalue = M.str[component][identifier];
728
729 if (typeof a == 'undefined') {
730 // no placeholder substitution requested
731 return stringvalue;
732 }
733
734 if (typeof a == 'number' || typeof a == 'string') {
735 // replace all occurrences of {$a} with the placeholder value
736 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
737 return stringvalue;
738 }
739
740 if (typeof a == 'object') {
741 // replace {$a->key} placeholders
742 for (var key in a) {
743 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
744 if (M.cfg.developerdebug) {
745 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
746 }
747 continue;
748 }
749 var search = '{$a->' + key + '}';
750 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
751 search = new RegExp(search, 'g');
752 stringvalue = stringvalue.replace(search, a[key]);
753 }
754 return stringvalue;
755 }
756
757 if (M.cfg.developerdebug) {
758 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
759 }
760 return stringvalue;
761};
762
2d16dad0
PS
763/**
764 * Set focus on username or password field of the login form
765 */
766M.util.focus_login_form = function(Y) {
767 var username = Y.one('#username');
768 var password = Y.one('#password');
769
770 if (username == null || password == null) {
771 // something is wrong here
772 return;
773 }
774
775 var curElement = document.activeElement
776 if (curElement == 'undefined') {
777 // legacy browser - skip refocus protection
778 } else if (curElement.tagName == 'INPUT') {
779 // user was probably faster to focus something, do not mess with focus
780 return;
781 }
782
783 if (username.get('value') == '') {
784 username.focus();
785 } else {
786 password.focus();
787 }
788}
789
790
38224dcb
PS
791//=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
792
63d28811 793function checkall() {
214f5850 794 var inputs = document.getElementsByTagName('input');
795 for (var i = 0; i < inputs.length; i++) {
796 if (inputs[i].type == 'checkbox') {
797 inputs[i].checked = true;
798 }
d2ce367f 799 }
63d28811 800}
801
03f9425f 802function checknone() {
214f5850 803 var inputs = document.getElementsByTagName('input');
804 for (var i = 0; i < inputs.length; i++) {
805 if (inputs[i].type == 'checkbox') {
806 inputs[i].checked = false;
807 }
d2ce367f 808 }
03f9425f 809}
810
45caa363 811/**
812 * Either check, or uncheck, all checkboxes inside the element with id is
813 * @param id the id of the container
814 * @param checked the new state, either '' or 'checked'.
815 */
816function select_all_in_element_with_id(id, checked) {
817 var container = document.getElementById(id);
818 if (!container) {
819 return;
820 }
821 var inputs = container.getElementsByTagName('input');
822 for (var i = 0; i < inputs.length; ++i) {
823 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
824 inputs[i].checked = checked;
825 }
826 }
827}
828
8ceb09e0 829function select_all_in(elTagName, elClass, elId) {
d2ce367f 830 var inputs = document.getElementsByTagName('input');
8ceb09e0 831 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 832 for(var i = 0; i < inputs.length; ++i) {
bee40515 833 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 834 inputs[i].checked = 'checked';
835 }
836 }
837}
838
8ceb09e0 839function deselect_all_in(elTagName, elClass, elId) {
363cb62c 840 var inputs = document.getElementsByTagName('INPUT');
8ceb09e0 841 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 842 for(var i = 0; i < inputs.length; ++i) {
bee40515 843 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 844 inputs[i].checked = '';
845 }
846 }
847}
848
849function confirm_if(expr, message) {
850 if(!expr) {
851 return true;
852 }
853 return confirm(message);
854}
47aa42e2 855
856
857/*
858 findParentNode (start, elementName, elementClass, elementID)
50ef8eb9 859
47aa42e2 860 Travels up the DOM hierarchy to find a parent element with the
861 specified tag name, class, and id. All conditions must be met,
862 but any can be ommitted. Returns the BODY element if no match
863 found.
864*/
865function findParentNode(el, elName, elClass, elId) {
45caa363 866 while (el.nodeName.toUpperCase() != 'BODY') {
867 if ((!elName || el.nodeName.toUpperCase() == elName) &&
47aa42e2 868 (!elClass || el.className.indexOf(elClass) != -1) &&
45caa363 869 (!elId || el.id == elId)) {
47aa42e2 870 break;
871 }
872 el = el.parentNode;
873 }
874 return el;
875}
19194f82 876/*
877 findChildNode (start, elementName, elementClass, elementID)
878
879 Travels down the DOM hierarchy to find all child elements with the
880 specified tag name, class, and id. All conditions must be met,
881 but any can be ommitted.
a23f0aaf 882 Doesn't examine children of matches.
19194f82 883*/
884function findChildNodes(start, tagName, elementClass, elementID, elementName) {
885 var children = new Array();
83c9a8a2 886 for (var i = 0; i < start.childNodes.length; i++) {
a23f0aaf 887 var classfound = false;
83c9a8a2 888 var child = start.childNodes[i];
a23f0aaf 889 if((child.nodeType == 1) &&//element node type
b51709c1 890 (elementClass && (typeof(child.className)=='string'))) {
a23f0aaf 891 var childClasses = child.className.split(/\s+/);
b51709c1 892 for (var childClassIndex in childClasses) {
893 if (childClasses[childClassIndex]==elementClass) {
a23f0aaf 894 classfound = true;
895 break;
896 }
897 }
898 }
f07b9627 899 if(child.nodeType == 1) { //element node type
900 if ( (!tagName || child.nodeName == tagName) &&
901 (!elementClass || classfound)&&
902 (!elementID || child.id == elementID) &&
903 (!elementName || child.name == elementName))
904 {
905 children = children.concat(child);
906 } else {
907 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
908 }
19194f82 909 }
19194f82 910 }
911 return children;
912}
47aa42e2 913
54bb33eb 914function unmaskPassword(id) {
239ade45 915 var pw = document.getElementById(id);
54bb33eb 916 var chb = document.getElementById(id+'unmask');
239ade45 917
918 try {
919 // first try IE way - it can not set name attribute later
920 if (chb.checked) {
921 var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
922 } else {
923 var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
924 }
eba8cd63 925 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
239ade45 926 } catch (e) {
927 var newpw = document.createElement('input');
928 newpw.setAttribute('name', pw.name);
929 if (chb.checked) {
930 newpw.setAttribute('type', 'text');
931 } else {
932 newpw.setAttribute('type', 'password');
933 }
eba8cd63 934 newpw.setAttribute('class', pw.getAttribute('class'));
239ade45 935 }
936 newpw.id = pw.id;
937 newpw.size = pw.size;
938 newpw.onblur = pw.onblur;
939 newpw.onchange = pw.onchange;
940 newpw.value = pw.value;
941 pw.parentNode.replaceChild(newpw, pw);
942}
943
47aa42e2 944function filterByParent(elCollection, parentFinder) {
945 var filteredCollection = [];
45caa363 946 for (var i = 0; i < elCollection.length; ++i) {
47aa42e2 947 var findParent = parentFinder(elCollection[i]);
4d99adbb 948 if (findParent.nodeName.toUpperCase() != 'BODY') {
47aa42e2 949 filteredCollection.push(elCollection[i]);
950 }
951 }
952 return filteredCollection;
953}
954
7979105c 955/*
956 All this is here just so that IE gets to handle oversized blocks
957 in a visually pleasing manner. It does a browser detect. So sue me.
958*/
959
960function fix_column_widths() {
961 var agt = navigator.userAgent.toLowerCase();
962 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
963 fix_column_width('left-column');
964 fix_column_width('right-column');
965 }
966}
967
968function fix_column_width(colName) {
969 if(column = document.getElementById(colName)) {
970 if(!column.offsetWidth) {
971 setTimeout("fix_column_width('" + colName + "')", 20);
972 return;
973 }
974
975 var width = 0;
976 var nodes = column.childNodes;
977
978 for(i = 0; i < nodes.length; ++i) {
6605ff8c 979 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 980 if(width < nodes[i].offsetWidth) {
981 width = nodes[i].offsetWidth;
982 }
983 }
984 }
985
986 for(i = 0; i < nodes.length; ++i) {
6605ff8c 987 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 988 nodes[i].style.width = width + 'px';
989 }
990 }
991 }
992}
d13c5938 993
994
995/*
9f439b17 996 Insert myValue at current cursor position
997 */
d13c5938 998function insertAtCursor(myField, myValue) {
9f439b17 999 // IE support
1000 if (document.selection) {
1001 myField.focus();
1002 sel = document.selection.createRange();
1003 sel.text = myValue;
1004 }
1005 // Mozilla/Netscape support
1006 else if (myField.selectionStart || myField.selectionStart == '0') {
1007 var startPos = myField.selectionStart;
1008 var endPos = myField.selectionEnd;
1009 myField.value = myField.value.substring(0, startPos)
1010 + myValue + myField.value.substring(endPos, myField.value.length);
1011 } else {
1012 myField.value += myValue;
1013 }
d13c5938 1014}
7470d6de 1015
1016
1017/*
c0056e22 1018 Call instead of setting window.onload directly or setting body onload=.
1019 Adds your function to a chain of functions rather than overwriting anything
1020 that exists.
1021*/
7470d6de 1022function addonload(fn) {
1023 var oldhandler=window.onload;
1024 window.onload=function() {
1025 if(oldhandler) oldhandler();
c0056e22 1026 fn();
7470d6de 1027 }
1028}
7d2a0492 1029/**
1030 * Replacement for getElementsByClassName in browsers that aren't cool enough
6f5e0852 1031 *
7d2a0492 1032 * Relying on the built-in getElementsByClassName is far, far faster than
1033 * using YUI.
6f5e0852 1034 *
7d2a0492 1035 * Note: the third argument used to be an object with odd behaviour. It now
1036 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1037 * mimicked if you pass an object.
1038 *
1039 * @param {Node} oElm The top-level node for searching. To search a whole
1040 * document, use `document`.
1041 * @param {String} strTagName filter by tag names
1042 * @param {String} name same as HTML5 spec
1043 */
1044function getElementsByClassName(oElm, strTagName, name) {
1045 // for backwards compatibility
1046 if(typeof name == "object") {
1047 var names = new Array();
1048 for(var i=0; i<name.length; i++) names.push(names[i]);
1049 name = names.join('');
1050 }
1051 // use native implementation if possible
1052 if (oElm.getElementsByClassName && Array.filter) {
1053 if (strTagName == '*') {
1054 return oElm.getElementsByClassName(name);
1055 } else {
1056 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1057 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1058 });
1059 }
1060 }
1061 // native implementation unavailable, fall back to slow method
c849ed1e 1062 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1063 var arrReturnElements = new Array();
1064 var arrRegExpClassNames = new Array();
7d2a0492 1065 var names = name.split(' ');
1066 for(var i=0; i<names.length; i++) {
1067 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
c849ed1e 1068 }
1069 var oElement;
1070 var bMatchesAll;
b51709c1 1071 for(var j=0; j<arrElements.length; j++) {
c849ed1e 1072 oElement = arrElements[j];
1073 bMatchesAll = true;
b51709c1 1074 for(var k=0; k<arrRegExpClassNames.length; k++) {
1075 if(!arrRegExpClassNames[k].test(oElement.className)) {
c849ed1e 1076 bMatchesAll = false;
1077 break;
1078 }
1079 }
b51709c1 1080 if(bMatchesAll) {
c849ed1e 1081 arrReturnElements.push(oElement);
1082 }
1083 }
1084 return (arrReturnElements)
1085}
77241d9b 1086
f8065dd2 1087function openpopup(event, args) {
1088
2cf81209 1089 if (event) {
c7e3e61c
SH
1090 if (event.preventDefault) {
1091 event.preventDefault();
1092 } else {
1093 event.returnValue = false;
1094 }
2cf81209 1095 }
3a42ad12 1096
2cf81209
SH
1097 var fullurl = args.url;
1098 if (!args.url.match(/https?:\/\//)) {
1099 fullurl = M.cfg.wwwroot + args.url;
77241d9b 1100 }
2cf81209
SH
1101 var windowobj = window.open(fullurl,args.name,args.options);
1102 if (!windowobj) {
1103 return true;
1104 }
1105 if (args.fullscreen) {
1106 windowobj.moveTo(0,0);
1107 windowobj.resizeTo(screen.availWidth,screen.availHeight);
1108 }
1109 windowobj.focus();
3a42ad12 1110
2cf81209 1111 return false;
77241d9b 1112}
740939ec 1113
b166403f 1114/** Close the current browser window. */
7a5c78e0 1115function close_window(e) {
c7e3e61c
SH
1116 if (e.preventDefault) {
1117 e.preventDefault();
1118 } else {
1119 e.returnValue = false;
1120 }
1db6b9b8 1121 window.close();
b166403f 1122}
1123
9f319372 1124/**
1125 * Used in a couple of modules to hide navigation areas when using AJAX
1126 */
34a2777c 1127
2538037f 1128function show_item(itemid) {
1129 var item = document.getElementById(itemid);
1130 if (item) {
1131 item.style.display = "";
1132 }
1133}
1134
1135function destroy_item(itemid) {
1136 var item = document.getElementById(itemid);
1137 if (item) {
1138 item.parentNode.removeChild(item);
1139 }
1140}
34a2777c 1141/**
1142 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1143 * @param controlid the control id.
1144 */
1145function focuscontrol(controlid) {
1146 var control = document.getElementById(controlid);
1147 if (control) {
1148 control.focus();
1149 }
e29380f3 1150}
1151
e11a8328 1152/**
1153 * Transfers keyboard focus to an HTML element based on the old style style of focus
1154 * This function should be removed as soon as it is no longer used
1155 */
428acddb 1156function old_onload_focus(formid, controlname) {
5f8bce50 1157 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
428acddb 1158 document.forms[formid].elements[controlname].focus();
e11a8328 1159 }
1160}
1161
1bcb7eb5 1162function build_querystring(obj) {
aa281068
AD
1163 return convert_object_to_string(obj, '&');
1164}
1165
1166function build_windowoptionsstring(obj) {
1167 return convert_object_to_string(obj, ',');
1168}
1169
1170function convert_object_to_string(obj, separator) {
1bcb7eb5 1171 if (typeof obj !== 'object') {
1172 return null;
1173 }
1174 var list = [];
1175 for(var k in obj) {
1176 k = encodeURIComponent(k);
1177 var value = obj[k];
1178 if(obj[k] instanceof Array) {
1179 for(var i in value) {
1180 list.push(k+'[]='+encodeURIComponent(value[i]));
1181 }
1182 } else {
1183 list.push(k+'='+encodeURIComponent(value));
1184 }
1185 }
aa281068 1186 return list.join(separator);
1bcb7eb5 1187}
d25e2ca3 1188
1189function stripHTML(str) {
1190 var re = /<\S[^><]*>/g;
1191 var ret = str.replace(re, "");
1192 return ret;
1193}
1194
fd4faf98 1195Number.prototype.fixed=function(n){
1196 with(Math)
1197 return round(Number(this)*pow(10,n))/pow(10,n);
667c0ae1 1198};
fd4faf98 1199function update_progress_bar (id, width, pt, msg, es){
dfd9f745 1200 var percent = pt;
fd4faf98 1201 var status = document.getElementById("status_"+id);
1202 var percent_indicator = document.getElementById("pt_"+id);
1203 var progress_bar = document.getElementById("progress_"+id);
1204 var time_es = document.getElementById("time_"+id);
1205 status.innerHTML = msg;
1206 percent_indicator.innerHTML = percent.fixed(2) + '%';
1207 if(percent == 100) {
1208 progress_bar.style.background = "green";
1209 time_es.style.display = "none";
1210 } else {
1211 progress_bar.style.background = "#FFCC66";
dfd9f745
PS
1212 if (es == '?'){
1213 time_es.innerHTML = "";
fd4faf98 1214 }else {
1215 time_es.innerHTML = es.fixed(2)+" sec";
1216 time_es.style.display
1217 = "block";
1218 }
1219 }
1220 progress_bar.style.width = width + "px";
1221
1222}
bf6c37c7 1223
73b62703
PS
1224
1225// ===== Deprecated core Javascript functions for Moodle ====
1226// DO NOT USE!!!!!!!
e50b4c89 1227// Do not put this stuff in separate file because it only adds extra load on servers!
73b62703 1228
2d65597b
PS
1229/**
1230 * Used in a couple of modules to hide navigation areas when using AJAX
1231 */
1232function hide_item(itemid) {
3b044ba3 1233 // use class='hiddenifjs' instead
2d65597b
PS
1234 var item = document.getElementById(itemid);
1235 if (item) {
1236 item.style.display = "none";
1237 }
1238}
36282d85 1239
2cf81209
SH
1240M.util.help_icon = {
1241 Y : null,
36282d85 1242 instance : null,
2cf81209
SH
1243 add : function(Y, properties) {
1244 this.Y = Y;
61ddb953
RW
1245 properties.node = Y.one('#'+properties.id);
1246 if (properties.node) {
1247 properties.node.on('click', this.display, this, properties);
f855a5d2 1248 }
2cf81209
SH
1249 },
1250 display : function(event, args) {
1251 event.preventDefault();
1252 if (M.util.help_icon.instance === null) {
1253 var Y = M.util.help_icon.Y;
0958759d 1254 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', function(Y) {
2cf81209 1255 var help_content_overlay = {
61ddb953 1256 helplink : null,
2cf81209
SH
1257 overlay : null,
1258 init : function() {
1259
61ddb953 1260 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
2cf81209
SH
1261 // Create an overlay from markup
1262 this.overlay = new Y.Overlay({
1263 headerContent: closebtn,
1264 bodyContent: '',
61ddb953 1265 id: 'helppopupbox',
3a42ad12 1266 width:'400px',
2cf81209 1267 visible : false,
3a42ad12 1268 constrain : true
2cf81209
SH
1269 });
1270 this.overlay.render(Y.one(document.body));
1271
1272 closebtn.on('click', this.overlay.hide, this.overlay);
3a42ad12 1273
2cf81209 1274 var boundingBox = this.overlay.get("boundingBox");
2cf81209
SH
1275
1276 // Hide the menu if the user clicks outside of its content
1277 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1278 var oTarget = event.target;
1279 var menuButton = Y.one("#"+args.id);
3a42ad12 1280
2cf81209
SH
1281 if (!oTarget.compareTo(menuButton) &&
1282 !menuButton.contains(oTarget) &&
1283 !oTarget.compareTo(boundingBox) &&
1284 !boundingBox.contains(oTarget)) {
1285 this.overlay.hide();
1286 }
1287 }, this);
61ddb953
RW
1288
1289 Y.on("key", this.close, closebtn , "down:13", this);
1290 closebtn.on('click', this.close, this);
1291 },
1292
1293 close : function(e) {
1294 e.preventDefault();
1295 this.helplink.focus();
1296 this.overlay.hide();
2cf81209
SH
1297 },
1298
1299 display : function(event, args) {
61ddb953 1300 this.helplink = args.node;
2cf81209 1301 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
61ddb953 1302 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
2cf81209
SH
1303
1304 var fullurl = args.url;
1305 if (!args.url.match(/https?:\/\//)) {
1306 fullurl = M.cfg.wwwroot + args.url;
36282d85 1307 }
36282d85 1308
2cf81209
SH
1309 var ajaxurl = fullurl + '&ajax=1';
1310
1311 var cfg = {
1312 method: 'get',
1313 context : this,
1314 on: {
1315 success: function(id, o, node) {
1316 this.display_callback(o.responseText);
1317 },
1318 failure: function(id, o, node) {
1319 var debuginfo = o.statusText;
1320 if (M.cfg.developerdebug) {
1321 o.statusText += ' (' + ajaxurl + ')';
1322 }
1323 this.display_callback('bodyContent',debuginfo);
1324 }
1325 }
1326 };
36282d85 1327
2cf81209
SH
1328 Y.io(ajaxurl, cfg);
1329 this.overlay.show();
61ddb953
RW
1330
1331 Y.one('#closehelpbox').focus();
2cf81209 1332 },
36282d85 1333
2cf81209 1334 display_callback : function(content) {
61ddb953 1335 this.overlay.set('bodyContent', content);
2cf81209
SH
1336 },
1337
1338 hideContent : function() {
1339 help = this;
1340 help.overlay.hide();
36282d85 1341 }
667c0ae1 1342 };
2cf81209
SH
1343 help_content_overlay.init();
1344 M.util.help_icon.instance = help_content_overlay;
1345 M.util.help_icon.instance.display(event, args);
1346 });
1347 } else {
1348 M.util.help_icon.instance.display(event, args);
1349 }
1350 },
3a42ad12
RW
1351 init : function(Y) {
1352 this.Y = Y;
36282d85 1353 }
667c0ae1 1354};
d2dbd0c0
SH
1355
1356/**
1357 * Custom menu namespace
1358 */
1359M.core_custom_menu = {
1360 /**
1361 * This method is used to initialise a custom menu given the id that belongs
1362 * to the custom menu's root node.
3a42ad12 1363 *
d2dbd0c0
SH
1364 * @param {YUI} Y
1365 * @param {string} nodeid
1366 */
1367 init : function(Y, nodeid) {
242b78f7
SH
1368 var node = Y.one('#'+nodeid);
1369 if (node) {
1370 Y.use('node-menunav', function(Y) {
1371 // Get the node
1372 // Remove the javascript-disabled class.... obviously javascript is enabled.
1373 node.removeClass('javascript-disabled');
1374 // Initialise the menunav plugin
1375 node.plug(Y.Plugin.NodeMenuNav);
1376 });
1377 }
d2dbd0c0 1378 }
667c0ae1 1379};
572dd8ec
SH
1380
1381/**
1382 * Used to store form manipulation methods and enhancments
1383 */
1384M.form = M.form || {};
1385
1386/**
1387 * Converts a nbsp indented select box into a multi drop down custom control much
1388 * like the custom menu. It also selectable categories on or off.
1389 *
1390 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1391 *
1392 * @param {YUI} Y
1393 * @param {string} id
1394 * @param {Array} options
1395 */
1396M.form.init_smartselect = function(Y, id, options) {
882e66fe
SH
1397 if (!id.match(/^id_/)) {
1398 id = 'id_'+id;
1399 }
1400 var select = Y.one('select#'+id);
572dd8ec
SH
1401 if (!select) {
1402 return false;
1403 }
1404 Y.use('event-delegate',function(){
1405 var smartselect = {
1406 id : id,
1407 structure : [],
1408 options : [],
1409 submenucount : 0,
1410 currentvalue : null,
1411 currenttext : null,
1412 shownevent : null,
1413 cfg : {
1414 selectablecategories : true,
1415 mode : null
1416 },
1417 nodes : {
1418 select : null,
1419 loading : null,
1420 menu : null
1421 },
1422 init : function(Y, id, args, nodes) {
1423 if (typeof(args)=='object') {
1424 for (var i in this.cfg) {
1425 if (args[i] || args[i]===false) {
1426 this.cfg[i] = args[i];
1427 }
1428 }
1429 }
3a42ad12 1430
572dd8ec
SH
1431 // Display a loading message first up
1432 this.nodes.select = nodes.select;
1433
1434 this.currentvalue = this.nodes.select.get('selectedIndex');
1435 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1436
1437 var options = Array();
667c0ae1 1438 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
572dd8ec
SH
1439 this.nodes.select.all('option').each(function(option, index) {
1440 var rawtext = option.get('innerHTML');
1441 var text = rawtext.replace(/^(&nbsp;)*/, '');
1442 if (rawtext === text) {
1443 text = rawtext.replace(/^(\s)*/, '');
1444 var depth = (rawtext.length - text.length ) + 1;
1445 } else {
1446 var depth = ((rawtext.length - text.length )/12)+1;
1447 }
1448 option.set('innerHTML', text);
1449 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1450 }, this);
1451
1452 this.structure = [];
1453 var structcount = 0;
1454 for (var i in options) {
1455 var o = options[i];
1456 if (o.depth == 0) {
1457 this.structure.push(o);
1458 structcount++;
1459 } else {
1460 var d = o.depth;
1461 var current = this.structure[structcount-1];
1462 for (var j = 0; j < o.depth-1;j++) {
1463 if (current && current.children) {
1464 current = current.children[current.children.length-1];
1465 }
1466 }
1467 if (current && current.children) {
1468 current.children.push(o);
1469 }
1470 }
1471 }
1472
1473 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1474 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1475 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1476 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1477
1478 if (this.cfg.mode == null) {
1479 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1480 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1481 this.cfg.mode = 'compact';
1482 } else {
1483 this.cfg.mode = 'spanning';
1484 }
1485 }
1486
1487 if (this.cfg.mode == 'compact') {
1488 this.nodes.menu.addClass('compactmenu');
1489 } else {
1490 this.nodes.menu.addClass('spanningmenu');
1491 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1492 }
1493
1494 Y.one(document.body).append(this.nodes.menu);
1495 var pos = this.nodes.select.getXY();
1496 pos[0] += 1;
1497 this.nodes.menu.setXY(pos);
1498 this.nodes.menu.on('click', this.handle_click, this);
32690ac2
SH
1499
1500 Y.one(window).on('resize', function(){
1501 var pos = this.nodes.select.getXY();
1502 pos[0] += 1;
1503 this.nodes.menu.setXY(pos);
1504 }, this);
572dd8ec
SH
1505 },
1506 generate_menu_content : function() {
1507 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1508 content += this.generate_submenu_content(this.structure[0], true);
1509 content += '</ul></div>';
1510 return content;
1511 },
1512 generate_submenu_content : function(item, rootelement) {
1513 this.submenucount++;
1514 var content = '';
1515 if (item.children.length > 0) {
1516 if (rootelement) {
1517 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1518 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1519 content += '<div class="smartselect_menu_content">';
1520 } else {
1521 content += '<li class="smartselect_submenuitem">';
1522 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1523 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1524 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1525 content += '<div class="smartselect_submenu_content">';
1526 }
1527 content += '<ul>';
1528 for (var i in item.children) {
1529 content += this.generate_submenu_content(item.children[i],false);
1530 }
1531 content += '</ul>';
1532 content += '</div>';
1533 content += '</div>';
1534 if (rootelement) {
1535 } else {
1536 content += '</li>';
1537 }
1538 } else {
1539 content += '<li class="smartselect_menuitem">';
1540 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1541 content += '</li>';
1542 }
1543 return content;
1544 },
1545 select : function(e) {
1546 var t = e.target;
1547 e.halt();
1548 this.currenttext = t.get('innerHTML');
1549 this.currentvalue = t.getAttribute('value');
1550 this.nodes.select.set('selectedIndex', this.currentvalue);
1551 this.hide_menu();
1552 },
1553 handle_click : function(e) {
1554 var target = e.target;
1555 if (target.hasClass('smartselect_mask')) {
1556 this.show_menu(e);
1557 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1558 this.select(e);
1559 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1560 this.show_sub_menu(e);
1561 }
1562 },
1563 show_menu : function(e) {
1564 e.halt();
1565 var menu = e.target.ancestor().one('.smartselect_menu');
1566 menu.addClass('visible');
1567 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1568 },
1569 show_sub_menu : function(e) {
1570 e.halt();
1571 var target = e.target;
1572 if (!target.hasClass('smartselect_submenuitem')) {
1573 target = target.ancestor('.smartselect_submenuitem');
1574 }
1575 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1576 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1577 return;
1578 }
1579 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1580 target.one('.smartselect_submenu').addClass('visible');
1581 },
1582 hide_menu : function() {
1583 this.nodes.menu.all('.visible').removeClass('visible');
1584 if (this.shownevent) {
1585 this.shownevent.detach();
1586 }
1587 }
667c0ae1 1588 };
572dd8ec
SH
1589 smartselect.init(Y, id, options, {select:select});
1590 });
667c0ae1 1591};
3a42ad12 1592
fcd2cbaf
PS
1593/** List of flv players to be loaded */
1594M.util.video_players = [];
1595/** List of mp3 players to be loaded */
1596M.util.audio_players = [];
1597
1598/**
1599 * Add video player
1600 * @param id element id
1601 * @param fileurl media url
1602 * @param width
1603 * @param height
1604 * @param autosize true means detect size from media
1605 */
1606M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1607 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
667c0ae1 1608};
3a42ad12 1609
fcd2cbaf
PS
1610/**
1611 * Add audio player.
1612 * @param id
1613 * @param fileurl
1614 * @param small
1615 */
1616M.util.add_audio_player = function (id, fileurl, small) {
1617 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
667c0ae1 1618};
3a42ad12 1619
fcd2cbaf
PS
1620/**
1621 * Initialise all audio and video player, must be called from page footer.
1622 */
1623M.util.load_flowplayer = function() {
1624 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1625 return;
1626 }
1627 if (typeof(flowplayer) == 'undefined') {
1628 var loaded = false;
1629
1630 var embed_function = function() {
1631 if (loaded || typeof(flowplayer) == 'undefined') {
1632 return;
3a42ad12 1633 }
fcd2cbaf
PS
1634 loaded = true;
1635
1636 var controls = {
1637 autoHide: true
1638 }
1639 /* TODO: add CSS color overrides for the flv flow player */
1640
1641 for(var i=0; i<M.util.video_players.length; i++) {
1642 var video = M.util.video_players[i];
1643 if (video.width > 0 && video.height > 0) {
1644 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.7.swf', width: video.width, height: video.height};
1645 } else {
1646 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.7.swf';
1647 }
1648 flowplayer(video.id, src, {
1649 plugins: {controls: controls},
1650 clip: {
1651 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1652 onMetaData: function(clip) {
1653 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1654 clip.mvideo.resized = true;
1655 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1656 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1657 // bad luck, we have to guess - we may not get metadata at all
1658 var width = clip.width;
1659 var height = clip.height;
1660 } else {
1661 var width = clip.metaData.width;
1662 var height = clip.metaData.height;
1663 }
1664 var minwidth = 300; // controls are messed up in smaller objects
1665 if (width < minwidth) {
1666 height = (height * minwidth) / width;
1667 width = minwidth;
1668 }
1669
1670 var object = this._api();
1671 object.width = width;
1672 object.height = height;
1673 }
1674 }
1675 }
1676 });
1677 }
1678 if (M.util.audio_players.length == 0) {
1679 return;
1680 }
1681 var controls = {
1682 autoHide: false,
1683 fullscreen: false,
1684 next: false,
1685 previous: false,
1686 scrubber: true,
1687 play: true,
1688 pause: true,
1689 volume: true,
1690 mute: false,
1691 backgroundGradient: [0.5,0,0.3]
1692 };
1693
1694 var rule;
1695 for (var j=0; j < document.styleSheets.length; j++) {
1696 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1697 var allrules = document.styleSheets[j].rules;
1698 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1699 var allrules = document.styleSheets[j].cssRules;
1700 } else {
1701 // why??
1702 continue;
1703 }
1704 for(var i=0; i<allrules.length; i++) {
1705 rule = '';
1706 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1707 if (typeof(allrules[i].cssText) != 'undefined') {
18382a85 1708 rule = allrules[i].cssText;
fcd2cbaf
PS
1709 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1710 rule = allrules[i].style.cssText;
1711 }
1712 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1713 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1714 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1715 controls[colprop] = rule;
1716 }
1717 }
1718 }
1719 allrules = false;
1720 }
1721
1722 for(i=0; i<M.util.audio_players.length; i++) {
1723 var audio = M.util.audio_players[i];
1724 if (audio.small) {
1725 controls.controlall = false;
1726 controls.height = 15;
1727 controls.time = false;
1728 } else {
1729 controls.controlall = true;
1730 controls.height = 25;
1731 controls.time = true;
1732 }
1733 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.7.swf', {
1734 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.2.swf'}},
1735 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1736 });
1737 }
1738 }
1739
1740 if (M.cfg.jsrev == -10) {
1741 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.6.js';
1742 } else {
1743 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?file=/lib/flowplayer/flowplayer-3.2.6.js&rev=' + M.cfg.jsrev;
1744 }
1745 var fileref = document.createElement('script');
1746 fileref.setAttribute('type','text/javascript');
1747 fileref.setAttribute('src', jsurl);
1748 fileref.onload = embed_function;
1749 fileref.onreadystatechange = embed_function;
1750 document.getElementsByTagName('head')[0].appendChild(fileref);
1751 }
1752}