Unit tests MDL-24907 Fix blog unit tests
[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'));
368 select.on('click', processchange, form, select.get('selectedIndex'));
369 }
4aea3cc7 370 }
4aea3cc7 371 });
a9967cf5 372};
dd9b1882 373
4d10e579
PS
374/**
375 * Attach handler to url_select
376 */
377M.util.init_url_select = function(Y, formid, selectid, nothing) {
4aea3cc7
PS
378 YUI(M.yui.loader).use('node', function(Y) {
379 Y.on('change', function() {
c7bbb86f
SH
380 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
381 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
382 }
4aea3cc7
PS
383 },
384 '#'+selectid);
385 });
386};
387
388/**
389 * Breaks out all links to the top frame - used in frametop page layout.
390 */
391M.util.init_frametop = function(Y) {
392 Y.all('a').each(function(node) {
393 node.set('target', '_top');
394 });
395 Y.all('form').each(function(node) {
396 node.set('target', '_top');
397 });
4d10e579
PS
398};
399
24e27ac0
SH
400/**
401 * Finds all nodes that match the given CSS selector and attaches events to them
402 * so that they toggle a given classname when clicked.
403 *
404 * @param {YUI} Y
405 * @param {string} id An id containing elements to target
406 * @param {string} cssselector A selector to use to find targets
407 * @param {string} toggleclassname A classname to toggle
408 */
409M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname) {
410 var node = Y.one('#'+id);
411 node.all(cssselector).each(function(node){
412 node.on('click', function(e){
413 e.stopPropagation();
4461a18e
SH
414 if (e.target.test(cssselector) && e.target.get('nodeName')!='A' && e.target.get('nodeName')!='IMG') {
415 this.ancestor().toggleClass(toggleclassname);
24e27ac0
SH
416 }
417 }, node);
418 });
419 // Attach this click event to the node rather than all selectors... will be much better
420 // for performance
421 node.on('click', function(e){
422 if (e.target.hasClass('addtoall')) {
423 node.all(cssselector).addClass(toggleclassname);
424 } else if (e.target.hasClass('removefromall')) {
425 node.all(cssselector+'.'+toggleclassname).removeClass(toggleclassname);
426 }
427 }, node);
667c0ae1 428};
24e27ac0 429
233b6ff9
SH
430/**
431 * Initialises a colour picker
432 *
433 * Designed to be used with admin_setting_configcolourpicker although could be used
434 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
435 * above or below the input (must have the same parent) and then call this with the
436 * id.
437 *
438 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
439 * contrib/blocks. For better docs refer to that.
440 *
441 * @param {YUI} Y
442 * @param {int} id
443 * @param {object} previewconf
444 */
445M.util.init_colour_picker = function(Y, id, previewconf) {
446 /**
447 * We need node and event-mouseenter
448 */
449 Y.use('node', 'event-mouseenter', function(){
450 /**
451 * The colour picker object
452 */
453 var colourpicker = {
454 box : null,
455 input : null,
456 image : null,
457 preview : null,
458 current : null,
459 eventClick : null,
460 eventMouseEnter : null,
461 eventMouseLeave : null,
462 eventMouseMove : null,
463 width : 300,
464 height : 100,
465 factor : 5,
466 /**
467 * Initalises the colour picker by putting everything together and wiring the events
468 */
469 init : function() {
470 this.input = Y.one('#'+id);
471 this.box = this.input.ancestor().one('.admin_colourpicker');
472 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
473 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
474 this.preview = Y.Node.create('<div class="previewcolour"></div>');
475 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
476 this.current = Y.Node.create('<div class="currentcolour"></div>');
477 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
478 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
479
480 if (typeof(previewconf) === 'object' && previewconf !== null) {
481 Y.one('#'+id+'_preview').on('click', function(e){
3c3a3c3c 482 if (Y.Lang.isString(previewconf.selector)) {
bb9e9362
SH
483 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
484 } else {
485 for (var i in previewconf.selector) {
486 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
487 }
488 }
233b6ff9
SH
489 }, this);
490 }
491
492 this.eventClick = this.image.on('click', this.pickColour, this);
493 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
494 },
495 /**
496 * Starts to follow the mouse once it enter the image
497 */
498 startFollow : function(e) {
499 this.eventMouseEnter.detach();
500 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
501 this.eventMouseMove = this.image.on('mousemove', function(e){
502 this.preview.setStyle('backgroundColor', this.determineColour(e));
503 }, this);
504 },
505 /**
506 * Stops following the mouse
507 */
508 endFollow : function(e) {
509 this.eventMouseMove.detach();
510 this.eventMouseLeave.detach();
511 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
512 },
513 /**
514 * Picks the colour the was clicked on
515 */
516 pickColour : function(e) {
517 var colour = this.determineColour(e);
518 this.input.set('value', colour);
519 this.current.setStyle('backgroundColor', colour);
520 },
521 /**
522 * Calculates the colour fromthe given co-ordinates
523 */
524 determineColour : function(e) {
525 var eventx = Math.floor(e.pageX-e.target.getX());
526 var eventy = Math.floor(e.pageY-e.target.getY());
527
528 var imagewidth = this.width;
529 var imageheight = this.height;
530 var factor = this.factor;
531 var colour = [255,0,0];
532
533 var matrices = [
534 [ 0, 1, 0],
535 [ -1, 0, 0],
536 [ 0, 0, 1],
537 [ 0, -1, 0],
538 [ 1, 0, 0],
539 [ 0, 0, -1]
540 ];
541
542 var matrixcount = matrices.length;
543 var limit = Math.round(imagewidth/matrixcount);
544 var heightbreak = Math.round(imageheight/2);
545
546 for (var x = 0; x < imagewidth; x++) {
547 var divisor = Math.floor(x / limit);
548 var matrix = matrices[divisor];
549
550 colour[0] += matrix[0]*factor;
551 colour[1] += matrix[1]*factor;
552 colour[2] += matrix[2]*factor;
553
554 if (eventx==x) {
555 break;
556 }
557 }
558
559 var pixel = [colour[0], colour[1], colour[2]];
560 if (eventy < heightbreak) {
561 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
562 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
563 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
564 } else if (eventy > heightbreak) {
565 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
566 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
567 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
568 }
569
570 return this.convert_rgb_to_hex(pixel);
571 },
572 /**
573 * Converts an RGB value to Hex
574 */
575 convert_rgb_to_hex : function(rgb) {
576 var hex = '#';
577 var hexchars = "0123456789ABCDEF";
578 for (var i=0; i<3; i++) {
579 var number = Math.abs(rgb[i]);
580 if (number == 0 || isNaN(number)) {
581 hex += '00';
582 } else {
583 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
584 }
585 }
586 return hex;
587 }
667c0ae1 588 };
233b6ff9
SH
589 /**
590 * Initialise the colour picker :) Hoorah
591 */
592 colourpicker.init();
593 });
667c0ae1 594};
233b6ff9 595
cbb54cce
SH
596M.util.init_block_hider = function(Y, config) {
597 Y.use('base', 'node', function(Y) {
598 M.util.block_hider = M.util.block_hider || (function(){
599 var blockhider = function() {
600 blockhider.superclass.constructor.apply(this, arguments);
667c0ae1 601 };
cbb54cce
SH
602 blockhider.prototype = {
603 initializer : function(config) {
667c0ae1 604 this.set('block', '#'+this.get('id'));
cbb54cce
SH
605 var b = this.get('block'),
606 t = b.one('.title'),
607 a = null;
608 if (t && (a = t.one('.block_action'))) {
667c0ae1 609 var hide = Y.Node.create('<img class="block-hider-hide" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
cbb54cce 610 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
667c0ae1 611 var show = Y.Node.create('<img class="block-hider-show" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
cbb54cce
SH
612 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
613 a.insert(show, 0).insert(hide, 0);
614 }
615 },
616 updateState : function(e, hide) {
617 M.util.set_user_preference(this.get('preference'), hide);
618 if (hide) {
619 this.get('block').addClass('hidden');
620 } else {
621 this.get('block').removeClass('hidden');
622 }
623 }
667c0ae1 624 };
cbb54cce
SH
625 Y.extend(blockhider, Y.Base, blockhider.prototype, {
626 NAME : 'blockhider',
627 ATTRS : {
628 id : {},
629 preference : {},
630 iconVisible : {
631 value : M.util.image_url('t/switch_minus', 'moodle')
632 },
633 iconHidden : {
634 value : M.util.image_url('t/switch_plus', 'moodle')
635 },
636 block : {
637 setter : function(node) {
638 return Y.one(node);
639 }
640 }
641 }
642 });
643 return blockhider;
644 })();
645 new M.util.block_hider(config);
646 });
667c0ae1 647};
cbb54cce 648
38224dcb
PS
649//=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
650
b48fd7b5 651function popupchecker(msg) {
496e3ccd 652 var testwindow = window.open('', '', 'width=1,height=1,left=0,top=0,scrollbars=no');
f6b6861d 653 if (!testwindow) {
654 alert(msg);
655 } else {
b48fd7b5 656 testwindow.close();
657 }
658}
659
63d28811 660function checkall() {
214f5850 661 var inputs = document.getElementsByTagName('input');
662 for (var i = 0; i < inputs.length; i++) {
663 if (inputs[i].type == 'checkbox') {
664 inputs[i].checked = true;
665 }
d2ce367f 666 }
63d28811 667}
668
03f9425f 669function checknone() {
214f5850 670 var inputs = document.getElementsByTagName('input');
671 for (var i = 0; i < inputs.length; i++) {
672 if (inputs[i].type == 'checkbox') {
673 inputs[i].checked = false;
674 }
d2ce367f 675 }
03f9425f 676}
677
45caa363 678/**
679 * Either check, or uncheck, all checkboxes inside the element with id is
680 * @param id the id of the container
681 * @param checked the new state, either '' or 'checked'.
682 */
683function select_all_in_element_with_id(id, checked) {
684 var container = document.getElementById(id);
685 if (!container) {
686 return;
687 }
688 var inputs = container.getElementsByTagName('input');
689 for (var i = 0; i < inputs.length; ++i) {
690 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
691 inputs[i].checked = checked;
692 }
693 }
694}
695
8ceb09e0 696function select_all_in(elTagName, elClass, elId) {
d2ce367f 697 var inputs = document.getElementsByTagName('input');
8ceb09e0 698 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 699 for(var i = 0; i < inputs.length; ++i) {
bee40515 700 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 701 inputs[i].checked = 'checked';
702 }
703 }
704}
705
8ceb09e0 706function deselect_all_in(elTagName, elClass, elId) {
363cb62c 707 var inputs = document.getElementsByTagName('INPUT');
8ceb09e0 708 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
363cb62c 709 for(var i = 0; i < inputs.length; ++i) {
bee40515 710 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
363cb62c 711 inputs[i].checked = '';
712 }
713 }
714}
715
716function confirm_if(expr, message) {
717 if(!expr) {
718 return true;
719 }
720 return confirm(message);
721}
47aa42e2 722
723
724/*
725 findParentNode (start, elementName, elementClass, elementID)
50ef8eb9 726
47aa42e2 727 Travels up the DOM hierarchy to find a parent element with the
728 specified tag name, class, and id. All conditions must be met,
729 but any can be ommitted. Returns the BODY element if no match
730 found.
731*/
732function findParentNode(el, elName, elClass, elId) {
45caa363 733 while (el.nodeName.toUpperCase() != 'BODY') {
734 if ((!elName || el.nodeName.toUpperCase() == elName) &&
47aa42e2 735 (!elClass || el.className.indexOf(elClass) != -1) &&
45caa363 736 (!elId || el.id == elId)) {
47aa42e2 737 break;
738 }
739 el = el.parentNode;
740 }
741 return el;
742}
19194f82 743/*
744 findChildNode (start, elementName, elementClass, elementID)
745
746 Travels down the DOM hierarchy to find all child elements with the
747 specified tag name, class, and id. All conditions must be met,
748 but any can be ommitted.
a23f0aaf 749 Doesn't examine children of matches.
19194f82 750*/
751function findChildNodes(start, tagName, elementClass, elementID, elementName) {
752 var children = new Array();
83c9a8a2 753 for (var i = 0; i < start.childNodes.length; i++) {
a23f0aaf 754 var classfound = false;
83c9a8a2 755 var child = start.childNodes[i];
a23f0aaf 756 if((child.nodeType == 1) &&//element node type
b51709c1 757 (elementClass && (typeof(child.className)=='string'))) {
a23f0aaf 758 var childClasses = child.className.split(/\s+/);
b51709c1 759 for (var childClassIndex in childClasses) {
760 if (childClasses[childClassIndex]==elementClass) {
a23f0aaf 761 classfound = true;
762 break;
763 }
764 }
765 }
f07b9627 766 if(child.nodeType == 1) { //element node type
767 if ( (!tagName || child.nodeName == tagName) &&
768 (!elementClass || classfound)&&
769 (!elementID || child.id == elementID) &&
770 (!elementName || child.name == elementName))
771 {
772 children = children.concat(child);
773 } else {
774 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
775 }
19194f82 776 }
19194f82 777 }
778 return children;
779}
47aa42e2 780
54bb33eb 781function unmaskPassword(id) {
239ade45 782 var pw = document.getElementById(id);
54bb33eb 783 var chb = document.getElementById(id+'unmask');
239ade45 784
785 try {
786 // first try IE way - it can not set name attribute later
787 if (chb.checked) {
788 var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
789 } else {
790 var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
791 }
eba8cd63 792 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
239ade45 793 } catch (e) {
794 var newpw = document.createElement('input');
795 newpw.setAttribute('name', pw.name);
796 if (chb.checked) {
797 newpw.setAttribute('type', 'text');
798 } else {
799 newpw.setAttribute('type', 'password');
800 }
eba8cd63 801 newpw.setAttribute('class', pw.getAttribute('class'));
239ade45 802 }
803 newpw.id = pw.id;
804 newpw.size = pw.size;
805 newpw.onblur = pw.onblur;
806 newpw.onchange = pw.onchange;
807 newpw.value = pw.value;
808 pw.parentNode.replaceChild(newpw, pw);
809}
810
47aa42e2 811function filterByParent(elCollection, parentFinder) {
812 var filteredCollection = [];
45caa363 813 for (var i = 0; i < elCollection.length; ++i) {
47aa42e2 814 var findParent = parentFinder(elCollection[i]);
45caa363 815 if (findParent.nodeName.toUpperCase != 'BODY') {
47aa42e2 816 filteredCollection.push(elCollection[i]);
817 }
818 }
819 return filteredCollection;
820}
821
7979105c 822/*
823 All this is here just so that IE gets to handle oversized blocks
824 in a visually pleasing manner. It does a browser detect. So sue me.
825*/
826
827function fix_column_widths() {
828 var agt = navigator.userAgent.toLowerCase();
829 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
830 fix_column_width('left-column');
831 fix_column_width('right-column');
832 }
833}
834
835function fix_column_width(colName) {
836 if(column = document.getElementById(colName)) {
837 if(!column.offsetWidth) {
838 setTimeout("fix_column_width('" + colName + "')", 20);
839 return;
840 }
841
842 var width = 0;
843 var nodes = column.childNodes;
844
845 for(i = 0; i < nodes.length; ++i) {
6605ff8c 846 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 847 if(width < nodes[i].offsetWidth) {
848 width = nodes[i].offsetWidth;
849 }
850 }
851 }
852
853 for(i = 0; i < nodes.length; ++i) {
6605ff8c 854 if(nodes[i].className.indexOf("block") != -1 ) {
7979105c 855 nodes[i].style.width = width + 'px';
856 }
857 }
858 }
859}
d13c5938 860
861
862/*
9f439b17 863 Insert myValue at current cursor position
864 */
d13c5938 865function insertAtCursor(myField, myValue) {
9f439b17 866 // IE support
867 if (document.selection) {
868 myField.focus();
869 sel = document.selection.createRange();
870 sel.text = myValue;
871 }
872 // Mozilla/Netscape support
873 else if (myField.selectionStart || myField.selectionStart == '0') {
874 var startPos = myField.selectionStart;
875 var endPos = myField.selectionEnd;
876 myField.value = myField.value.substring(0, startPos)
877 + myValue + myField.value.substring(endPos, myField.value.length);
878 } else {
879 myField.value += myValue;
880 }
d13c5938 881}
7470d6de 882
883
884/*
c0056e22 885 Call instead of setting window.onload directly or setting body onload=.
886 Adds your function to a chain of functions rather than overwriting anything
887 that exists.
888*/
7470d6de 889function addonload(fn) {
890 var oldhandler=window.onload;
891 window.onload=function() {
892 if(oldhandler) oldhandler();
c0056e22 893 fn();
7470d6de 894 }
895}
7d2a0492 896/**
897 * Replacement for getElementsByClassName in browsers that aren't cool enough
6f5e0852 898 *
7d2a0492 899 * Relying on the built-in getElementsByClassName is far, far faster than
900 * using YUI.
6f5e0852 901 *
7d2a0492 902 * Note: the third argument used to be an object with odd behaviour. It now
903 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
904 * mimicked if you pass an object.
905 *
906 * @param {Node} oElm The top-level node for searching. To search a whole
907 * document, use `document`.
908 * @param {String} strTagName filter by tag names
909 * @param {String} name same as HTML5 spec
910 */
911function getElementsByClassName(oElm, strTagName, name) {
912 // for backwards compatibility
913 if(typeof name == "object") {
914 var names = new Array();
915 for(var i=0; i<name.length; i++) names.push(names[i]);
916 name = names.join('');
917 }
918 // use native implementation if possible
919 if (oElm.getElementsByClassName && Array.filter) {
920 if (strTagName == '*') {
921 return oElm.getElementsByClassName(name);
922 } else {
923 return Array.filter(oElm.getElementsByClassName(name), function(el) {
924 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
925 });
926 }
927 }
928 // native implementation unavailable, fall back to slow method
c849ed1e 929 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
930 var arrReturnElements = new Array();
931 var arrRegExpClassNames = new Array();
7d2a0492 932 var names = name.split(' ');
933 for(var i=0; i<names.length; i++) {
934 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
c849ed1e 935 }
936 var oElement;
937 var bMatchesAll;
b51709c1 938 for(var j=0; j<arrElements.length; j++) {
c849ed1e 939 oElement = arrElements[j];
940 bMatchesAll = true;
b51709c1 941 for(var k=0; k<arrRegExpClassNames.length; k++) {
942 if(!arrRegExpClassNames[k].test(oElement.className)) {
c849ed1e 943 bMatchesAll = false;
944 break;
945 }
946 }
b51709c1 947 if(bMatchesAll) {
c849ed1e 948 arrReturnElements.push(oElement);
949 }
950 }
951 return (arrReturnElements)
952}
77241d9b 953
f8065dd2 954function openpopup(event, args) {
955
2cf81209 956 if (event) {
c7e3e61c
SH
957 if (event.preventDefault) {
958 event.preventDefault();
959 } else {
960 event.returnValue = false;
961 }
2cf81209 962 }
3a42ad12 963
2cf81209
SH
964 var fullurl = args.url;
965 if (!args.url.match(/https?:\/\//)) {
966 fullurl = M.cfg.wwwroot + args.url;
77241d9b 967 }
2cf81209
SH
968 var windowobj = window.open(fullurl,args.name,args.options);
969 if (!windowobj) {
970 return true;
971 }
972 if (args.fullscreen) {
973 windowobj.moveTo(0,0);
974 windowobj.resizeTo(screen.availWidth,screen.availHeight);
975 }
976 windowobj.focus();
3a42ad12 977
2cf81209 978 return false;
77241d9b 979}
740939ec 980
b166403f 981/** Close the current browser window. */
7a5c78e0 982function close_window(e) {
c7e3e61c
SH
983 if (e.preventDefault) {
984 e.preventDefault();
985 } else {
986 e.returnValue = false;
987 }
7a5c78e0 988 self.close();
b166403f 989}
990
991/**
992 * Close the current browser window, forcing the window/tab that opened this
993 * popup to reload itself. */
994function close_window_reloading_opener() {
995 if (window.opener) {
996 window.opener.location.reload(1);
997 close_window();
998 // Intentionally, only try to close the window if there is some evidence we are in a popup.
999 }
8e7cebb0 1000}
9f319372 1001
1002/**
1003 * Used in a couple of modules to hide navigation areas when using AJAX
1004 */
34a2777c 1005
2538037f 1006function show_item(itemid) {
1007 var item = document.getElementById(itemid);
1008 if (item) {
1009 item.style.display = "";
1010 }
1011}
1012
1013function destroy_item(itemid) {
1014 var item = document.getElementById(itemid);
1015 if (item) {
1016 item.parentNode.removeChild(item);
1017 }
1018}
34a2777c 1019/**
1020 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1021 * @param controlid the control id.
1022 */
1023function focuscontrol(controlid) {
1024 var control = document.getElementById(controlid);
1025 if (control) {
1026 control.focus();
1027 }
e29380f3 1028}
1029
e11a8328 1030/**
1031 * Transfers keyboard focus to an HTML element based on the old style style of focus
1032 * This function should be removed as soon as it is no longer used
1033 */
428acddb 1034function old_onload_focus(formid, controlname) {
5f8bce50 1035 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
428acddb 1036 document.forms[formid].elements[controlname].focus();
e11a8328 1037 }
1038}
1039
1bcb7eb5 1040function build_querystring(obj) {
aa281068
AD
1041 return convert_object_to_string(obj, '&');
1042}
1043
1044function build_windowoptionsstring(obj) {
1045 return convert_object_to_string(obj, ',');
1046}
1047
1048function convert_object_to_string(obj, separator) {
1bcb7eb5 1049 if (typeof obj !== 'object') {
1050 return null;
1051 }
1052 var list = [];
1053 for(var k in obj) {
1054 k = encodeURIComponent(k);
1055 var value = obj[k];
1056 if(obj[k] instanceof Array) {
1057 for(var i in value) {
1058 list.push(k+'[]='+encodeURIComponent(value[i]));
1059 }
1060 } else {
1061 list.push(k+'='+encodeURIComponent(value));
1062 }
1063 }
aa281068 1064 return list.join(separator);
1bcb7eb5 1065}
d25e2ca3 1066
1067function stripHTML(str) {
1068 var re = /<\S[^><]*>/g;
1069 var ret = str.replace(re, "");
1070 return ret;
1071}
1072
fd4faf98 1073Number.prototype.fixed=function(n){
1074 with(Math)
1075 return round(Number(this)*pow(10,n))/pow(10,n);
667c0ae1 1076};
fd4faf98 1077function update_progress_bar (id, width, pt, msg, es){
1078 var percent = pt*100;
1079 var status = document.getElementById("status_"+id);
1080 var percent_indicator = document.getElementById("pt_"+id);
1081 var progress_bar = document.getElementById("progress_"+id);
1082 var time_es = document.getElementById("time_"+id);
1083 status.innerHTML = msg;
1084 percent_indicator.innerHTML = percent.fixed(2) + '%';
1085 if(percent == 100) {
1086 progress_bar.style.background = "green";
1087 time_es.style.display = "none";
1088 } else {
1089 progress_bar.style.background = "#FFCC66";
1090 if (es == Infinity){
1091 time_es.innerHTML = "Initializing...";
1092 }else {
1093 time_es.innerHTML = es.fixed(2)+" sec";
1094 time_es.style.display
1095 = "block";
1096 }
1097 }
1098 progress_bar.style.width = width + "px";
1099
1100}
bf6c37c7 1101
1102function frame_breakout(e, properties) {
1103 this.setAttribute('target', properties.framename);
41f23791 1104}
78946b9b 1105
73b62703
PS
1106
1107// ===== Deprecated core Javascript functions for Moodle ====
1108// DO NOT USE!!!!!!!
e50b4c89 1109// Do not put this stuff in separate file because it only adds extra load on servers!
73b62703 1110
2d65597b
PS
1111/**
1112 * Used in a couple of modules to hide navigation areas when using AJAX
1113 */
1114function hide_item(itemid) {
3b044ba3 1115 // use class='hiddenifjs' instead
2d65597b
PS
1116 var item = document.getElementById(itemid);
1117 if (item) {
1118 item.style.display = "none";
1119 }
1120}
36282d85 1121
2cf81209
SH
1122M.util.help_icon = {
1123 Y : null,
36282d85 1124 instance : null,
2cf81209
SH
1125 add : function(Y, properties) {
1126 this.Y = Y;
61ddb953
RW
1127 properties.node = Y.one('#'+properties.id);
1128 if (properties.node) {
1129 properties.node.on('click', this.display, this, properties);
f855a5d2 1130 }
2cf81209
SH
1131 },
1132 display : function(event, args) {
1133 event.preventDefault();
1134 if (M.util.help_icon.instance === null) {
1135 var Y = M.util.help_icon.Y;
61ddb953 1136 Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
2cf81209 1137 var help_content_overlay = {
61ddb953 1138 helplink : null,
2cf81209
SH
1139 overlay : null,
1140 init : function() {
1141
61ddb953 1142 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
2cf81209
SH
1143 // Create an overlay from markup
1144 this.overlay = new Y.Overlay({
1145 headerContent: closebtn,
1146 bodyContent: '',
61ddb953 1147 id: 'helppopupbox',
3a42ad12 1148 width:'400px',
2cf81209 1149 visible : false,
3a42ad12 1150 constrain : true
2cf81209
SH
1151 });
1152 this.overlay.render(Y.one(document.body));
1153
1154 closebtn.on('click', this.overlay.hide, this.overlay);
3a42ad12 1155
2cf81209 1156 var boundingBox = this.overlay.get("boundingBox");
2cf81209
SH
1157
1158 // Hide the menu if the user clicks outside of its content
1159 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1160 var oTarget = event.target;
1161 var menuButton = Y.one("#"+args.id);
3a42ad12 1162
2cf81209
SH
1163 if (!oTarget.compareTo(menuButton) &&
1164 !menuButton.contains(oTarget) &&
1165 !oTarget.compareTo(boundingBox) &&
1166 !boundingBox.contains(oTarget)) {
1167 this.overlay.hide();
1168 }
1169 }, this);
61ddb953
RW
1170
1171 Y.on("key", this.close, closebtn , "down:13", this);
1172 closebtn.on('click', this.close, this);
1173 },
1174
1175 close : function(e) {
1176 e.preventDefault();
1177 this.helplink.focus();
1178 this.overlay.hide();
2cf81209
SH
1179 },
1180
1181 display : function(event, args) {
61ddb953 1182 this.helplink = args.node;
2cf81209 1183 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
61ddb953 1184 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
2cf81209
SH
1185
1186 var fullurl = args.url;
1187 if (!args.url.match(/https?:\/\//)) {
1188 fullurl = M.cfg.wwwroot + args.url;
36282d85 1189 }
36282d85 1190
2cf81209
SH
1191 var ajaxurl = fullurl + '&ajax=1';
1192
1193 var cfg = {
1194 method: 'get',
1195 context : this,
1196 on: {
1197 success: function(id, o, node) {
1198 this.display_callback(o.responseText);
1199 },
1200 failure: function(id, o, node) {
1201 var debuginfo = o.statusText;
1202 if (M.cfg.developerdebug) {
1203 o.statusText += ' (' + ajaxurl + ')';
1204 }
1205 this.display_callback('bodyContent',debuginfo);
1206 }
1207 }
1208 };
36282d85 1209
2cf81209
SH
1210 Y.io(ajaxurl, cfg);
1211 this.overlay.show();
61ddb953
RW
1212
1213 Y.one('#closehelpbox').focus();
2cf81209 1214 },
36282d85 1215
2cf81209 1216 display_callback : function(content) {
61ddb953 1217 this.overlay.set('bodyContent', content);
2cf81209
SH
1218 },
1219
1220 hideContent : function() {
1221 help = this;
1222 help.overlay.hide();
36282d85 1223 }
667c0ae1 1224 };
2cf81209
SH
1225 help_content_overlay.init();
1226 M.util.help_icon.instance = help_content_overlay;
1227 M.util.help_icon.instance.display(event, args);
1228 });
1229 } else {
1230 M.util.help_icon.instance.display(event, args);
1231 }
1232 },
3a42ad12
RW
1233 init : function(Y) {
1234 this.Y = Y;
36282d85 1235 }
667c0ae1 1236};
d2dbd0c0
SH
1237
1238/**
1239 * Custom menu namespace
1240 */
1241M.core_custom_menu = {
1242 /**
1243 * This method is used to initialise a custom menu given the id that belongs
1244 * to the custom menu's root node.
3a42ad12 1245 *
d2dbd0c0
SH
1246 * @param {YUI} Y
1247 * @param {string} nodeid
1248 */
1249 init : function(Y, nodeid) {
242b78f7
SH
1250 var node = Y.one('#'+nodeid);
1251 if (node) {
1252 Y.use('node-menunav', function(Y) {
1253 // Get the node
1254 // Remove the javascript-disabled class.... obviously javascript is enabled.
1255 node.removeClass('javascript-disabled');
1256 // Initialise the menunav plugin
1257 node.plug(Y.Plugin.NodeMenuNav);
1258 });
1259 }
d2dbd0c0 1260 }
667c0ae1 1261};
572dd8ec
SH
1262
1263/**
1264 * Used to store form manipulation methods and enhancments
1265 */
1266M.form = M.form || {};
1267
1268/**
1269 * Converts a nbsp indented select box into a multi drop down custom control much
1270 * like the custom menu. It also selectable categories on or off.
1271 *
1272 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1273 *
1274 * @param {YUI} Y
1275 * @param {string} id
1276 * @param {Array} options
1277 */
1278M.form.init_smartselect = function(Y, id, options) {
882e66fe
SH
1279 if (!id.match(/^id_/)) {
1280 id = 'id_'+id;
1281 }
1282 var select = Y.one('select#'+id);
572dd8ec
SH
1283 if (!select) {
1284 return false;
1285 }
1286 Y.use('event-delegate',function(){
1287 var smartselect = {
1288 id : id,
1289 structure : [],
1290 options : [],
1291 submenucount : 0,
1292 currentvalue : null,
1293 currenttext : null,
1294 shownevent : null,
1295 cfg : {
1296 selectablecategories : true,
1297 mode : null
1298 },
1299 nodes : {
1300 select : null,
1301 loading : null,
1302 menu : null
1303 },
1304 init : function(Y, id, args, nodes) {
1305 if (typeof(args)=='object') {
1306 for (var i in this.cfg) {
1307 if (args[i] || args[i]===false) {
1308 this.cfg[i] = args[i];
1309 }
1310 }
1311 }
3a42ad12 1312
572dd8ec
SH
1313 // Display a loading message first up
1314 this.nodes.select = nodes.select;
1315
1316 this.currentvalue = this.nodes.select.get('selectedIndex');
1317 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1318
1319 var options = Array();
667c0ae1 1320 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
572dd8ec
SH
1321 this.nodes.select.all('option').each(function(option, index) {
1322 var rawtext = option.get('innerHTML');
1323 var text = rawtext.replace(/^(&nbsp;)*/, '');
1324 if (rawtext === text) {
1325 text = rawtext.replace(/^(\s)*/, '');
1326 var depth = (rawtext.length - text.length ) + 1;
1327 } else {
1328 var depth = ((rawtext.length - text.length )/12)+1;
1329 }
1330 option.set('innerHTML', text);
1331 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1332 }, this);
1333
1334 this.structure = [];
1335 var structcount = 0;
1336 for (var i in options) {
1337 var o = options[i];
1338 if (o.depth == 0) {
1339 this.structure.push(o);
1340 structcount++;
1341 } else {
1342 var d = o.depth;
1343 var current = this.structure[structcount-1];
1344 for (var j = 0; j < o.depth-1;j++) {
1345 if (current && current.children) {
1346 current = current.children[current.children.length-1];
1347 }
1348 }
1349 if (current && current.children) {
1350 current.children.push(o);
1351 }
1352 }
1353 }
1354
1355 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1356 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1357 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1358 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1359
1360 if (this.cfg.mode == null) {
1361 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1362 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1363 this.cfg.mode = 'compact';
1364 } else {
1365 this.cfg.mode = 'spanning';
1366 }
1367 }
1368
1369 if (this.cfg.mode == 'compact') {
1370 this.nodes.menu.addClass('compactmenu');
1371 } else {
1372 this.nodes.menu.addClass('spanningmenu');
1373 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1374 }
1375
1376 Y.one(document.body).append(this.nodes.menu);
1377 var pos = this.nodes.select.getXY();
1378 pos[0] += 1;
1379 this.nodes.menu.setXY(pos);
1380 this.nodes.menu.on('click', this.handle_click, this);
32690ac2
SH
1381
1382 Y.one(window).on('resize', function(){
1383 var pos = this.nodes.select.getXY();
1384 pos[0] += 1;
1385 this.nodes.menu.setXY(pos);
1386 }, this);
572dd8ec
SH
1387 },
1388 generate_menu_content : function() {
1389 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1390 content += this.generate_submenu_content(this.structure[0], true);
1391 content += '</ul></div>';
1392 return content;
1393 },
1394 generate_submenu_content : function(item, rootelement) {
1395 this.submenucount++;
1396 var content = '';
1397 if (item.children.length > 0) {
1398 if (rootelement) {
1399 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1400 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1401 content += '<div class="smartselect_menu_content">';
1402 } else {
1403 content += '<li class="smartselect_submenuitem">';
1404 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1405 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1406 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1407 content += '<div class="smartselect_submenu_content">';
1408 }
1409 content += '<ul>';
1410 for (var i in item.children) {
1411 content += this.generate_submenu_content(item.children[i],false);
1412 }
1413 content += '</ul>';
1414 content += '</div>';
1415 content += '</div>';
1416 if (rootelement) {
1417 } else {
1418 content += '</li>';
1419 }
1420 } else {
1421 content += '<li class="smartselect_menuitem">';
1422 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1423 content += '</li>';
1424 }
1425 return content;
1426 },
1427 select : function(e) {
1428 var t = e.target;
1429 e.halt();
1430 this.currenttext = t.get('innerHTML');
1431 this.currentvalue = t.getAttribute('value');
1432 this.nodes.select.set('selectedIndex', this.currentvalue);
1433 this.hide_menu();
1434 },
1435 handle_click : function(e) {
1436 var target = e.target;
1437 if (target.hasClass('smartselect_mask')) {
1438 this.show_menu(e);
1439 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1440 this.select(e);
1441 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1442 this.show_sub_menu(e);
1443 }
1444 },
1445 show_menu : function(e) {
1446 e.halt();
1447 var menu = e.target.ancestor().one('.smartselect_menu');
1448 menu.addClass('visible');
1449 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1450 },
1451 show_sub_menu : function(e) {
1452 e.halt();
1453 var target = e.target;
1454 if (!target.hasClass('smartselect_submenuitem')) {
1455 target = target.ancestor('.smartselect_submenuitem');
1456 }
1457 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1458 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1459 return;
1460 }
1461 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1462 target.one('.smartselect_submenu').addClass('visible');
1463 },
1464 hide_menu : function() {
1465 this.nodes.menu.all('.visible').removeClass('visible');
1466 if (this.shownevent) {
1467 this.shownevent.detach();
1468 }
1469 }
667c0ae1 1470 };
572dd8ec
SH
1471 smartselect.init(Y, id, options, {select:select});
1472 });
667c0ae1 1473};
3a42ad12
RW
1474
1475M.util.init_flvflowplayer = function (id, playerpath, fileurl) {
1476 $f(id, playerpath, {
1477 plugins: {
1478 controls: {
1479 autoHide: true
1480 }
1481 },
1482 clip: {
1483 url: fileurl,
1484 autoPlay: false,
1485 autoBuffering: true
1486 }
1487 });
667c0ae1 1488};
3a42ad12
RW
1489
1490M.util.init_mp3flowplayer = function (id, playerpath, audioplayerpath, fileurl, color) {
1491
1492 $f(id, playerpath, {
1493
1494 plugins: {
1495 controls: {
1496 fullscreen: false,
1497 height: 25,
1498 autoHide: false,
1499 background: '#'+color['bgColour'],
1500 buttonColor: '#'+color['btnColour'],
1501 sliderColor: '#'+color['handleColour'],
1502 volumeSliderColor: '#'+color['handleColour'],
1503 volumeColor: '#'+color['trackColour'],
1504 durationColor: '#'+color['fontColour'],
1505 buttonOverColor: '#'+color['iconOverColour'],
1506 progressColor: '#'+color['handleColour']
1507 },
1508 audio: {url: audioplayerpath}
1509 },
4461a18e 1510 clip: {url: fileurl,
3a42ad12
RW
1511 provider: "audio",
1512 autoPlay: false
1513 }
1514 });
667c0ae1 1515};
3a42ad12
RW
1516
1517M.util.init_mp3flowplayerplugin = function (id, playerpath, audioplayerpath, fileurl, color) {
1518 $f(id, playerpath, {
1519 plugins: {
1520 controls: {
1521 all: false,
1522 play: true,
1523 pause: true,
1524 scrubber: true,
1525 autoHide: false,
1526 height: 15,
1527 background: '#'+color['bgColour'],
1528 buttonColor: '#'+color['btnColour'],
1529 sliderColor: '#'+color['handleColour'],
1530 volumeSliderColor: '#'+color['handleColour'],
1531 progressColor: '#'+color['handleColour'],
1532 volumeColor: '#'+color['trackColour'],
1533 buttonOverColor: '#'+color['iconOverColour']
1534 },
1535 audio: {url: audioplayerpath}
1536 },
4461a18e 1537 clip: {url: fileurl,
3a42ad12
RW
1538 provider: "audio",
1539 autoPlay: false
1540 }
1541 });
18097238 1542};