yui MDL-24146 Removed 3.1.1
[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);
51}
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;
60}
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 });
288}
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');
312 var newwidth = get_htmlelement_size('content', 'width') - 15;
313
314 if (newwidth > 600) {
315 obj.setStyle('width', newwidth + 'px');
316 } else {
317 obj.setStyle('width', '600px');
318 }
319 var pageheight = get_htmlelement_size('page', 'height');
320 var objheight = get_htmlelement_size(obj, 'height');
321 var newheight = objheight + parseInt(obj.get('winHeight')) - pageheight - 30;
322 if (newheight > 400) {
323 obj.setStyle('height', newheight + 'px');
324 } else {
325 obj.setStyle('height', '400px');
326 }
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 }
362 }
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();
414 if (e.target.get('nodeName')!='A' && e.target.get('nodeName')!='IMG') {
415 this.toggleClass(toggleclassname);
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);
428}
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 }
588 }
589 /**
590 * Initialise the colour picker :) Hoorah
591 */
592 colourpicker.init();
593 });
594}
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);
601 }
602 blockhider.prototype = {
603 initializer : function(config) {
604 this.set('block', '#'+this.get('id'))
605 var b = this.get('block'),
606 t = b.one('.title'),
607 a = null;
608 if (t && (a = t.one('.block_action'))) {
609 var hide = Y.Node.create('<img class="block-hider-hide" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />')
610 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
611 var show = Y.Node.create('<img class="block-hider-show" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />')
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 }
624 }
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 });
647}
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 }
36282d85 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();
977
978 return false;
77241d9b 979}
740939ec 980
981/* This is only used on a few help pages. */
982emoticons_help = {
983 inputarea: null,
984
985 init: function(formname, fieldname, listid) {
986 if (!opener || !opener.document.forms[formname]) {
987 return;
988 }
989 emoticons_help.inputarea = opener.document.forms[formname][fieldname];
990 if (!emoticons_help.inputarea) {
991 return;
992 }
993 var emoticons = document.getElementById(listid).getElementsByTagName('li');
994 for (var i = 0; i < emoticons.length; i++) {
995 var text = emoticons[i].getElementsByTagName('img')[0].alt;
996 YAHOO.util.Event.addListener(emoticons[i], 'click', emoticons_help.inserttext, text);
997 }
998 },
999
1000 inserttext: function(e, text) {
1001 text = ' ' + text + ' ';
1002 if (emoticons_help.inputarea.createTextRange && emoticons_help.inputarea.caretPos) {
1003 var caretPos = emoticons_help.inputarea.caretPos;
1004 caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
1005 } else {
1006 emoticons_help.inputarea.value += text;
1007 }
1008 emoticons_help.inputarea.focus();
1009 }
bd1884fe 1010}
1011
b166403f 1012/** Close the current browser window. */
7a5c78e0 1013function close_window(e) {
c7e3e61c
SH
1014 if (e.preventDefault) {
1015 e.preventDefault();
1016 } else {
1017 e.returnValue = false;
1018 }
7a5c78e0 1019 self.close();
b166403f 1020}
1021
1022/**
1023 * Close the current browser window, forcing the window/tab that opened this
1024 * popup to reload itself. */
1025function close_window_reloading_opener() {
1026 if (window.opener) {
1027 window.opener.location.reload(1);
1028 close_window();
1029 // Intentionally, only try to close the window if there is some evidence we are in a popup.
1030 }
8e7cebb0 1031}
9f319372 1032
1033/**
1034 * Used in a couple of modules to hide navigation areas when using AJAX
1035 */
34a2777c 1036
2538037f 1037function show_item(itemid) {
1038 var item = document.getElementById(itemid);
1039 if (item) {
1040 item.style.display = "";
1041 }
1042}
1043
1044function destroy_item(itemid) {
1045 var item = document.getElementById(itemid);
1046 if (item) {
1047 item.parentNode.removeChild(item);
1048 }
1049}
34a2777c 1050/**
1051 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1052 * @param controlid the control id.
1053 */
1054function focuscontrol(controlid) {
1055 var control = document.getElementById(controlid);
1056 if (control) {
1057 control.focus();
1058 }
e29380f3 1059}
1060
e11a8328 1061/**
1062 * Transfers keyboard focus to an HTML element based on the old style style of focus
1063 * This function should be removed as soon as it is no longer used
1064 */
428acddb 1065function old_onload_focus(formid, controlname) {
5f8bce50 1066 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
428acddb 1067 document.forms[formid].elements[controlname].focus();
e11a8328 1068 }
1069}
1070
1bcb7eb5 1071function build_querystring(obj) {
aa281068
AD
1072 return convert_object_to_string(obj, '&');
1073}
1074
1075function build_windowoptionsstring(obj) {
1076 return convert_object_to_string(obj, ',');
1077}
1078
1079function convert_object_to_string(obj, separator) {
1bcb7eb5 1080 if (typeof obj !== 'object') {
1081 return null;
1082 }
1083 var list = [];
1084 for(var k in obj) {
1085 k = encodeURIComponent(k);
1086 var value = obj[k];
1087 if(obj[k] instanceof Array) {
1088 for(var i in value) {
1089 list.push(k+'[]='+encodeURIComponent(value[i]));
1090 }
1091 } else {
1092 list.push(k+'='+encodeURIComponent(value));
1093 }
1094 }
aa281068 1095 return list.join(separator);
1bcb7eb5 1096}
d25e2ca3 1097
1098function stripHTML(str) {
1099 var re = /<\S[^><]*>/g;
1100 var ret = str.replace(re, "");
1101 return ret;
1102}
1103
fd4faf98 1104Number.prototype.fixed=function(n){
1105 with(Math)
1106 return round(Number(this)*pow(10,n))/pow(10,n);
1107}
1108function update_progress_bar (id, width, pt, msg, es){
1109 var percent = pt*100;
1110 var status = document.getElementById("status_"+id);
1111 var percent_indicator = document.getElementById("pt_"+id);
1112 var progress_bar = document.getElementById("progress_"+id);
1113 var time_es = document.getElementById("time_"+id);
1114 status.innerHTML = msg;
1115 percent_indicator.innerHTML = percent.fixed(2) + '%';
1116 if(percent == 100) {
1117 progress_bar.style.background = "green";
1118 time_es.style.display = "none";
1119 } else {
1120 progress_bar.style.background = "#FFCC66";
1121 if (es == Infinity){
1122 time_es.innerHTML = "Initializing...";
1123 }else {
1124 time_es.innerHTML = es.fixed(2)+" sec";
1125 time_es.style.display
1126 = "block";
1127 }
1128 }
1129 progress_bar.style.width = width + "px";
1130
1131}
bf6c37c7 1132
1133function frame_breakout(e, properties) {
1134 this.setAttribute('target', properties.framename);
41f23791 1135}
78946b9b 1136
73b62703
PS
1137
1138// ===== Deprecated core Javascript functions for Moodle ====
1139// DO NOT USE!!!!!!!
e50b4c89 1140// Do not put this stuff in separate file because it only adds extra load on servers!
73b62703 1141
2d65597b
PS
1142/**
1143 * Used in a couple of modules to hide navigation areas when using AJAX
1144 */
1145function hide_item(itemid) {
3b044ba3 1146 // use class='hiddenifjs' instead
2d65597b
PS
1147 var item = document.getElementById(itemid);
1148 if (item) {
1149 item.style.display = "none";
1150 }
1151}
36282d85 1152
2cf81209
SH
1153M.util.help_icon = {
1154 Y : null,
36282d85 1155 instance : null,
2cf81209
SH
1156 add : function(Y, properties) {
1157 this.Y = Y;
61ddb953
RW
1158 properties.node = Y.one('#'+properties.id);
1159 if (properties.node) {
1160 properties.node.on('click', this.display, this, properties);
f855a5d2 1161 }
2cf81209
SH
1162 },
1163 display : function(event, args) {
1164 event.preventDefault();
1165 if (M.util.help_icon.instance === null) {
1166 var Y = M.util.help_icon.Y;
61ddb953 1167 Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
2cf81209 1168 var help_content_overlay = {
61ddb953 1169 helplink : null,
2cf81209
SH
1170 overlay : null,
1171 init : function() {
1172
61ddb953 1173 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
2cf81209
SH
1174 // Create an overlay from markup
1175 this.overlay = new Y.Overlay({
1176 headerContent: closebtn,
1177 bodyContent: '',
61ddb953
RW
1178 id: 'helppopupbox',
1179 width:'400px',
2cf81209 1180 visible : false,
61ddb953 1181 constrain : true
2cf81209
SH
1182 });
1183 this.overlay.render(Y.one(document.body));
1184
1185 closebtn.on('click', this.overlay.hide, this.overlay);
61ddb953 1186
2cf81209 1187 var boundingBox = this.overlay.get("boundingBox");
2cf81209
SH
1188
1189 // Hide the menu if the user clicks outside of its content
1190 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1191 var oTarget = event.target;
1192 var menuButton = Y.one("#"+args.id);
61ddb953 1193
2cf81209
SH
1194 if (!oTarget.compareTo(menuButton) &&
1195 !menuButton.contains(oTarget) &&
1196 !oTarget.compareTo(boundingBox) &&
1197 !boundingBox.contains(oTarget)) {
1198 this.overlay.hide();
1199 }
1200 }, this);
61ddb953
RW
1201
1202 Y.on("key", this.close, closebtn , "down:13", this);
1203 closebtn.on('click', this.close, this);
1204 },
1205
1206 close : function(e) {
1207 e.preventDefault();
1208 this.helplink.focus();
1209 this.overlay.hide();
2cf81209
SH
1210 },
1211
1212 display : function(event, args) {
61ddb953 1213 this.helplink = args.node;
2cf81209 1214 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
61ddb953 1215 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
2cf81209
SH
1216
1217 var fullurl = args.url;
1218 if (!args.url.match(/https?:\/\//)) {
1219 fullurl = M.cfg.wwwroot + args.url;
36282d85 1220 }
36282d85 1221
2cf81209
SH
1222 var ajaxurl = fullurl + '&ajax=1';
1223
1224 var cfg = {
1225 method: 'get',
1226 context : this,
1227 on: {
1228 success: function(id, o, node) {
1229 this.display_callback(o.responseText);
1230 },
1231 failure: function(id, o, node) {
1232 var debuginfo = o.statusText;
1233 if (M.cfg.developerdebug) {
1234 o.statusText += ' (' + ajaxurl + ')';
1235 }
1236 this.display_callback('bodyContent',debuginfo);
1237 }
1238 }
1239 };
36282d85 1240
2cf81209
SH
1241 Y.io(ajaxurl, cfg);
1242 this.overlay.show();
61ddb953
RW
1243
1244 Y.one('#closehelpbox').focus();
2cf81209 1245 },
36282d85 1246
2cf81209 1247 display_callback : function(content) {
61ddb953 1248 this.overlay.set('bodyContent', content);
2cf81209
SH
1249 },
1250
1251 hideContent : function() {
1252 help = this;
1253 help.overlay.hide();
36282d85 1254 }
2cf81209
SH
1255 }
1256 help_content_overlay.init();
1257 M.util.help_icon.instance = help_content_overlay;
1258 M.util.help_icon.instance.display(event, args);
1259 });
1260 } else {
1261 M.util.help_icon.instance.display(event, args);
1262 }
1263 },
1264 init : function(Y) {
61ddb953 1265 this.Y = Y;
36282d85
RW
1266 }
1267}
d2dbd0c0
SH
1268
1269/**
1270 * Custom menu namespace
1271 */
1272M.core_custom_menu = {
1273 /**
1274 * This method is used to initialise a custom menu given the id that belongs
1275 * to the custom menu's root node.
1276 *
1277 * @param {YUI} Y
1278 * @param {string} nodeid
1279 */
1280 init : function(Y, nodeid) {
242b78f7
SH
1281 var node = Y.one('#'+nodeid);
1282 if (node) {
1283 Y.use('node-menunav', function(Y) {
1284 // Get the node
1285 // Remove the javascript-disabled class.... obviously javascript is enabled.
1286 node.removeClass('javascript-disabled');
1287 // Initialise the menunav plugin
1288 node.plug(Y.Plugin.NodeMenuNav);
1289 });
1290 }
d2dbd0c0 1291 }
572dd8ec
SH
1292}
1293
1294/**
1295 * Used to store form manipulation methods and enhancments
1296 */
1297M.form = M.form || {};
1298
1299/**
1300 * Converts a nbsp indented select box into a multi drop down custom control much
1301 * like the custom menu. It also selectable categories on or off.
1302 *
1303 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1304 *
1305 * @param {YUI} Y
1306 * @param {string} id
1307 * @param {Array} options
1308 */
1309M.form.init_smartselect = function(Y, id, options) {
882e66fe
SH
1310 if (!id.match(/^id_/)) {
1311 id = 'id_'+id;
1312 }
1313 var select = Y.one('select#'+id);
572dd8ec
SH
1314 if (!select) {
1315 return false;
1316 }
1317 Y.use('event-delegate',function(){
1318 var smartselect = {
1319 id : id,
1320 structure : [],
1321 options : [],
1322 submenucount : 0,
1323 currentvalue : null,
1324 currenttext : null,
1325 shownevent : null,
1326 cfg : {
1327 selectablecategories : true,
1328 mode : null
1329 },
1330 nodes : {
1331 select : null,
1332 loading : null,
1333 menu : null
1334 },
1335 init : function(Y, id, args, nodes) {
1336 if (typeof(args)=='object') {
1337 for (var i in this.cfg) {
1338 if (args[i] || args[i]===false) {
1339 this.cfg[i] = args[i];
1340 }
1341 }
1342 }
1343
1344 // Display a loading message first up
1345 this.nodes.select = nodes.select;
1346
1347 this.currentvalue = this.nodes.select.get('selectedIndex');
1348 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1349
1350 var options = Array();
1351 options[''] = {text:this.currenttext,value:'',depth:0,children:[]}
1352 this.nodes.select.all('option').each(function(option, index) {
1353 var rawtext = option.get('innerHTML');
1354 var text = rawtext.replace(/^(&nbsp;)*/, '');
1355 if (rawtext === text) {
1356 text = rawtext.replace(/^(\s)*/, '');
1357 var depth = (rawtext.length - text.length ) + 1;
1358 } else {
1359 var depth = ((rawtext.length - text.length )/12)+1;
1360 }
1361 option.set('innerHTML', text);
1362 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1363 }, this);
1364
1365 this.structure = [];
1366 var structcount = 0;
1367 for (var i in options) {
1368 var o = options[i];
1369 if (o.depth == 0) {
1370 this.structure.push(o);
1371 structcount++;
1372 } else {
1373 var d = o.depth;
1374 var current = this.structure[structcount-1];
1375 for (var j = 0; j < o.depth-1;j++) {
1376 if (current && current.children) {
1377 current = current.children[current.children.length-1];
1378 }
1379 }
1380 if (current && current.children) {
1381 current.children.push(o);
1382 }
1383 }
1384 }
1385
1386 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1387 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1388 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1389 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1390
1391 if (this.cfg.mode == null) {
1392 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1393 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1394 this.cfg.mode = 'compact';
1395 } else {
1396 this.cfg.mode = 'spanning';
1397 }
1398 }
1399
1400 if (this.cfg.mode == 'compact') {
1401 this.nodes.menu.addClass('compactmenu');
1402 } else {
1403 this.nodes.menu.addClass('spanningmenu');
1404 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1405 }
1406
1407 Y.one(document.body).append(this.nodes.menu);
1408 var pos = this.nodes.select.getXY();
1409 pos[0] += 1;
1410 this.nodes.menu.setXY(pos);
1411 this.nodes.menu.on('click', this.handle_click, this);
32690ac2
SH
1412
1413 Y.one(window).on('resize', function(){
1414 var pos = this.nodes.select.getXY();
1415 pos[0] += 1;
1416 this.nodes.menu.setXY(pos);
1417 }, this);
572dd8ec
SH
1418 },
1419 generate_menu_content : function() {
1420 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1421 content += this.generate_submenu_content(this.structure[0], true);
1422 content += '</ul></div>';
1423 return content;
1424 },
1425 generate_submenu_content : function(item, rootelement) {
1426 this.submenucount++;
1427 var content = '';
1428 if (item.children.length > 0) {
1429 if (rootelement) {
1430 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1431 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1432 content += '<div class="smartselect_menu_content">';
1433 } else {
1434 content += '<li class="smartselect_submenuitem">';
1435 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1436 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1437 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1438 content += '<div class="smartselect_submenu_content">';
1439 }
1440 content += '<ul>';
1441 for (var i in item.children) {
1442 content += this.generate_submenu_content(item.children[i],false);
1443 }
1444 content += '</ul>';
1445 content += '</div>';
1446 content += '</div>';
1447 if (rootelement) {
1448 } else {
1449 content += '</li>';
1450 }
1451 } else {
1452 content += '<li class="smartselect_menuitem">';
1453 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1454 content += '</li>';
1455 }
1456 return content;
1457 },
1458 select : function(e) {
1459 var t = e.target;
1460 e.halt();
1461 this.currenttext = t.get('innerHTML');
1462 this.currentvalue = t.getAttribute('value');
1463 this.nodes.select.set('selectedIndex', this.currentvalue);
1464 this.hide_menu();
1465 },
1466 handle_click : function(e) {
1467 var target = e.target;
1468 if (target.hasClass('smartselect_mask')) {
1469 this.show_menu(e);
1470 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1471 this.select(e);
1472 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1473 this.show_sub_menu(e);
1474 }
1475 },
1476 show_menu : function(e) {
1477 e.halt();
1478 var menu = e.target.ancestor().one('.smartselect_menu');
1479 menu.addClass('visible');
1480 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1481 },
1482 show_sub_menu : function(e) {
1483 e.halt();
1484 var target = e.target;
1485 if (!target.hasClass('smartselect_submenuitem')) {
1486 target = target.ancestor('.smartselect_submenuitem');
1487 }
1488 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1489 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1490 return;
1491 }
1492 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1493 target.one('.smartselect_submenu').addClass('visible');
1494 },
1495 hide_menu : function() {
1496 this.nodes.menu.all('.visible').removeClass('visible');
1497 if (this.shownevent) {
1498 this.shownevent.detach();
1499 }
1500 }
1501 }
1502 smartselect.init(Y, id, options, {select:select});
1503 });
d2dbd0c0 1504}