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