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