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