MDL-21577 converting select auto submit code
[moodle.git] / lib / javascript-static.js
1 // Miscellaneous core Javascript functions for Moodle
2 // Global M object is initilised in inline javascript
4 /**
5  * Add module to list of available modules that can be laoded from YUI.
6  * @param {Array} modules
7  */
8 M.yui.add_module = function(modules) {
9     for (var modname in modules) {
10         M.yui.loader.modules[modname] = modules[modname];
11     }
12 };
15 /**
16  * Various utility functions
17  */
18 M.util = {};
20 /**
21  * Language strings - initialised from page footer.
22  */
23 M.str = {};
25 /**
26  * Returns url for images.
27  * @param {String} imagename
28  * @param {String} component
29  * @return {String}
30  */
31 M.util.image_url = function(imagename, component) {
32     var url = M.cfg.wwwroot + '/theme/image.php?theme=' + M.cfg.theme + '&image=' + imagename;
34     if (M.cfg.themerev > 0) {
35         url = url + '&rev=' + M.cfg.themerev;
36     }
38     if (component && component != '' && component != 'moodle' && component != 'core') {
39         url = url + '&component=' + component;
40     }
42     return url;
43 };
45 M.util.create_UFO_object = function (eid, FO) {
46     UFO.create(FO, eid);
47 }
49 /**
50  * Init a collapsible region, see print_collapsible_region in weblib.php
51  * @param {YUI} Y YUI3 instance with all libraries loaded
52  * @param {String} id the HTML id for the div.
53  * @param {String} userpref the user preference that records the state of this box. false if none.
54  * @param {String} strtooltip
55  */
56 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
57     Y.use('anim', function(Y) {
58         new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
59     });
60 };
62 /**
63  * Object to handle a collapsible region : instantiate and forget styled object
64  *
65  * @class
66  * @constructor
67  * @param {YUI} Y YUI3 instance with all libraries loaded
68  * @param {String} id The HTML id for the div.
69  * @param {String} userpref The user preference that records the state of this box. false if none.
70  * @param {String} strtooltip
71  */
72 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
73     // Record the pref name
74     this.userpref = userpref;
76     // Find the divs in the document.
77     this.div = Y.one('#'+id);
79     // Get the caption for the collapsible region
80     var caption = this.div.one('#'+id + '_caption');
81     caption.setAttribute('title', strtooltip);
83     // Create a link
84     var a = Y.Node.create('<a href="#"></a>');
85     // Create a local scoped lamba function to move nodes to a new link
86     var movenode = function(node){
87         node.remove();
88         a.append(node);
89     };
90     // Apply the lamba function on each of the captions child nodes
91     caption.get('children').each(movenode, this);
92     caption.append(a);
94     // Get the height of the div at this point before we shrink it if required
95     var height = this.div.get('offsetHeight');
96     if (this.div.hasClass('collapsed')) {
97         // Add the correct image and record the YUI node created in the process
98         this.icon = Y.Node.create('<img src="'+M.util.image_url('t/collapsed', 'moodle')+'" alt="" />');
99         // Shrink the div as it is collapsed by default
100         this.div.setStyle('height', caption.get('offsetHeight')+'px');
101     } else {
102         // Add the correct image and record the YUI node created in the process
103         this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
104     }
105     a.append(this.icon);
107     // Create the animation.
108     var animation = new Y.Anim({
109         node: this.div,
110         duration: 0.3,
111         easing: Y.Easing.easeBoth,
112         to: {height:caption.get('offsetHeight')},
113         from: {height:height}
114     });
116     // Handler for the animation finishing.
117     animation.on('end', function() {
118         this.div.toggleClass('collapsed');
119         if (this.div.hasClass('collapsed')) {
120             this.icon.set('src', M.util.image_url('t/collapsed', 'moodle'));
121         } else {
122             this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
123         }
124     }, this);
126     // Hook up the event handler.
127     a.on('click', function(e, animation) {
128         e.preventDefault();
129         // Animate to the appropriate size.
130         if (animation.get('running')) {
131             animation.stop();
132         }
133         animation.set('reverse', this.div.hasClass('collapsed'));
134         // Update the user preference.
135         if (this.userpref) {
136             M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
137         }
138         animation.run();
139     }, this, animation);
140 };
142 /**
143  * The user preference that stores the state of this box.
144  * @property userpref
145  * @type String
146  */
147 M.util.CollapsibleRegion.prototype.userpref = null;
149 /**
150  * The key divs that make up this
151  * @property div
152  * @type Y.Node
153  */
154 M.util.CollapsibleRegion.prototype.div = null;
156 /**
157  * The key divs that make up this
158  * @property icon
159  * @type Y.Node
160  */
161 M.util.CollapsibleRegion.prototype.icon = null;
163 /**
164  * Finds all help icons on the page and initiates YUI tooltips for
165  * each of them, which load a truncated version of the help's content
166  * on-the-fly asynchronously
167  */
168 M.util.init_help_icons = function(Y) {
169     Y.use('io', 'yui2-dom', 'yui2-container', function(Y) {
170         // remove all titles, they would obscure the YUI tooltip
171         Y.all('span.helplink a').each(function(node) {
172             node.set('title', '');
173         });
175         var iconspans = YAHOO.util.Dom.getElementsByClassName('helplink', 'span');
176         var tooltip = new YAHOO.widget.Tooltip('help_icon_tooltip', {
177             context: iconspans,
178             showdelay: 1000,
179             hidedelay: 150,
180             autodismissdelay: 50000,
181             underlay: 'none',
182             zIndex: '1000'
183         });
185         tooltip.contextTriggerEvent.subscribe(
186             function(type, args) {
187                 // Fetch help page contents asynchronously
188                 // Load spinner icon while content is loading
189                 var spinner = document.createElement('img');
190                 spinner.src = M.cfg.loadingicon;
192                 this.cfg.setProperty('text', spinner);
194                 var context = args[0];
196                 var link = context.getElementsByTagName('a')[0];
197                 var thistooltip = this;
198                 var ajaxurl = link.href + '&fortooltip=1';
201                 var cfg = {
202                     method: 'get',
203                     on: {
204                         success: function(id, o, args) {
205                             thistooltip.cfg.setProperty('text', o.responseText);
206                         },
207                         failure: function(id, o, args) {
208                             var debuginfo = o.statusText;
209                             if (M.cfg.developerdebug) {
210                                 o.statusText += ' (' + ajaxurl + ')';
211                             }
212                             thistooltip.cfg.setProperty('text', debuginfo);
213                         }
214                     }
215                 };
217                 var conn = Y.io(ajaxurl, cfg);
218             }
219         );
220     });
221 };
224 /**
225  * Makes a best effort to connect back to Moodle to update a user preference,
226  * however, there is no mechanism for finding out if the update succeeded.
227  *
228  * Before you can use this function in your JavsScript, you must have called
229  * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
230  * the udpate is allowed, and how to safely clean and submitted values.
231  *
232  * @param String name the name of the setting to udpate.
233  * @param String the value to set it to.
234  */
235 M.util.set_user_preference = function(name, value) {
236     YUI(M.yui.loader).use('io', function(Y) {
237         var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
238                 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
240         // If we are a developer, ensure that failures are reported.
241         var cfg = {
242                 method: 'get',
243                 on: {}
244             };
245         if (M.cfg.developerdebug) {
246             cfg.on.failure = function(id, o, args) {
247                 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
248             }
249         }
251         // Make the request.
252         Y.io(url, cfg);
253     });
254 };
256 /**
257  * Prints a confirmation dialog in the style of DOM.confirm().
258  * @param object event A YUI DOM event or null if launched manually
259  * @param string message The message to show in the dialog
260  * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
261  * @param function fn A JS function to run if YES is clicked.
262  */
263 M.util.show_confirm_dialog = function(e, args) {
264     var target = e.target;
265     if (e.preventDefault) {
266         e.preventDefault();
267     }
269     YUI(M.yui.loader).use('yui2-container', 'yui2-event', function(Y) {
270         var simpledialog = new YAHOO.widget.SimpleDialog('confirmdialog',
271             { width: '300px',
272               fixedcenter: true,
273               modal: true,
274               visible: false,
275               draggable: false
276             }
277         );
279         simpledialog.setHeader(M.str.admin.confirmation);
280         simpledialog.setBody(args.message);
281         simpledialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
283         var handle_cancel = function() {
284             simpledialog.hide();
285         };
287         var handle_yes = function() {
288             simpledialog.hide();
290             if (args.callback) {
291                 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
292                 var callback = null;
293                 if (Y.Lang.isFunction(args.callback)) {
294                     callback = args.callback;
295                 } else {
296                     callback = eval('('+args.callback+')');
297                 }
299                 if (Y.Lang.isObject(args.scope)) {
300                     var sc = args.scope;
301                 } else {
302                     var sc = e.target;
303                 }
305                 if (args.callbackargs) {
306                     callback.apply(sc, args.callbackargs);
307                 } else {
308                     callback.apply(sc);
309                 }
310                 return;
311             }
313             if (target.get('tagName').toLowerCase() == 'a') {
314                 window.location = target.get('href');
315             } else if (target.get('tagName').toLowerCase() == 'input') {
316                 var parentelement = target.get('parentNode');
317                 while (parentelement.get('tagName').toLowerCase() != 'form' && parentelement.get('tagName').toLowerCase() != 'body') {
318                     parentelement = parentelement.get('parentNode');
319                 }
320                 if (parentelement.get('tagName').toLowerCase() == 'form') {
321                     parentelement.submit();
322                 }
323             } else if (M.cfg.developerdebug) {
324                 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A or INPUT");
325             }
326         };
328         var buttons = [ { text: M.str.moodle.cancel, handler: handle_cancel, isDefault: true },
329                         { text: M.str.moodle.yes, handler: handle_yes } ];
331         simpledialog.cfg.queueProperty('buttons', buttons);
333         simpledialog.render(document.body);
334         simpledialog.show();
335     });
338 /** Useful for full embedding of various stuff */
339 M.util.init_maximised_embed = function(Y, id) {
340     var obj = Y.one('#'+id);
341     if (!obj) {
342         return;
343     }
346     var get_htmlelement_size = function(el, prop) {
347         if (Y.Lang.isString(el)) {
348             el = Y.one('#' + el);
349         }
350         var val = el.getStyle(prop);
351         if (val == 'auto') {
352             val = el.getComputedStyle(prop);
353         }
354         return parseInt(val);
355     };
357     var resize_object = function() {
358         obj.setStyle('width', '0px');
359         obj.setStyle('height', '0px');
360         var newwidth = get_htmlelement_size('content', 'width') - 15;
362         if (newwidth > 600) {
363             obj.setStyle('width', newwidth  + 'px');
364         } else {
365             obj.setStyle('width', '600px');
366         }
367         var pageheight = get_htmlelement_size('page', 'height');
368         var objheight = get_htmlelement_size(obj, 'height');
369         var newheight = objheight + parseInt(obj.get('winHeight')) - pageheight - 30;
370         if (newheight > 400) {
371             obj.setStyle('height', newheight + 'px');
372         } else {
373             obj.setStyle('height', '400px');
374         }
375     };
377     resize_object();
378     // fix layout if window resized too
379     window.onresize = function() {
380         resize_object();
381     };
382 };
384 /**
385  * Attach handler to single_select
386  */
387 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
388         YUI(M.yui.loader).use('node', function(Y) {
389                 Y.on('change', function() {
390                 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
391                         Y.one('#'+formid).submit();
392                 }
393                 },
394                 '#'+selectid);
395         });
396 };
398 /**
399  * Attach handler to url_select
400  */
401 M.util.init_url_select = function(Y, formid, selectid, nothing) {
402         YUI(M.yui.loader).use('node', function(Y) {
403                 Y.on('change', function() {
404                 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
405                         window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
406                 }
407                 },
408                 '#'+selectid);
409         });
410 };
412 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
414 function popupchecker(msg) {
415     var testwindow = window.open('', '', 'width=1,height=1,left=0,top=0,scrollbars=no');
416     if (!testwindow) {
417         alert(msg);
418     } else {
419         testwindow.close();
420     }
423 function checkall() {
424     var inputs = document.getElementsByTagName('input');
425     for (var i = 0; i < inputs.length; i++) {
426         if (inputs[i].type == 'checkbox') {
427             inputs[i].checked = true;
428         }
429     }
432 function checknone() {
433     var inputs = document.getElementsByTagName('input');
434     for (var i = 0; i < inputs.length; i++) {
435         if (inputs[i].type == 'checkbox') {
436             inputs[i].checked = false;
437         }
438     }
441 function lockoptions(formid, master, subitems) {
442   // Subitems is an array of names of sub items.
443   // Optionally, each item in subitems may have a
444   // companion hidden item in the form with the
445   // same name but prefixed by "h".
446   var form = document.forms[formid];
448   if (eval("form."+master+".checked")) {
449     for (i=0; i<subitems.length; i++) {
450       unlockoption(form, subitems[i]);
451     }
452   } else {
453     for (i=0; i<subitems.length; i++) {
454       lockoption(form, subitems[i]);
455     }
456   }
457   return(true);
460 function lockoption(form,item) {
461   eval("form."+item+".disabled=true");/* IE thing */
462   if(form.elements['h'+item]) {
463     eval("form.h"+item+".value=1");
464   }
467 function unlockoption(form,item) {
468   eval("form."+item+".disabled=false");/* IE thing */
469   if(form.elements['h'+item]) {
470     eval("form.h"+item+".value=0");
471   }
474 /**
475  * Get the value of the 'virtual form element' with a particular name. That is,
476  * abstracts away the difference between a normal form element, like a select
477  * which is a single HTML element with a .value property, and a set of radio
478  * buttons, which is several HTML elements.
479  *
480  * @param form a HTML form.
481  * @param master the name of an element in that form.
482  * @return the value of that element.
483  */
484 function get_form_element_value(form, name) {
485     var element = form[name];
486     if (!element) {
487         return null;
488     }
489     if (element.tagName) {
490         // Ordinarly thing like a select box.
491         return element.value;
492     }
493     // Array of things, like radio buttons.
494     for (var j = 0; j < element.length; j++) {
495         var el = element[j];
496         if (el.checked) {
497             return el.value;
498         }
499     }
500     return null;
504 /**
505  * Set the disabled state of the 'virtual form element' with a particular name.
506  * This abstracts away the difference between a normal form element, like a select
507  * which is a single HTML element with a .value property, and a set of radio
508  * buttons, which is several HTML elements.
509  *
510  * @param form a HTML form.
511  * @param master the name of an element in that form.
512  * @param disabled the disabled state to set.
513  */
514 function set_form_element_disabled(form, name, disabled) {
515     var element = form[name];
516     if (!element) {
517         return;
518     }
519     if (element.tagName) {
520         // Ordinarly thing like a select box.
521         element.disabled = disabled;
522     }
523     // Array of things, like radio buttons.
524     for (var j = 0; j < element.length; j++) {
525         var el = element[j];
526         el.disabled = disabled;
527     }
530 /**
531  * Set the hidden state of the 'virtual form element' with a particular name.
532  * This abstracts away the difference between a normal form element, like a select
533  * which is a single HTML element with a .value property, and a set of radio
534  * buttons, which is several HTML elements.
535  *
536  * @param form a HTML form.
537  * @param master the name of an element in that form.
538  * @param hidden the hidden state to set.
539  */
540 function set_form_element_hidden(form, name, hidden) {
541     var element = form[name];
542     if (!element) {
543         return;
544     }
545     if (element.tagName) {
546         var el = findParentNode(element, 'DIV', 'fitem', false);
547         if (el!=null) {
548             el.style.display = hidden ? 'none' : '';
549             el.style.visibility = hidden ? 'hidden' : '';
550         }
551     }
552     // Array of things, like radio buttons.
553     for (var j = 0; j < element.length; j++) {
554         var el = findParentNode(element[j], 'DIV', 'fitem', false);
555         if (el!=null) {
556             el.style.display = hidden ? 'none' : '';
557             el.style.visibility = hidden ? 'hidden' : '';
558         }
559     }
562 function lockoptionsall(formid) {
563     var form = document.forms[formid];
564     var dependons = eval(formid + 'items');
565     var tolock = [];
566     var tohide = [];
567     for (var dependon in dependons) {
568         // change for MooTools compatibility
569         if (!dependons.propertyIsEnumerable(dependon)) {
570             continue;
571         }
572         if (!form[dependon]) {
573             continue;
574         }
575         for (var condition in dependons[dependon]) {
576             for (var value in dependons[dependon][condition]) {
577                 var lock;
578                 var hide = false;
579                 switch (condition) {
580                   case 'notchecked':
581                       lock = !form[dependon].checked; break;
582                   case 'checked':
583                       lock = form[dependon].checked; break;
584                   case 'noitemselected':
585                       lock = form[dependon].selectedIndex == -1; break;
586                   case 'eq':
587                       lock = get_form_element_value(form, dependon) == value; break;
588                   case 'hide':
589                       // hide as well as disable
590                       hide = true; break;
591                   default:
592                       lock = get_form_element_value(form, dependon) != value; break;
593                 }
594                 for (var ei in dependons[dependon][condition][value]) {
595                     var eltolock = dependons[dependon][condition][value][ei];
596                     if (hide) {
597                         tohide[eltolock] = true;
598                     }
599                     if (tolock[eltolock] != null) {
600                         tolock[eltolock] = lock || tolock[eltolock];
601                     } else {
602                         tolock[eltolock] = lock;
603                     }
604                 }
605             }
606         }
607     }
608     for (var el in tolock) {
609         // change for MooTools compatibility
610         if (!tolock.propertyIsEnumerable(el)) {
611             continue;
612         }
613         set_form_element_disabled(form, el, tolock[el]);
614         if (tohide.propertyIsEnumerable(el)) {
615             set_form_element_hidden(form, el, tolock[el]);
616     }
617     }
618     return true;
621 function lockoptionsallsetup(formid) {
622     var form = document.forms[formid];
623     var dependons = eval(formid+'items');
624     for (var dependon in dependons) {
625         // change for MooTools compatibility
626         if (!dependons.propertyIsEnumerable(dependon)) {
627             continue;
628         }
629         var masters = form[dependon];
630         if (!masters) {
631             continue;
632         }
633         if (masters.tagName) {
634             // If master is radio buttons, we get an array, otherwise we don't.
635             // Convert both cases to an array for convinience.
636             masters = [masters];
637         }
638         for (var j = 0; j < masters.length; j++) {
639             master = masters[j];
640             master.formid = formid;
641             master.onclick  = function() {return lockoptionsall(this.formid);};
642             master.onblur   = function() {return lockoptionsall(this.formid);};
643             master.onchange = function() {return lockoptionsall(this.formid);};
644         }
645     }
646     for (var i = 0; i < form.elements.length; i++) {
647         var formelement = form.elements[i];
648         if (formelement.type=='reset') {
649             formelement.formid = formid;
650             formelement.onclick  = function() {this.form.reset();return lockoptionsall(this.formid);};
651             formelement.onblur   = function() {this.form.reset();return lockoptionsall(this.formid);};
652             formelement.onchange = function() {this.form.reset();return lockoptionsall(this.formid);};
653         }
654     }
655     return lockoptionsall(formid);
658 /**
659  * Either check, or uncheck, all checkboxes inside the element with id is
660  * @param id the id of the container
661  * @param checked the new state, either '' or 'checked'.
662  */
663 function select_all_in_element_with_id(id, checked) {
664     var container = document.getElementById(id);
665     if (!container) {
666         return;
667     }
668     var inputs = container.getElementsByTagName('input');
669     for (var i = 0; i < inputs.length; ++i) {
670         if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
671             inputs[i].checked = checked;
672         }
673     }
676 function select_all_in(elTagName, elClass, elId) {
677     var inputs = document.getElementsByTagName('input');
678     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
679     for(var i = 0; i < inputs.length; ++i) {
680         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
681             inputs[i].checked = 'checked';
682         }
683     }
686 function deselect_all_in(elTagName, elClass, elId) {
687     var inputs = document.getElementsByTagName('INPUT');
688     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
689     for(var i = 0; i < inputs.length; ++i) {
690         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
691             inputs[i].checked = '';
692         }
693     }
696 function confirm_if(expr, message) {
697     if(!expr) {
698         return true;
699     }
700     return confirm(message);
704 /*
705     findParentNode (start, elementName, elementClass, elementID)
707     Travels up the DOM hierarchy to find a parent element with the
708     specified tag name, class, and id. All conditions must be met,
709     but any can be ommitted. Returns the BODY element if no match
710     found.
711 */
712 function findParentNode(el, elName, elClass, elId) {
713     while (el.nodeName.toUpperCase() != 'BODY') {
714         if ((!elName || el.nodeName.toUpperCase() == elName) &&
715             (!elClass || el.className.indexOf(elClass) != -1) &&
716             (!elId || el.id == elId)) {
717             break;
718         }
719         el = el.parentNode;
720     }
721     return el;
723 /*
724     findChildNode (start, elementName, elementClass, elementID)
726     Travels down the DOM hierarchy to find all child elements with the
727     specified tag name, class, and id. All conditions must be met,
728     but any can be ommitted.
729     Doesn't examine children of matches.
730 */
731 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
732     var children = new Array();
733     for (var i = 0; i < start.childNodes.length; i++) {
734         var classfound = false;
735         var child = start.childNodes[i];
736         if((child.nodeType == 1) &&//element node type
737                   (elementClass && (typeof(child.className)=='string'))) {
738             var childClasses = child.className.split(/\s+/);
739             for (var childClassIndex in childClasses) {
740                 if (childClasses[childClassIndex]==elementClass) {
741                     classfound = true;
742                     break;
743                 }
744             }
745         }
746         if(child.nodeType == 1) { //element node type
747             if  ( (!tagName || child.nodeName == tagName) &&
748                 (!elementClass || classfound)&&
749                 (!elementID || child.id == elementID) &&
750                 (!elementName || child.name == elementName))
751             {
752                 children = children.concat(child);
753             } else {
754                 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
755             }
756         }
757     }
758     return children;
760 /*
761     elementSetHide (elements, hide)
763     Adds or removes the "hide" class for the specified elements depending on boolean hide.
764 */
765 function elementShowAdvanced(elements, show) {
766     for (var elementIndex in elements) {
767         element = elements[elementIndex];
768         element.className = element.className.replace(new RegExp(' ?hide'), '')
769         if(!show) {
770             element.className += ' hide';
771         }
772     }
775 function showAdvancedInit(addBefore, nameAttr, buttonLabel, hideText, showText) {
776     var showHideButton = document.createElement("input");
777     showHideButton.type = 'button';
778     showHideButton.value = buttonLabel;
779     showHideButton.name = nameAttr;
780     showHideButton.moodle = {
781         hideLabel: M.str.form.hideadvanced,
782         showLabel: M.str.form.showadvanced
783     };
784     YAHOO.util.Event.addListener(showHideButton, 'click', showAdvancedOnClick);
785     el = document.getElementById(addBefore);
786     el.parentNode.insertBefore(showHideButton, el);
789 function showAdvancedOnClick(e) {
790     var button = e.target ? e.target : e.srcElement;
792     var toSet=findChildNodes(button.form, null, 'advanced');
793     var buttontext = '';
794     if (button.form.elements['mform_showadvanced_last'].value == '0' ||  button.form.elements['mform_showadvanced_last'].value == '' ) {
795         elementShowAdvanced(toSet, true);
796         buttontext = button.moodle.hideLabel;
797         button.form.elements['mform_showadvanced_last'].value = '1';
798     } else {
799         elementShowAdvanced(toSet, false);
800         buttontext = button.moodle.showLabel;
801         button.form.elements['mform_showadvanced_last'].value = '0';
802     }
803     var formelements = button.form.elements;
804     // Fixed MDL-10506
805     for (var i = 0; i < formelements.length; i++) {
806         if (formelements[i] && formelements[i].name && (formelements[i].name=='mform_showadvanced')) {
807             formelements[i].value = buttontext;
808         }
809     }
810     //never submit the form if js is enabled.
811     return false;
814 function unmaskPassword(id) {
815   var pw = document.getElementById(id);
816   var chb = document.getElementById(id+'unmask');
818   try {
819     // first try IE way - it can not set name attribute later
820     if (chb.checked) {
821       var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
822     } else {
823       var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
824     }
825     newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
826   } catch (e) {
827     var newpw = document.createElement('input');
828     newpw.setAttribute('name', pw.name);
829     if (chb.checked) {
830       newpw.setAttribute('type', 'text');
831     } else {
832       newpw.setAttribute('type', 'password');
833     }
834     newpw.setAttribute('class', pw.getAttribute('class'));
835   }
836   newpw.id = pw.id;
837   newpw.size = pw.size;
838   newpw.onblur = pw.onblur;
839   newpw.onchange = pw.onchange;
840   newpw.value = pw.value;
841   pw.parentNode.replaceChild(newpw, pw);
844 /**
845  * Search a Moodle form to find all the fdate_time_selector and fdate_selector
846  * elements, and add date_selector_calendar instance to each.
847  */
848 function init_date_selectors(firstdayofweek) {
849     var els = YAHOO.util.Dom.getElementsByClassName('fdate_time_selector', 'fieldset');
850     for (var i = 0; i < els.length; i++) {
851         new date_selector_calendar(els[i], firstdayofweek);
852     }
853     els = YAHOO.util.Dom.getElementsByClassName('fdate_selector', 'fieldset');
854     for (i = 0; i < els.length; i++) {
855         new date_selector_calendar(els[i], firstdayofweek);
856     }
859 /**
860  * Constructor for a JavaScript object that connects to a fdate_time_selector
861  * or a fdate_selector in a Moodle form, and shows a popup calendar whenever
862  * that element has keyboard focus.
863  * @param el the fieldset class="fdate_time_selector" or "fdate_selector".
864  */
865 function date_selector_calendar(el, firstdayofweek) {
866     // Ensure that the shared div and calendar exist.
867     if (!date_selector_calendar.panel) {
868         date_selector_calendar.panel = new YAHOO.widget.Panel('date_selector_calendar_panel',
869                 {visible: false, draggable: false});
870         var div = document.createElement('div');
871         date_selector_calendar.panel.setBody(div);
872         date_selector_calendar.panel.render(document.body);
874         YAHOO.util.Event.addListener(document, 'click', date_selector_calendar.document_click);
875         date_selector_calendar.panel.showEvent.subscribe(function() {
876             date_selector_calendar.panel.fireEvent('changeContent');
877         });
878         date_selector_calendar.panel.hideEvent.subscribe(date_selector_calendar.release_current);
880         date_selector_calendar.calendar = new YAHOO.widget.Calendar(div,
881                 {iframe: false, hide_blank_weeks: true, start_weekday: firstdayofweek});
882         date_selector_calendar.calendar.renderEvent.subscribe(function() {
883             date_selector_calendar.panel.fireEvent('changeContent');
884             date_selector_calendar.delayed_reposition();
885         });
886     }
888     this.fieldset = el;
889     var controls = el.getElementsByTagName('select');
890     for (var i = 0; i < controls.length; i++) {
891         if (/\[year\]$/.test(controls[i].name)) {
892             this.yearselect = controls[i];
893         } else if (/\[month\]$/.test(controls[i].name)) {
894             this.monthselect = controls[i];
895         } else if (/\[day\]$/.test(controls[i].name)) {
896             this.dayselect = controls[i];
897         } else {
898             YAHOO.util.Event.addFocusListener(controls[i], date_selector_calendar.cancel_any_timeout, this);
899             YAHOO.util.Event.addBlurListener(controls[i], this.blur_event, this);
900         }
901     }
902     if (!(this.yearselect && this.monthselect && this.dayselect)) {
903         throw 'Failed to initialise calendar.';
904     }
905     YAHOO.util.Event.addFocusListener([this.yearselect, this.monthselect, this.dayselect], this.focus_event, this);
906     YAHOO.util.Event.addBlurListener([this.yearselect, this.monthselect, this.dayselect], this.blur_event, this);
908     this.enablecheckbox = el.getElementsByTagName('input')[0];
909     if (this.enablecheckbox) {
910         YAHOO.util.Event.addFocusListener(this.enablecheckbox, this.focus_event, this);
911         YAHOO.util.Event.addListener(this.enablecheckbox, 'change', this.focus_event, this);
912         YAHOO.util.Event.addBlurListener(this.enablecheckbox, this.blur_event, this);
913     }
916 /** The pop-up calendar that contains the calendar. */
917 date_selector_calendar.panel = null;
919 /** The shared YAHOO.widget.Calendar used by all date_selector_calendars. */
920 date_selector_calendar.calendar = null;
922 /** The date_selector_calendar that currently owns the shared stuff. */
923 date_selector_calendar.currentowner = null;
925 /** Used as a timeout when hiding the calendar on blur - so we don't hide the calendar
926  * if we are just jumping from on of our controls to another. */
927 date_selector_calendar.hidetimeout = null;
929 /** Timeout for repositioning after a delay after a change of months. */
930 date_selector_calendar.repositiontimeout = null;
932 /** Member variables. Pointers to various bits of the DOM. */
933 date_selector_calendar.prototype.fieldset = null;
934 date_selector_calendar.prototype.yearselect = null;
935 date_selector_calendar.prototype.monthselect = null;
936 date_selector_calendar.prototype.dayselect = null;
937 date_selector_calendar.prototype.enablecheckbox = null;
939 date_selector_calendar.cancel_any_timeout = function() {
940     if (date_selector_calendar.hidetimeout) {
941         clearTimeout(date_selector_calendar.hidetimeout);
942         date_selector_calendar.hidetimeout = null;
943     }
944     if (date_selector_calendar.repositiontimeout) {
945         clearTimeout(date_selector_calendar.repositiontimeout);
946         date_selector_calendar.repositiontimeout = null;
947     }
950 date_selector_calendar.delayed_reposition = function() {
951     if (date_selector_calendar.repositiontimeout) {
952         clearTimeout(date_selector_calendar.repositiontimeout);
953         date_selector_calendar.repositiontimeout = null;
954     }
955     date_selector_calendar.repositiontimeout = setTimeout(date_selector_calendar.fix_position, 500);
958 date_selector_calendar.fix_position = function() {
959     if (date_selector_calendar.currentowner) {
960         date_selector_calendar.panel.cfg.setProperty('context', [date_selector_calendar.currentowner.fieldset, 'bl', 'tl']);
961     }
964 date_selector_calendar.release_current = function() {
965     if (date_selector_calendar.currentowner) {
966         date_selector_calendar.currentowner.release_calendar();
967     }
970 date_selector_calendar.prototype.focus_event = function(e, me) {
971     date_selector_calendar.cancel_any_timeout();
972     if (me.enablecheckbox == null || me.enablecheckbox.checked) {
973         me.claim_calendar();
974     } else {
975         if (date_selector_calendar.currentowner) {
976             date_selector_calendar.currentowner.release_calendar();
977         }
978     }
981 date_selector_calendar.prototype.blur_event = function(e, me) {
982     date_selector_calendar.hidetimeout = setTimeout(date_selector_calendar.release_current, 300);
985 date_selector_calendar.prototype.handle_select_change = function(e, me) {
986     me.set_date_from_selects();
989 date_selector_calendar.document_click = function(event) {
990     if (date_selector_calendar.currentowner) {
991         var currentcontainer = date_selector_calendar.currentowner.fieldset;
992         var eventarget = YAHOO.util.Event.getTarget(event);
993         if (YAHOO.util.Dom.isAncestor(currentcontainer, eventarget)) {
994             setTimeout(function() {date_selector_calendar.cancel_any_timeout()}, 100);
995         } else {
996             date_selector_calendar.currentowner.release_calendar();
997         }
998     }
1001 date_selector_calendar.prototype.claim_calendar = function() {
1002     date_selector_calendar.cancel_any_timeout();
1003     if (date_selector_calendar.currentowner == this) {
1004         return;
1005     }
1006     if (date_selector_calendar.currentowner) {
1007         date_selector_calendar.currentowner.release_calendar();
1008     }
1010     if (date_selector_calendar.currentowner != this) {
1011         this.connect_handlers();
1012     }
1013     date_selector_calendar.currentowner = this;
1015     date_selector_calendar.calendar.cfg.setProperty('mindate', new Date(this.yearselect.options[0].value, 0, 1));
1016     date_selector_calendar.calendar.cfg.setProperty('maxdate', new Date(this.yearselect.options[this.yearselect.options.length - 1].value, 11, 31));
1017     this.fieldset.insertBefore(date_selector_calendar.panel.element, this.yearselect.nextSibling);
1018     this.set_date_from_selects();
1019     date_selector_calendar.panel.show();
1020     var me = this;
1021     setTimeout(function() {date_selector_calendar.cancel_any_timeout()}, 100);
1024 date_selector_calendar.prototype.set_date_from_selects = function() {
1025     var year = parseInt(this.yearselect.value);
1026     var month = parseInt(this.monthselect.value) - 1;
1027     var day = parseInt(this.dayselect.value);
1028     date_selector_calendar.calendar.select(new Date(year, month, day));
1029     date_selector_calendar.calendar.setMonth(month);
1030     date_selector_calendar.calendar.setYear(year);
1031     date_selector_calendar.calendar.render();
1032     date_selector_calendar.fix_position();
1035 date_selector_calendar.prototype.set_selects_from_date = function(eventtype, args) {
1036     var date = args[0][0];
1037     var newyear = date[0];
1038     var newindex = newyear - this.yearselect.options[0].value;
1039     this.yearselect.selectedIndex = newindex;
1040     this.monthselect.selectedIndex = date[1] - this.monthselect.options[0].value;
1041     this.dayselect.selectedIndex = date[2] - this.dayselect.options[0].value;
1044 date_selector_calendar.prototype.connect_handlers = function() {
1045     YAHOO.util.Event.addListener([this.yearselect, this.monthselect, this.dayselect], 'change', this.handle_select_change, this);
1046     date_selector_calendar.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true);
1049 date_selector_calendar.prototype.release_calendar = function() {
1050     date_selector_calendar.panel.hide();
1051     date_selector_calendar.currentowner = null;
1052     YAHOO.util.Event.removeListener([this.yearselect, this.monthselect, this.dayselect], this.handle_select_change);
1053     date_selector_calendar.calendar.selectEvent.unsubscribe(this.set_selects_from_date, this);
1056 function filterByParent(elCollection, parentFinder) {
1057     var filteredCollection = [];
1058     for (var i = 0; i < elCollection.length; ++i) {
1059         var findParent = parentFinder(elCollection[i]);
1060         if (findParent.nodeName.toUpperCase != 'BODY') {
1061             filteredCollection.push(elCollection[i]);
1062         }
1063     }
1064     return filteredCollection;
1067 /*
1068     All this is here just so that IE gets to handle oversized blocks
1069     in a visually pleasing manner. It does a browser detect. So sue me.
1070 */
1072 function fix_column_widths() {
1073     var agt = navigator.userAgent.toLowerCase();
1074     if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1075         fix_column_width('left-column');
1076         fix_column_width('right-column');
1077     }
1080 function fix_column_width(colName) {
1081     if(column = document.getElementById(colName)) {
1082         if(!column.offsetWidth) {
1083             setTimeout("fix_column_width('" + colName + "')", 20);
1084             return;
1085         }
1087         var width = 0;
1088         var nodes = column.childNodes;
1090         for(i = 0; i < nodes.length; ++i) {
1091             if(nodes[i].className.indexOf("sideblock") != -1 ) {
1092                 if(width < nodes[i].offsetWidth) {
1093                     width = nodes[i].offsetWidth;
1094                 }
1095             }
1096         }
1098         for(i = 0; i < nodes.length; ++i) {
1099             if(nodes[i].className.indexOf("sideblock") != -1 ) {
1100                 nodes[i].style.width = width + 'px';
1101             }
1102         }
1103     }
1107 /*
1108    Insert myValue at current cursor position
1109  */
1110 function insertAtCursor(myField, myValue) {
1111     // IE support
1112     if (document.selection) {
1113         myField.focus();
1114         sel = document.selection.createRange();
1115         sel.text = myValue;
1116     }
1117     // Mozilla/Netscape support
1118     else if (myField.selectionStart || myField.selectionStart == '0') {
1119         var startPos = myField.selectionStart;
1120         var endPos = myField.selectionEnd;
1121         myField.value = myField.value.substring(0, startPos)
1122             + myValue + myField.value.substring(endPos, myField.value.length);
1123     } else {
1124         myField.value += myValue;
1125     }
1129 /*
1130         Call instead of setting window.onload directly or setting body onload=.
1131         Adds your function to a chain of functions rather than overwriting anything
1132         that exists.
1133 */
1134 function addonload(fn) {
1135     var oldhandler=window.onload;
1136     window.onload=function() {
1137         if(oldhandler) oldhandler();
1138             fn();
1139     }
1141 /**
1142  * Replacement for getElementsByClassName in browsers that aren't cool enough
1143  *
1144  * Relying on the built-in getElementsByClassName is far, far faster than
1145  * using YUI.
1146  *
1147  * Note: the third argument used to be an object with odd behaviour. It now
1148  * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1149  * mimicked if you pass an object.
1150  *
1151  * @param {Node} oElm The top-level node for searching. To search a whole
1152  *                    document, use `document`.
1153  * @param {String} strTagName filter by tag names
1154  * @param {String} name same as HTML5 spec
1155  */
1156 function getElementsByClassName(oElm, strTagName, name) {
1157     // for backwards compatibility
1158     if(typeof name == "object") {
1159         var names = new Array();
1160         for(var i=0; i<name.length; i++) names.push(names[i]);
1161         name = names.join('');
1162     }
1163     // use native implementation if possible
1164     if (oElm.getElementsByClassName && Array.filter) {
1165         if (strTagName == '*') {
1166             return oElm.getElementsByClassName(name);
1167         } else {
1168             return Array.filter(oElm.getElementsByClassName(name), function(el) {
1169                 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1170             });
1171         }
1172     }
1173     // native implementation unavailable, fall back to slow method
1174     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1175     var arrReturnElements = new Array();
1176     var arrRegExpClassNames = new Array();
1177     var names = name.split(' ');
1178     for(var i=0; i<names.length; i++) {
1179         arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1180     }
1181     var oElement;
1182     var bMatchesAll;
1183     for(var j=0; j<arrElements.length; j++) {
1184         oElement = arrElements[j];
1185         bMatchesAll = true;
1186         for(var k=0; k<arrRegExpClassNames.length; k++) {
1187             if(!arrRegExpClassNames[k].test(oElement.className)) {
1188                 bMatchesAll = false;
1189                 break;
1190             }
1191         }
1192         if(bMatchesAll) {
1193             arrReturnElements.push(oElement);
1194         }
1195     }
1196     return (arrReturnElements)
1199 function openpopup(event, args) {
1201     YAHOO.util.Event.preventDefault(event);
1203     var fullurl = args.url;
1204     if (!args.url.match(/https?:\/\//)) {
1205         fullurl = M.cfg.wwwroot + args.url;
1206     }
1207     var windowobj = window.open(fullurl,args.name,args.options);
1208     if (!windowobj) {
1209         return true;
1210     }
1211     if (args.fullscreen) {
1212         windowobj.moveTo(0,0);
1213         windowobj.resizeTo(screen.availWidth,screen.availHeight);
1214     }
1215     windowobj.focus();
1216     return false;
1219 /* This is only used on a few help pages. */
1220 emoticons_help = {
1221     inputarea: null,
1223     init: function(formname, fieldname, listid) {
1224         if (!opener || !opener.document.forms[formname]) {
1225             return;
1226         }
1227         emoticons_help.inputarea = opener.document.forms[formname][fieldname];
1228         if (!emoticons_help.inputarea) {
1229             return;
1230         }
1231         var emoticons = document.getElementById(listid).getElementsByTagName('li');
1232         for (var i = 0; i < emoticons.length; i++) {
1233             var text = emoticons[i].getElementsByTagName('img')[0].alt;
1234             YAHOO.util.Event.addListener(emoticons[i], 'click', emoticons_help.inserttext, text);
1235         }
1236     },
1238     inserttext: function(e, text) {
1239         text = ' ' + text + ' ';
1240         if (emoticons_help.inputarea.createTextRange && emoticons_help.inputarea.caretPos) {
1241             var caretPos = emoticons_help.inputarea.caretPos;
1242             caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
1243         } else {
1244             emoticons_help.inputarea.value  += text;
1245         }
1246         emoticons_help.inputarea.focus();
1247     }
1250 /**
1251  * Oject to handle expanding and collapsing blocks when an icon is clicked on.
1252  * @constructor
1253  * @param String id the HTML id for the div.
1254  * @param String userpref the user preference that records the state of this block.
1255  * @param String visibletooltip tool tip/alt to show when the block is visible.
1256  * @param String hiddentooltip tool tip/alt to show when the block is hidden.
1257  * @param String visibleicon URL of the icon to show when the block is visible.
1258  * @param String hiddenicon URL of the icon to show when the block is hidden.
1259  */
1260 function block_hider(id, userpref, visibletooltip, hiddentooltip, visibleicon, hiddenicon) {
1261     // Find the elemen that is the block.
1262     this.block = document.getElementById(id);
1263     var title_div = YAHOO.util.Dom.getElementsByClassName('title', 'div', this.block);
1264     if (!title_div || !title_div[0]) {
1265         return this;
1266     }
1267     title_div = title_div[0];
1268     this.ishidden = YAHOO.util.Dom.hasClass(this.block, 'hidden');
1270     // Record the pref name
1271     this.userpref = userpref;
1272     this.visibletooltip = visibletooltip;
1273     this.hiddentooltip = hiddentooltip;
1274     this.visibleicon = visibleicon;
1275     this.hiddenicon = hiddenicon;
1277     // Add the icon.
1278     this.icon = document.createElement('input');
1279     this.icon.type = 'image';
1280     this.icon.className = 'hide-show-image';
1281     this.update_state();
1282     title_div.insertBefore(this.icon, title_div.firstChild);
1284     // Hook up the event handler.
1285     YAHOO.util.Event.addListener(this.icon, 'click', this.handle_click, null, this);
1288 /** Handle click on a block show/hide icon. */
1289 block_hider.prototype.handle_click = function(e) {
1290     YAHOO.util.Event.stopEvent(e);
1291     this.ishidden = !this.ishidden;
1292     this.update_state();
1293     M.util.set_user_preference(this.userpref, this.ishidden);
1296 /** Set the state of the block show/hide icon to this.ishidden. */
1297 block_hider.prototype.update_state = function () {
1298     if (this.ishidden) {
1299         YAHOO.util.Dom.addClass(this.block, 'hidden');
1300         this.icon.alt = this.hiddentooltip;
1301         this.icon.title = this.hiddentooltip;
1302         this.icon.src = this.hiddenicon;
1303     } else {
1304         YAHOO.util.Dom.removeClass(this.block, 'hidden');
1305         this.icon.alt = this.visibletooltip;
1306         this.icon.title = this.visibletooltip;
1307         this.icon.src = this.visibleicon;
1308     }
1311 /** Close the current browser window. */
1312 function close_window(e) {
1313     YAHOO.util.Event.preventDefault(e);
1314     self.close();
1317 /**
1318  * Close the current browser window, forcing the window/tab that opened this
1319  * popup to reload itself. */
1320 function close_window_reloading_opener() {
1321     if (window.opener) {
1322         window.opener.location.reload(1);
1323         close_window();
1324         // Intentionally, only try to close the window if there is some evidence we are in a popup.
1325     }
1328 /**
1329  * Used in a couple of modules to hide navigation areas when using AJAX
1330  */
1332 function show_item(itemid) {
1333     var item = document.getElementById(itemid);
1334     if (item) {
1335         item.style.display = "";
1336     }
1339 function destroy_item(itemid) {
1340     var item = document.getElementById(itemid);
1341     if (item) {
1342         item.parentNode.removeChild(item);
1343     }
1345 /**
1346  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1347  * @param controlid the control id.
1348  */
1349 function focuscontrol(controlid) {
1350     var control = document.getElementById(controlid);
1351     if (control) {
1352         control.focus();
1353     }
1356 /**
1357  * Transfers keyboard focus to an HTML element based on the old style style of focus
1358  * This function should be removed as soon as it is no longer used
1359  */
1360 function old_onload_focus(formid, controlname) {
1361     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1362         document.forms[formid].elements[controlname].focus();
1363     }
1366 function build_querystring(obj) {
1367     if (typeof obj !== 'object') {
1368         return null;
1369     }
1370     var list = [];
1371     for(var k in obj) {
1372         k = encodeURIComponent(k);
1373         var value = obj[k];
1374         if(obj[k] instanceof Array) {
1375             for(var i in value) {
1376                 list.push(k+'[]='+encodeURIComponent(value[i]));
1377             }
1378         } else {
1379             list.push(k+'='+encodeURIComponent(value));
1380         }
1381     }
1382     return list.join('&');
1385 function stripHTML(str) {
1386     var re = /<\S[^><]*>/g;
1387     var ret = str.replace(re, "");
1388     return ret;
1391 Number.prototype.fixed=function(n){
1392     with(Math)
1393         return round(Number(this)*pow(10,n))/pow(10,n);
1395 function update_progress_bar (id, width, pt, msg, es){
1396     var percent = pt*100;
1397     var status = document.getElementById("status_"+id);
1398     var percent_indicator = document.getElementById("pt_"+id);
1399     var progress_bar = document.getElementById("progress_"+id);
1400     var time_es = document.getElementById("time_"+id);
1401     status.innerHTML = msg;
1402     percent_indicator.innerHTML = percent.fixed(2) + '%';
1403     if(percent == 100) {
1404         progress_bar.style.background = "green";
1405         time_es.style.display = "none";
1406     } else {
1407         progress_bar.style.background = "#FFCC66";
1408         if (es == Infinity){
1409             time_es.innerHTML = "Initializing...";
1410         }else {
1411             time_es.innerHTML = es.fixed(2)+" sec";
1412             time_es.style.display
1413                 = "block";
1414         }
1415     }
1416     progress_bar.style.width = width + "px";
1420 function frame_breakout(e, properties) {
1421     this.setAttribute('target', properties.framename);
1425 // ===== Deprecated core Javascript functions for Moodle ====
1426 //       DO NOT USE!!!!!!!
1427 // Do not put this stuff in separate file because it only adds extra load on servers!
1429 /**
1430  * Used in a couple of modules to hide navigation areas when using AJAX
1431  */
1432 function hide_item(itemid) {
1433     // use class='hiddenifjs' instead
1434     var item = document.getElementById(itemid);
1435     if (item) {
1436         item.style.display = "none";
1437     }