PageSourceSearch

https://informs-sim.org/wsc23papers/includes/jquery/basic.js

js informs-sim.org collected 2026-10-01 11:29:03 UTC 158,086 bytes, 4,627 lines download raw bytes

1// We use a counter at the end of this file name to flush people's browser cache.
2
3// This helps us resize an element when its contents change or are resized. Needs improvement. Use like:
4// $('#foo').on("DOMSubtreeModified show hide", function() { do resizing here });
5(function($) {
6    $.each(['show', 'hide'], function (i, ev) {
7        var el = $.fn[ev];
8        $.fn[ev] = function() {
9            var ans = el.apply(this, arguments);
10            this.trigger(ev);
11            return ans;
12        };
13    });
14})(jQuery);
15
16
17// Jump to the 'value' of the select-list option.
18// use like this: <select onChange=jumpToSelectedValue(this)>...
19function jumpToSelectedValue(selObj) {
20    // eval("parent.location='" + selObj.options[selObj.selectedIndex].value + "'"); // This version doesn't work in iframes.
21    eval("document.location='" + selObj.options[selObj.selectedIndex].value + "'");
22    eval("location.href = '" + selObj.options[selObj.selectedIndex].value + "'");
23}
24
25// Jump to the 'id' of the select-list option.
26// use like this: <select onChange=jumpToSelectedId(this)>...
27function jumpToSelectedId(selObj) {
28    // eval("parent.location='" + selObj.options[selObj.selectedIndex].id + "'"); // This version doesn't work in iframes.
29    eval("document.location='" + selObj.options[selObj.selectedIndex].id + "'");
30    eval("location.href = '" + selObj.options[selObj.selectedIndex].id + "'");
31}
32
33
34// DEPRECATED
35function count_words(textfield, countfield) {
36    var val = textfield.value.replace(/^\s+/g, "").replace(/\s+$/g, "");
37    if (val) { val = val + ' '; }
38    countfield.value = val.split(/\s+/).length - 1;
39}
40
41
42function count_words_jq(textfield_name, countfield_id) {
43    var field = $("[name='"+textfield_name+"']")
44
45    val = field.attr("value")
46    $("#" + countfield_id).html(count_words_in_str(val));
47}
48
49function tiny_mce_count_words_jq(tinymce_id, countfield_id) {
50    var field = tinymce.get(tinymce_id);
51    if (field != undefined) {
52        // I think that the object is not ready for getContent immediately.
53        // val = field.getContent();
54        try {
55            val = field.getContent();
56        }
57        catch {
58            val = '';
59        }
60        $("#" + countfield_id).html(count_words_in_str(val));
61    }
62}
63
64
65// This is used to remove markdown symbols that would increase the wordcount
66var MARKDOWN_WORDCOUNT_REGEX = /^(?:#{1,2}|=+|-+|\s*>\s|\s*\*+|\s*-\s+|\s*[0-9][\.\)]\s+)/gm
67// This is used to differentiate between markdown italiacs and math.
68// This does not correctly enforce two-star matching with two-stars and one-star matching with one-star.
69var GOOD_MARKDOWN_STAR_REGEX = /(?:\*\*[^\s\*][^\*]+[^\s\*]\*\*)|(?:\*[^\s\*][^\*]+[^\s\*]\*)/gm
70
71
72// This isn't treating html tags exactly like our python word count is.
73function count_words_in_str(val) {
74    val = strip_html(val);
75    // clean space at the beginning and end of lines
76    // val = val.replace(/^\s+/g, "").replace(/\s+$/g, "");
77    val = val.trim();
78    // remove patterns that look like markdown.
79    // val = val.replace(/^(?:#{1,2}|=+|-+|\s*>\s|\s*\*+\s+|\s*-\s+|\s*[0-9][\.\)]\s+)/gm, '');
80    val = val.replace(MARKDOWN_WORDCOUNT_REGEX, '');
81    // This one should have a space because you wouldn't have a word continuing into a code block
82    val = val.replace(/`{1,3}/g, ' ');
83    // This is strange but it fixes the empty string case.
84    if (val) { val = val + ' '; }
85    return val.split(/\s+/).length - 1;
86}
87
88function count_chars_jq(textfield_name, countfield_id) {
89    var val = $("[name='"+textfield_name+"']").attr("value").replace(/^\s+/g, "").replace(/\s+$/g, "").replace(/<br \/>/g, " ");
90    $("#"+countfield_id).html(val.length);
91}
92
93function tiny_mce_count_chars_jq(tinymce_id, countfield_id) {
94    var field = tinymce.get(tinymce_id);
95    if (field != undefined) {
96        // I think that the object is not ready for getContent immediately.
97        try {
98            val = field.getContent();
99        }
100        catch {
101            val = '';
102        }
103        val = val.replace(/^\s+/g, "").replace(/\s+$/g, "").replace(/<br \/>/g, " ");
104        $("#" + countfield_id).html(val.length);
105    }
106}
107
108function _____FILE_SIZE_ERROR_DETECTION______() {}
109
110
111// Taken and modified from http://stackoverflow.com/questions/3717793/javascript-file-upload-size-validation
112
113function get_file_size(file_field) {
114	var file;
115
116	// (Can't use `typeof FileReader === "function"` because apparently
117	// it comes back as "object" on some browsers. So just see if it's there
118	// at all.)
119	if (!window.FileReader) {
120	    return 0;
121	}
122
123	// We don't want the jquery object.
124	var input = file_field[0];
125
126	if (!input) {
127		return 0;
128	}
129	else if (!input.files) {
130		return 0;
131	}
132	else if (!input.files[0]) {
133		return 0;
134	}
135	else {
136	    file = input.files[0];
137		return file.size;
138	}
139}
140
141
142function check_file_size(event) {
143	var file_field = $(event.target);
144	var max_bytes = parseInt(file_field.attr('max_bytes'));
145
146	if (max_bytes != NaN) {
147		if (max_bytes == 0) {
148			return;
149		}
150		var file_size = get_file_size(file_field);
151		if (file_size > max_bytes) {
152			alert('This file is too large. It is ' + readable_file_size(file_size) + ' but should not exceed ' + readable_file_size(max_bytes) + '.');
153			file_field.val('');
154			return false;
155		}
156	}
157}
158
159
160function readable_file_size(size, inc_dec) {
161
162    if (typeof (inc_dec) === 'undefined') inc_dec = true;
163
164    if (size < 1024) {
165		// Write it in bytes.
166        return size.toString() + "B";
167    } else {
168        size = size / 1024.0
169        if (size < 1024) {
170            if (inc_dec) {
171                size = Math.round(size * 100) / 100
172            } else {
173                size = Math.round(size)
174            }
175	        return size.toString() + "KB";
176        } else {
177            size = size / 1024.0
178            if (inc_dec) {
179                size = Math.round(size * 100) / 100
180            } else {
181                size = Math.round(size)
182            }
183	        return size.toString() + "MB";
184		}
185    }
186}
187
188// Set up the change handler for file fields.
189$(function() {
190	$(document).on('change', 'input:file', check_file_size);
191})
192
193
194function expandcollapse(id) {
195	if (document.getElementById) {
196	    // DOM3 = IE5, NS6
197        id_style = document.getElementById(id).style;
198        if(id_style.display == 'none') {
199            id_style.display = ''; // visible (default inline/block for element)
200        } else {
201            id_style.display = 'none';
202        }
203	} else {
204	    // Apparently this works for some browsers.
205		if (document.layers) {
206			if (document.id.display == "none") {
207				document.id.display = 'block';
208				filter(("img"+id),'imgin');
209			} else {
210				filter(("img"+id),'imgout');
211				document.id.display = 'none';
212			}
213		} else {
214			if (document.all.id.style.visibility == "none") {
215				document.all.id.style.display = 'block';
216			} else {
217				filter(("img"+id),'imgout');
218				document.all.id.style.display = 'none';
219			}
220		}
221	}
222}
223
224
225function expandcollapse3(id) {
226    expandcollapse(id);
227    expandcollapse(id + '_show_span');
228    expandcollapse(id + '_hide_span');
229}
230
231
232// DEPRECATED.
233function getElementsByClass(strTagName, strClassName) {
234	var arrElements = (strTagName == "*" && document.all)? document.all : document.getElementsByTagName(strTagName);
235	var arrReturnElements = new Array();
236	strClassName = strClassName.replace(/-/g, "\-");
237	var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
238	var oElement;
239	for(var i=0; i<arrElements.length; i++){
240		oElement = arrElements[i];
241		if(oRegExp.test(oElement.className)){
242			arrReturnElements.push(oElement);
243		}
244	}
245	return (arrReturnElements)
246}
247
248
249function expandcollapse_multi(tag_class) {
250    var els = getElementsByClass('*', tag_class);
251    for (var i=0; i<els.length; i++) {
252        var el = els[i];
253        var el_style = el.style;
254        if(el_style.display == 'none') {
255            el_style.display = ''; // visible (default inline/block for element)
256        } else {
257            el_style.display = 'none';
258        }
259    }
260}
261
262
263function expandcollapse_multi3(tag_class) {
264    expandcollapse_multi(tag_class);
265    expandcollapse_multi(tag_class + '_show_span');
266    expandcollapse_multi(tag_class + '_hide_span');
267
268    $(document).trigger('expandCollapseMulti')
269}
270
271
272// The below functions work together to hide all (no matter what state they were in initially) or show all.
273function collapse_multi(tag_class) {
274    var els = getElementsByClass('*', tag_class);
275    for (var i=0; i<els.length; i++) {
276        els[i].style.display = 'none';
277    }
278}
279
280
281function expand_multi(tag_class) {
282    var els = getElementsByClass('*', tag_class);
283    for (var i=0; i<els.length; i++) {
284        els[i].style.display = ''; // visible (default inline/block for element)
285    }
286}
287
288
289function expand_multi3(tag_class) {
290    expand_multi(tag_class);
291    collapse_multi(tag_class + '_show_span');
292    expand_multi(tag_class + '_hide_span');
293
294    $(document).trigger('expandCollapseMulti')
295}
296
297
298function collapse_multi3(tag_class) {
299    collapse_multi(tag_class);
300    expand_multi(tag_class + '_show_span');
301    collapse_multi(tag_class + '_hide_span');
302
303    $(document).trigger('expandCollapseMulti')
304}
305
306
307// Functions below require jquery.
308
309// Seems to infinitely loop if not given <req_msg>!
310function jqueryGetReplaceElContents(url, el_id, req_msg, callback) {
311    // This is expects url to return just the contents of the element to replace.
312    // It does not replace the element with the returned contents, it just replaces the contents.
313    // If <url> returns an element with <el_id> use jqueryGetReplaceEl() instead.
314
315    // I have turned off most of the slide-down / slide-up animations below since it causes too
316    // much bouncing. Could add a 'duration' parameter to pass on to 
316jquery hide/shows to control animations.
317
318    $('#'+el_id).hide(0, function() {
319        $(this).html(req_msg).show();
320    });
321    $.ajax({
322        url: url,
323        type: 'GET',
324        cache: false,
325        data: {
326            'uniq_param': (new Date()).getTime()   // This prevents IE from caching.
327        },
328        dataType: 'html',
329        // contentType: "application/x-www-form-urlencoded; charset=iso-8859-1",
330        // contentType: "text/html; charset=iso-8859-1",
331        contentType: "text/html; charset=windows-1252",
332        // beforeSend: function(xhr) { xhr.setRequestHeader('Accept', "text/html; charset=iso-8859-1") },
333
334        // This version nests the new element inside the old one, which is not what we want.
335        // Breaks the rule of all elements having unique ids.
336        success: function(data, textStatus, jqXHR) {
337            $('#'+el_id).hide(0, function() {
338                $(this).html(data).show(0, function() {
339                    if (callback) { callback() }
340                })
341            })
342        }
343    })
344    return false;
345}
346
347
348function jqueryGetReplaceEl(url, el_id, req_msg, callback) {
349    // This is expects url to return an element with <el_id>.
350    // It replaces the element with the returned element.
351
352    // I have turned off most of the slide-down / slide-up animations below since it causes too
353    // much bouncing. Could add a 'duration' parameter to pass on to jquery hide/shows to control animations.
354
355    $('#'+el_id).hide(0, function() {
356        $(this).html(req_msg).show();
357    });
358    $.ajax({
359        url: url,
360        type: 'GET',
361        cache: false,
362        data: {
363            'uniq_param': (new Date()).getTime()   // This prevents IE from caching.
364        },
365        dataType: 'html',
366        // contentType: "application/x-www-form-urlencoded; charset=iso-8859-1",
367        // contentType: "text/html; charset=iso-8859-1",
368        contentType: "text/html; charset=windows-1252",
369        // beforeSend: function(xhr) { xhr.setRequestHeader('Accept', "text/html; charset=iso-8859-1") },
370
371        success: function(data, textStatus, jqXHR) {
372            $('#'+el_id).hide(0, function() {
373                $(this).replaceWith(data).show(0, function() {
374                    if (callback) { callback() }
375                })
376            })
377        }
378    })
379    return false;
380}
381
382
383function jqueryPostReplaceEl(url, el_id, req_msg, postdata, callback) {
384    // since this pretends to be a button it requires that one of the keys in
385    // postdata be postdata['button_<encrypted_url>'] like when we submit a form
386    // if <url> it will be placed into the post data
387    // <el_id> can be either an element id string or a jQuery object
388
389    if (url) { postdata[url] = 'button'; } // this is how we encode url args with forms
390
391    el = el_id;
392    if (!(el_id instanceof jQuery)) {el = $("#"+el_id);}
393
394    if (!req_msg) { req_msg = "Loading..."; }
395    req_msg = "<span class=\'loading\'><i class=\'fa fa-spinner fa-spin\'></i> "+req_msg+"</span>";
396
397    el.hide('normal', function(){
398        $(this).html(req_msg).slideDown('normal');
399    });
400
401    // IE won't post to an empty url, so we set it to '?' which will be our default handler
402    $.post('?', postdata, function(result_data) {
403        el.hide('normal', function(){
404            $(this).html(result_data).slideDown('normal', function() {
405                if (callback) { callback(); }
406            });
407        });
408    });
409    return false;
410}
411
412function mmCgiEncodedPost(post_url, postdata, callback) {
413  // <post_url> should be the result of a link(.., url_only=True) call.
414  var post_loc = post_url[0];
415  // this is standard for encoding args in a post submission
416  var routing_url = "button_" + post_url.slice(1);
417
418  if (typeof postdata === 'string' || postdata instanceof String) {
419      // postdata is already a query arg style string
420      postdata += '&' + encodeURIComponent(routing_url) + '=button';
421  } else {
422      // postdata is a data object
423      postdata[routing_url] = "button";
424  }
425
426  $.post(post_loc, postdata, callback);
427  return false;
428}
429
430function replaceElWithPostResponse($el, post_url, postdata, callback) {
431  return mmCgiEncodedPost(post_url, postdata, function(result_data) {
432    $el.html(result_data);
433    if (callback) { callback(); }
434  });
435}
436
437function replaceElWithPostResponseAnimated($el, post_url, postdata, loading_msg, callback) {
438  if (loading_msg) {
439    loading_msg = "<span class=\'loading\'><i class=\'fa fa-spinner fa-spin\'></i> " + loading_msg + "</span>";
440  } else {
441    loading_msg = "<span class=\'loading\'><i class=\'fa fa-spinner fa-spin\'></i> Loading...</span>";
442  }
443  $el.slideUp(function() {$(this).html(loading_msg).show()});
444  return mmCgiEncodedPost(post_url, postdata, function(result_data) {
445    $el.hide().html(result_data).slideDown();
446    if (callback) { callback(); }
447  });
448}
449
450function replaceElWithPostResponseOverlay($el, post_url, postdata, loading_msg, callback) {
451  add_message_overlay($el, loading_msg);
452  return mmCgiEncodedPost(post_url, postdata, function(result_data) {
453    $el.html(result_data);
454    if (callback) { callback(); }
455  });
456}
457
458
459function replaceElWithGetResponseOverlay($el, get_url, loading_msg, callback) {
460  add_message_overlay($el, loading_msg);
461  $.ajax({
462    url: get_url,
463    type: 'GET',
464    cache: false,
465    data: {
466        'uniq_param': (new Date()).getTime()   // This prevents IE from caching.
467    },
468    dataType: 'html',
469    // contentType: "application/x-www-form-urlencoded; charset=iso-8859-1",
470    // contentType: "text/html; charset=iso-8859-1",
471    contentType: "text/html; charset=windows-1252",
472    // beforeSend: function(xhr) { xhr.setRequestHeader('Accept', "text/html; charset=iso-8859-1") },
473    success: function(data, textStatus, jqXHR) {
474      $el.html(data);
475      if (callback) { callback(); }
476    }
477  });
478}
479
480
481function add_message_overlay(el, message) {
482    $el = $(el);
483    if (message) {
484      message = "<span class=\'loading\'><i class=\'fa fa-spinner fa-spin\'></i> " + message + "</span>";
485    } else {
486      message = "<span class=\'loading\'><i class=\'fa fa-spinner fa-spin\'></i> Loading...</span>";
487    }
488    $el.css("position", "relative");
489    $el.append(
490      $("<div Class='loading-overlay'></div>").css({
491        'position': 'absolute',
492        'left': 0,
493        'top': 0,
494        'width': '100%',
495        'height': '100%',
496        'background': 'rgba(255, 255, 255, .7)'
497      }).append(
498        $("<div></div>").css({
499          'position': 'absolute',
500          'top': '50%',
501          'left': '50%',
502          'transform': 'translate(-50%, -50%)'
503        }).html(message)
504      )
505    );
506}
507
508function remove_loading_overlay(el) {
509    $(el).find('.loading-overlay').remove();
510}
511
512
513function _____IFRAMES______() {}
514
515function resize_iframe(iid, h) {
516    // This one uses no sliding - better for changing the size of an existing iframe. Height only.
517    $('#'+iid).attr({height: h + 'px'})
518}
519function slide_resize_iframe(iid, h) {
520    // This one uses sliding. Height only
521    if ($('#'+iid).is('iframe')) {
522        $('#'+iid).hide().attr({height: h + 'px'}).slideDown()
523    } else {
524        // Using slideUp() for object tags causes an infinite loop.
525        $('#'+iid).attr({height: h + 'px'}).slideDown()
526    }
527}
528function resize_iframe_to_fit(iid) {
529    // The idea is to get it roughly right, then re-check every half-second.
530    // It is counter-intuitive that we need to wait 1 second after the document is "ready"
531    // to get the right height, but in cases like jquery UI tabs, there is some
532    // js reorganizing of the page that happens during that time.
533    // We can't slide the second time, since in Firefox that causes it to jump
534    // closed and then slide open.
535
536    $(function() {
537        function calc_height() {
538            // Other values of interest:
539            // document.body.scrollHeight,
540            // document.documentElement.scrollHeight,
541            // document.body.offsetHeight,
542            // document.documentElement.offsetHeight,
543            // document.body.clientHeight,
544            // document.documentElement.clientHeight
545
546            // ($.browser.mozilla & $.browser.version == '11.0')
547            if ($.browser.msie || $.browser.opera || !!navigator.userAgent.match(/Trident\/7\./) ) {
548                // Hard to really get IE and opera to shrink. Trust me.
549                return Math.max($(document).height(), $('html').outerHeight(true), $('body').outerHeight(true))
550            } else {
551                return Math.min($(document).height(), $('html').outerHeight(true))
552            }
553        }
554        var curr_height = calc_height()
555
556        try {
557            // This won't work if we're embedding it in an iframe outside of linklings.
558            parent.slide_resize_iframe(iid, curr_height)
559			window.parent.jQuery.colorbox.resize({
560                innerWidth: $(document).width(),
561                innerHeight: $(document).height()
562            })
563
564        } catch (e) {
565            // Don't do anything here
566        }
567
568        // jquery.ba-resize.min.js seems to have some issues, so do a simple version.
569        // Check every half-second if the height has changed, and adjust.
570        function fix_size(old_height) {
571            var new_height = calc_height()
572
573            // This is a hack to give a viewable window if something has gone wrong.
574            // We don't have a case that this is needed yet.
575            if (new_height < 10) {new_height=500}
576
577            if (new_height != old_height) {
578                parent.resize_iframe(iid, new_height)
579
580				window.parent.jQuery.colorbox.resize({
581                    innerWidth: $(document).width(),
582                    innerHeight: $(document).height()
583                })
584            }
585            window.setTimeout(function() { fix_size(new_height) }, 500)
586        }
587        fix_size(curr_height)
588    })
589}
590
591
592function rm_iframe(iid, idoc) {
593    // Uses contents of the form tag, if exists, else body tag.
594    var s = $('form', idoc).html();
595    if (!s.length) {
596        s = $(idoc.body).html();
597    }
598    $('#'+iid).parent().hide('normal', function () {
599        $(this).html(s).slideDown();
600    });
601    // Fix this!
602    vehif_view_mode(parse_iframe_id(iid));
603}
604
605
606function vehif_rm_iframe(iid, idoc) {
607    // Uses contents of the form tag, if exists, else body tag.
608    var s = $('form', idoc).html();
609    if (!s.length) {
610        s = $(idoc.body).html();
611    }
612    $('#'+iid).parent().hide('normal', function () {
613        $(this).html(s).slideDown();
614    });
615    vehif_view_mode(parse_iframe_id(iid));
616}
617
618
619function parse_iframe_id(iid) {
620    var a = iid.split('_');
621    return a[a.length-1];
622}
623
624
625function vehif_view_mode(uniq) {
626    var view_id = 'vehif_view_' + uniq;
627    var edit_id = 'vehif_edit_' + uniq;
628    var hide_id = 'vehif_hide_' + uniq;
629    var view2_id = 'vehif_view2_' + uniq;
630    var edit2_id = 'vehif_edit2_' + uniq;
631    var hide2_id = 'vehif_hide2_' + uniq;
632    $('#'+view_id+', #'+edit2_id+', #'+hide2_id).hide();
633    $('#'+view2_id+', #'+edit_id+', #'+hide_id).show();
634}
635
636
637function vehif_edit_mode(uniq) {
638    var view_id = 'vehif_view_' + uniq;
639    var edit_id = 'vehif_edit_' + uniq;
640    var hide_id = 'vehif_hide_' + uniq;
641    var view2_id = 'vehif_view2_' + uniq;
642    var edit2_id = 'vehif_edit2_' + uniq;
643    var hide2_id = 'vehif_hide2_' + uniq;
644    $('#'+view2_id+', #'+edit_id+', #'+hide2_id).hide();
645    $('#'+view_id+', #'+edit2_id+', #'+hide_id).show();
646}
647
648
649function vehif_hide_mode(uniq) {
650    var view_id = 'vehif_view_' + uniq;
651    var edit_id = 'vehif_edit_' + uniq;
652    var hide_id = 'vehif_hide_' + uniq;
653    var view2_id = 'vehif_view2_' + uniq;
654    var edit2_id = 'vehif_edit2_' + uniq;
655    var hide2_id = 'vehif_hide2_' + uniq;
656    $('#'+view2_id+', #'+edit2_id+', #'+hide_id).hide();
657    $('#'+view_id+', #'+edit_id+', #'+hide2_id).show();
658}
659
660
661function js_link_select(link_ctr, grp_ctr) {
662    // el_id was selected: unselect all, and then select the right one.
663    $('.link_group_'+grp_ctr+'_unselected').show();
664    $('.link_group_'+grp_ctr+'_selected').hide();
665    $('#link_'+link_ctr+'_unselected').hide();
666    $('#link_'+link_ctr+'_selected').show();
667}
668
669
670function check_reqs__old() {
671    var missing = false;
672    $('.required_field').each(function(i) {
673        if (!$(this).val().length) {
674            missing = true;
675            if (this.id) {
676                $('[for='+this.id+']').css("color","red");
677            }
678            $(this).css("border","solid red 1px");
679        }
680    });
681    if (missing) {
682        alert('Some required fields have not been completed. Please verify that all fields marked with red are filled out.');
683        return false;
684    }
685    return true;
686}
687
688
689function check_reqs() {
690    // This is an improved version that works with adding the 'required_field' class
691    // to input objects using the input_class argument.
692    let filter_find = '.jqx-combobox, text, textarea, input, hidden, file, password, checkbox, radio, select';
693    var missing = false;
694    // The not() here is because adding 'required_field' as an input_class
695    // adds it to the label as well as a div wrapping the element
696    $('.required_field').not('[class$=input_label_div]').each(function(i) {
697        let input_el;
698        input_el = $(this).filter(filter_find);
699        if (!input_el.length) {
700            // If the class wasn't directly on the input element then find an input in it's descendants.
701            input_el = $(this).find(filter_find);
702        }
703        let val = get_field_values(input_el);
704        // Don't include empty strings as values
705        val = val.filter(item => item);
706        if (!val.length) {
707            missing = true;
708            if (this.id) {
709                $('[for='+this.id+']').css("color","red");
710            }
711            $(this).css("border","solid red 1px");
712
713            // Set border for stars
714            let stars_box = $(this).closest('.stars_input_box');
715            if (stars_box.length > 0) {
716                stars_box.css("border", "solid red 1px");
717            }
718        }
719    });
720
721    if (missing) {
722        alert('Some required fields have not been completed. Please verify that all fields marked with red are filled out.');
723        return false;
724    }
725    return true;
726}
727
728
729function highlight_field_label(field, multi_ctr) {
730    // Brefly highlights the label for <field>.
731
732    var multi_fld = field;
733    if (multi_ctr !== '') {
734        multi_fld = field + '_' + multi_ctr;
735    }
736
737    var input_ob = $('[name=' + multi_fld + ']');
738
739    // There may be other special cases here.
740    if (
741        input_ob.parents('.auto_height_cb_widget').length > 0 ||
742        input_ob.parents('.cb_widget').length > 0
743    ) {
744        var input_class = field + '_cb';
745        if (multi_ctr != '') {
746            input_class = input_class + '_' + multi_ctr;
747        }
748        var label_ob = $('label[cbs_for=' + input_class + ']');
749    } else {
750        var input_id = input_ob.attr('id');
751        var label_ob = $('label[for=' + input_id + ']');
752    }
753
754    jump_to_highlight_obj(label_ob, input_ob);
755}
756
757
758function get_input_connected_to_label(label_ob) {
759
760    // This is used to get the input element that is connected to a label.
761
762    if (!label_ob) { return null; }
763
764    let input_obj;
765    if (label_ob.attr('for')) {
766        input_obj = $('#' + label_ob.attr('for'));
767    } else if (label_ob.attr('cbs_for')) {
768        input_obj = $('.' + label_ob.attr('cbs_for')).first();
769    }
770
771    return input_obj;
772}
773
774
775function jump_to_highlight_obj(obj, focus_field_obj=null) {
776
777    // Brefly highlights a specific jQuery object. Also
778    // assigns focus to the form field associated with the object, if possible.
779    if (!obj) { return; }
780
781    let focus_obj;
782    if (focus_field_obj) {
783        focus_obj = focus_field_obj;
784    } else {
785        focus_obj = get_input_connected_to_label(obj);
786        if (!focus_obj || !focus_obj.length) {
787            focus_obj = obj;
788        } else {
789            focus_obj = focus_obj.first();
790        }
791    }
792
793    let focus_obj_id = focus_obj.attr('id');
794
795    focus_obj.focus();
796
797    let closest_div = obj.closest('div');
798
799    const highlight_opts = {
800        duration: 750,
801        queue: true,
802    };
803
804    closest_div.addClass('highlight', highlight_opts);
805    closest_div.removeClass('highlight', highlight_opts);
806    closest_div.addClass('highlight', highlight_opts);
807    closest_div.removeClass('highlight', highlight_opts);
808}
809
810
811function test_cookies() {
812    $.removeCookie('testcookiesenabled');
813    $.cookie('testcookiesenabled', 'enabled');
814    if ($.cookie('testcookiesenabled')) {
815        $.removeCookie('testcookiesenabled');
816    } else {
817        alert("For security reasons, this site requires that your browser has cookies enabled, but your browser has them disabled. Please change your browser settings.");
818    }
819}
820
821function _____QTIP______() {}
822
823// Make qtips disappear when the escape key is pressed.
824$(function() {
825    $(window).on('keydown', function(event) {
826    	if(event.keyCode === 27) {
827    		$('.qtip').qtip('hide', event);
828    	}
829    });
830})
831
832
833var setup_info_links_throttle = null;
834var setup_info_links_request = null;
835
836function setup_info_links() {
837
838  // Setting up info links is an expensive operation
839  // 0.25 - 0.75 seconds
840  // With asynchronous snippets we are calling this after we've loaded each
841  // snippet.  This is onerous and probably unnecessary.  Here we wrap and
842  // rate limit calls to this function.
843
844  var rate_limit = 2500; // call it no more than every 2.5 seconds?
845
846  if (!setup_info_links_throttle) {
847    // console.log("setting throttle _setup_info_links");
848    setup_info_links_throttle = setInterval(
849      function() {
850        if (setup_info_links_request) {
851          // console.log("calling _setup_info_links");
852          _setup_info_links();
853          setup_info_links_request = false;
854        } else {
855          // if there hasn't been a request in the last interval, then
856          // we're done for now.
857          // console.log("clearing throttle _setup_info_links");
858          clearInterval(setup_info_links_throttle);
859          setup_info_links_throttle = false;
860        }
861      },
862      rate_limit
863    );
864
865    _setup_info_links();
866
867  } else {
868    // console.log("throttling _setup_info_links");
869    setup_info_links_request = true;
870  }
871}
872
873
874function _setup_info_links() {
875
876    // Create qtip pop-ups.
877
878    // button: "<img src='includes/images/action_delete.png' width='16px' height='16px' alt='close' />"
879
880    var qtip_content_helper = function(el, no_clone) {
881        /* <no_clone> is for when we don't want the contents cloned every time
882        the qtip is shown. We started cloning when we needed qtip to not
883        remove the contents from its current location; this was important when
884        doing in-page searching in the presence of tooltips. */
885
886        // only include the title if we have one.
887        if ($($(el).attr('title')).length > 0) {
888            title_opts = {
889                // Should 'this' below be 'el'?
890                text: function(api) { var title_id = $(this).attr('oldtitle'); return title_id ? $(title_id).clone() : null; },
891                button: true
892            }
893        } else {
894            title_opts = ''
895        }
896
897        if (no_clone) {
898            return {
899                title: title_opts,
900                text: function(api) { return $($(el).attr('rel')) }
901            }
902        } else {
903            return {
904                title: title_opts,
905                text: function(api) { return $($(el).attr('rel')).clone() }
906            }
907        }
908    }
909
910    // Try to make sure they can view the whole pop-up on small screens.
911    // This works better for people who need to click on scrollbars.
912    // var position_args = {adjust: {method: 'shift flip'}, viewport: $(window)};
913
914    // var position_args = {adjust: {method: 'shift shift'}, viewport: $(window)};
915
916    // Try to put it within the form so that buttons work.
917    if ($('form#thisForm').length > 0) {
918      var position_args = {adjust: {method: 'shift shift'}, container: $('form#thisForm'), viewport: $(window)};
919    } else {
920      var position_args = {adjust: {method: 'shift shift'}, viewport: $(window)};
921    }
922
923    // modal ttips
924    modal_position = {
925        my: 'center',
926        at: 'center',
927        target: $(window) // Or $(document.body), if you don't want it centered as you scroll
928    };
929
930    // This works better for people who can drag-scroll.
931    // var position_args = {adjust: {method: 'none none'}};
932    // I think the ideal would be to use force people on small screens to click the 'x' button (or 'esc' key).
933    // var position_args = {my: 'top left', at: 'bottom right', target: 'event', adjust: {method: 'shift flip'}, viewport: $(window)};
934    $('.ttip_object_info, .ttip_object_info_blue, .ttip_object_info_white').each(function() {
935        // pull other attributes out of the object
936        var width_class = $(this).attr('width_class')
937
938        var no_clone = false;
939        if ($(this).attr('clone') == 'no_clone') {
940            no_clone = true;
941        }
942
943        var show = { solo: true };
944        var hide;
945        if ($(this).attr('hover') != 'hover') {
946            show['event'] = 'click';
947            hide = { event: 'unfocus', leave: false };
948        }
949
950        var classes = 'qtip-rm qtip-rounded rm-qtip '
951        var position = position_args;
952        if ($(this).attr('modal') == 'modal') {
953            show['event'] = 'click';
954            show['modal'] = { on: true };
955            hide = { event: 'unfocus', leave: false };
956            position = modal_position;
957            classes = 'qtip-rm qtip-rounded rm-qtip rm-qtip-modal '
958        }
959
960        $(this).qtip({
961            content: qtip_content_helper(this, no_clone=no_clone),
962            show: show,
963            hide: hide,
964            style: {widget: false, classes: classes + width_class, tip: {width: 15, height: 10}},
965            position: position
966        })
967    })
968}
969
970
971function setup_tab_menu_qtips() {
972
973    // the hide and show objects used in calling qtip needs to be different if we're on a touchscreen.
974    var hide_args = {
975        fixed: true,
976        delay: 50,
977        effect: function() { $(this).slideUp(50); }
978    }
979    var show_args = {
980        solo: true,
981        event: 'hover',
982        delay: 100,
983        effect: function() { $(this).slideDown(100); }
984    }
985
986
987    if('ontouchstart' in document.documentElement) {
988        var hide_args = {
989            fixed: true,
990            delay: 50,
991            event: 'unfocus',
992            effect: function() { $(this).slideUp(50); }
993        }
994        var show_args = {
995            solo: true,
996            event: 'click',
997            delay: 100,
998            effect: function() { $(this).slideDown(100); }
999        }
1000    }
1001
1002    $('.tab_menu_label').each(function() {
1003        $(this).qtip({
1004            content: { text: function(api) { return $($(this).attr('rel')) } },
1005            // show: {solo: true, event: 'hover', delay: 100,
1006            //     effect: function() { $(this).slideDown(100); }
1007            // },
1008            show: show_args,
1009            // hide: {fixed: true, delay: 50,
1010            //     effect: function() { $(this).slideUp(50); }
1011            // },
1012            hide: hide_args,
1013            events: {
1014                show: function(event, api) { $(api.elements.target).parent().addClass('active') },
1015                hide: function(event, api) { $(api.elements.target).parent().removeClass('active') }
1016            },
1017            style: {
1018                widget: false, classes: 'qtip-rm-tab-menu', tip: false,
1019            },
1020            position: {
1021                adjust: {method: 'none none'},
1022                // viewport: $(window),
1023                my: 'top left', at: 'bottom left', adjust: {y: 1}
1024            }
1025        })
1026    })
1027}
1028
1029
1030function setup_filterable_lists() {
1031
1032	// custom css expression for a case-insensitive contains()
1033	jQuery.expr[':'].Contains = function(a,i,m){
1034	      return (a.textContent || a.innerText || "").toUpperCase().indexOf(m[3].toUpperCase())>=0;
1035	    };
1036
1037	// simple list filter for javascript.filterable_eo_list()
1038  $(".list_filter")
1039    .change( function () {
1040
1041	  var filter = $(this).val();
1042      var filt_list = $("#filterable_list_" + $(this).attr("id"));
1043      if(filter) {
1044        // this finds all links in a list that contain the input,
1045        // and hide the ones not containing the input while showing the ones that do
1046
1047		//filt_list.find("div:not(:Contains(" + filter + "))").parent().slideUp();
1048        //filt_list.find("div:Contains(" + filter + ")").parent().slideDown();
1049
1050		filt_list.find(".filt_str:not(:Contains(" + filter + "))").parent().hide();
1051        filt_list.find(".filt_str:Contains(" + filter + ")").parent().show();
1052
1053		//filt_list.hide();
1054      } else {
1055        filt_list.find(".filt_str").parent().show();
1056      }
1057      return false;
1058    })
1059  .keyup( function () {
1060      // fire the above change event after every letter
1061      $(this).change();
1062  });
1063
1064  return false;
1065
1066  // like the above (sortof), but specifically tailored for filtering the roadmap
1067  // This is now handled by an insta filter setup in rm_page.py
1068  // $(".roadmap_filter")
1069  //   .change( function () {
1070  // $(document)
1071  //   .on( 'change', ".roadmap_filter", function () {
1072  //
1073  //     var filter = $(this).val();
1074  //     var filt_list = $("#filterable_list_" + $(this).attr("id"));
1075  //     if(filter) {
1076  //     // remove all highlights
1077  //     //filt_list.find("li>span").removeHighlight();
1078  //     filt_list.find("li").removeHighlight();
1079  //     //filt_list.find(".glossary>div>span").removeHighlight();
1080  //     filt_list.find(".glossary>div").removeHighlight();
1081  //
1082  //       // this finds all li elements that contain the input,
1083  //       // and hide the ones not containing the input while showing the ones that do
1084  //     //filt_list.find("li>span:not(:Contains(" + filter + "))").parent().hide();
1085  //     filt_list.find("li:not(:Contains(" + filter + "))").hide();
1086  //     //filt_list.find("li>span:Contains(" + filter + ")").each(function(){
1087  //       filt_list.find("li:Contains(" + filter + ")").each(function(){
1088  //       el = $(this);
1089  //       el.show();
1090  //         el.parents("li").show();
1091  //           if (filter.length > 2) {
1092  //         // rather just doing el.highlight() we highlight in el's children
1093  //         // that are not <ul>.  This eliminates a bug where we double highlight
1094  //         // li>ul>li constructs, resulting in strangely eliminated text when
1095  //         // removing highlights.
1096  //         el.children().not("ul")
1097  //           .highlight(filter);
1098  //       }
1099  //       });
1100  //
1101  //     // this handles the glossary
1102  //     //filt_list.find(".glossary>div>span:not(:Contains(" + filter + "))").parent().hide();
1103  //     filt_list.find(".glossary>div:not(:Contains(" + filter + "))").hide();
1104  //       //filt_list.find(".glossary>div>span:Contains(" + filter + ")").each(function(){
1105  //       filt_list.find(".glossary>div:Contains(" + filter + ")").each(function(){
1106  //       el = $(this);
1107  //       el.show();
1108  //           if (filter.length > 2) {
1109  //         el.highlight(filter);
1110  //       }
1111  //     });
1112  //
1113  //     // yoink the glossary header
1114  //     $("#glossary-header").hide();
1115  //
1116  //     // make sure all the sections are expanded or not, as appropriate
1117  //     // basically, close and remove the empty ones, and show and open the ones with content
1118  //     filt_list.find("div>a.clickable").each(function(){
1119  //       link = $(this);
1120  //       if (link.css('display') != 'none') { // there are usually two of these links that toggle... we want the currently visible one
1121  //
1122  //         list_container = link.parent().find("div.arrow-slidedown>ol");
1123  //         if (!list_container.length) {
1124  //           list_container = link.parent().find("div.arrow-slidedown>blockquote");
1125  //           items = "div"
1126  //         } else {
1127  //           items = "li"
1128  //         }
1129  //         is_shown = list_container.is(":visible");
1130  //         should_show = false;
1131  //         list_container.children(items).each(function(){
1132  //           if ($(this).css('display') != 'none') { should_show = true; }
1133  //         });
1134  //
1135  //         // open or close the the section
1136  //         if (is_shown != should_show) {
1137  //           link.click();
1138  //         }
1139  //
1140  //         // show or hide the section
1141  //         if (!should_show) {
1142  //           link.parent().hide();
1143  //         } else {
1144  //           link.parent().show();
1145  //         }
1146  //
1147  //       }
1148  //     });
1149  //
1150  //     } else {
1151  //       // show all items, remove highlights
1152  //       //filt_list.find("li>span").each(function(){
1153  //       filt_list.find("li").each(function(){
1154  //       el = $(this);
1155  //       el.removeHighlight();
1156  //       el.show();
1157  //     });
1158  //     //filt_list.find(".glossary>div>span").each(function(){
1159  //     filt_list.find(".glossary>div").each(function(){
1160  //       el = $(this);
1161  //       el.removeHighlight();
1162  //       el.show();
1163  //     });
1164  //
1165  //     // but close and show all sections
1166  //     filt_list.find("div>a.clickable").each(function(){
1167  //       link = $(this);
1168  //       if (link.css('display') != 'none') { // there are usually two of these links that toggle... we want the currently visible one
1169  //         list_container = link.parent().find("div.arrow-slidedown>ol");
1170  //         if (!list_container.length) {
1171  //           list_container = link.parent().find("div.arrow-slidedown>blockquote");
1172  //         }
1173  //         is_shown = list_container.is(":visible");
1174  //         if (is_shown) {
1175  //           link.click();
1176  //         }
1177  //         link.parent().show();
1178  //       }
1179  //     });
1180  //
1181  //     // put back glossary header
1182  //     $("#glossary-header").show();
1183  //     }
1184  //     return false;
1185  //   })
1186  // // .keyup( function () {
1187  // .on( 'keyup', ".roadmap_filter", function () {
1188  //     // fire the above change event after every letter
1189  //     $(this).change();
1190  // });
1191}
1192
1193
1194// ******************* reordering functions *********************************
1195
1196// ###################### not yet working
1197
1198function make_deletes_persistant(div_class) {
1199    $("." + div_class).each(function(i) {
1200        if($(this).children(':input[name$="delete"]').val() == "1") {
1201            $(this).hide()
1202            $(this).removeClass(div_class)
1203            $(this).parent().append($(this))
1204        }
1205    });
1206    return false
1207}
1208
1209
1210function sort_by_order_field(a, b) {
1211    var a_order = $(a).children(':input[name$="order"]').val()
1212    //var a_order = $(a'> input[name$="order"]').val()
1213    var b_order = $(b).children(':input[name$="order"]').val()
1214    //var b_order = $(b + '> input[name$="order"]').val()
1215    return (a_order < b_order) ? -1 : (a_order > b_order) ? 1 : 0;
1216}
1217
1218
1219function make_author_sort_persistant(div_class) {
1220    var authors = $('.' + div_class + ':visible')
1221    var template = $('.' + div_class + ':hidden')
1222    //authors.hide()
1223    authors.sort(sort_by_order_field)
1224    authors.each(function(i) {
1225        $(this).parent().children(':nth-child('+(i+2)+')').after($(this))
1226    })
1227    //authors.show()
1228    return false
1229}
1230
1231
1232function hide_show_from_selector_val($selector, suffix, instant) {
1233    // shows elements with class of selector.val()+suffix
1234    // hides elements with class of any non-selected selector vals + suffix
1235    // Use 'instant' to skip the animation - often needed when form elements are to be hide/shown.
1236
1237    // hide all
1238    $selector.children("option").each(function() {
1239        non_selected_val = $(this).val();
1240        non_selected_el = $("."+non_selected_val+suffix);
1241        if (instant) { non_selected_el.hide(); } else { non_selected_el.slideUp(); }
1242    });
1243
1244    // show selected
1245    sel_val = $selector.val();
1246    selected_el = $("."+sel_val+suffix);
1247    if (instant) { selected_el.show(); } else { selected_el.slideUp(); }
1248
1249    return false;
1250}
1251
1252
1253// ################### end not yet working
1254
1255function _______MARKDOWN______() { }
1256
1257// MARKED
1258// https://marked.js.org/#/USING_ADVANCED.md
1259// marked.setOptions({
1260//     pedantic: false,
1261//     breaks: true
1262// });
1263// function __markdown(text) {
1264//     var html = marked(text);
1265//     return html;
1266// }
1267
1268// SHOWDOWN
1269// https://github.com/showdownjs/showdown/wiki/Showdown-options
1270
1271
1272var escape_parens_for_mathjax = function (converter) {
1273    // it could be that we just want to automatically replace slashes with double slashes
1274    return [
1275        {
1276            type: 'lang',
1277            regex: /\\\(.*?\\\)/g,
1278            replace: function (match, content) {
1279                return '\\\\(' + match.slice(2, -2) + '\\\\)';
1280            }
1281        },
1282        {
1283            type: 'lang',
1284            regex: /\\\[.*?\\\]/g,
1285            replace: function (match, content) {
1286                // This one seems to work, but will probably break if [] is used inside the latex
1287                // return match.replace('\[', '\\[').replace('\]', '\\]')
1288                return '\\\\[' + match.slice(2, -2) + '\\\\]';
1289            }
1290        },
1291        {
1292            type: 'lang',
1293            regex: /\\\{.*?\\\}/g,
1294            replace: function (match, content) {
1295                return '\\\\{' + match.slice(2, -2) + '\\\\}';
1296            }
1297        },
1298    ];
1299};
1300
1301
1302var escape_greater_thans_for_blockquote = function (converter) {
1303    // Allow greater thans as the first thing on a line.
1304    return [
1305        {
1306            type: 'lang',
1307            regex: /<br>\s*&gt;/gm,
1308            replace: function (match, content) {
1309                return '<br>>';
1310            }
1311        },
1312        {
1313            type: 'lang',
1314            regex: /^\s*&gt;/gm,
1315            replace: function (match, content) {
1316                return '>';
1317            }
1318        },
1319    ];
1320};
1321
1322
1323function __markdown(text) {
1324    var converter = new showdown.Converter({
1325        // tables: true,
1326        simpleLineBreaks: true,
1327        extensions: [escape_parens_for_mathjax, escape_greater_thans_for_blockquote],
1328        literalMidWordUnderscores: true
1329    });
1330    var html = converter.makeHtml(text);
1331    return html;
1332}
1333
1334
1335function emulate_prep_text(text) {
1336    // Treat text the same way we do coming out of the db. See html_util.prep_text()
1337    text = text.replace(/&/g, '&amp;');
1338    text = text.replace(/>/g, '&gt;').replace(/</g, '&lt;');
1339
1340    return text;
1341}
1342
1343
1344function unescape_for_showdown_display(text) {
1345
1346    // Showdown double-escapes '<>' in code sections. Undo this for display.
1347    text = text.replace(/&amp;/g, '&');
1348
1349    return text
1350}
1351
1352
1353function preview_markdown(source_id, target_id, render_mathjax) {
1354    var text = document.getElementById(source_id).value;
1355
1356    text = emulate_prep_text(text);
1357
1358    var target = document.getElementById(target_id);
1359    var html = __markdown(text);
1360
1361    html = unescape_for_showdown_display(html)
1362
1363    if (!html) {
1364        html = "<br><br><div style='color: gray; font-style: italic; text-align: center;'>No text has been entered yet, so there is nothing to preview.</div><br><br>"
1365    }
1366    target.innerHTML = html;
1367    if (render_mathjax) {
1368        MathJax.Hub.Queue(["Typeset", MathJax.Hub, target]);
1369    }
1370}
1371
1372// This allows common punctuation as well as spaces for the character after a closing *.
1373// var good_markdown_star_regex = /(?:\s|^)\*+[^\*]+\*+(?:\s|$|[.,:;])/gm
1374function looks_like_math(paragraph) {
1375    var matches = paragraph.match(/\*+/g);
1376    if (matches && matches.length > 1) {
1377        // Remove cases that look like good italics.
1378        paragraph = paragraph.replace(GOOD_MARKDOWN_STAR_REGEX, '')
1379        paragraph = paragraph.replace(MARKDOWN_WORDCOUNT_REGEX, '')
1380        // Any remaining *s look like math.
1381        var math_star_matches = paragraph.match(/\*+/g);
1382        if (math_star_matches != null) {
1383            return true;
1384        }
1385    }
1386
1387    return false;
1388}
1389
1390
1391var markdown_sects = 0;
1392function process_markdown(i, el) {
1393    var contents = $(el).clone();
1394
1395    contents.find("br").replaceWith("\n");
1396    var result = __markdown(contents.html());
1397
1398    // replace <p> with <br><br>
1399    var comp_result = $('<div>' + result + '</div>');
1400    comp_result.find('p').replaceWith(function(el) {
1401        var ans = $(this).html();
1402        if (comp_result.find('p').length > 1) {
1403            ans = ans + '<br><br>';
1404        }
1405        return ans;
1406    })
1407
1408    result = comp_result.html()
1409    // Get rid of trailing BRs
1410    result = result.replace(/(<br>\s*)+$/, '')
1411    // Get rid of extra spaces
1412    // result = result.replace(/\s+/g, ' ').trim()
1413
1414    if (result.trim().replace(/\s+/g, '').replace(/<br>/g, '') == contents.html().trim().replace(/\s+/g, '')) {
1415        return;
1416    } else {
1417        //  If a paragraph looks like it contains *s in a mathy way don't show the markdown view on load.
1418        var show_orig = false;
1419        var paragraphs = contents.html().split('\n\n');
1420        $(paragraphs).each(function(i, el) {
1421            show_orig = looks_like_math(this);
1422            if (show_orig) { return false; };
1423        });
1424
1425        var orig_markdown_sect_class = 'markdown_sect_' + markdown_sects;
1426        var new_markdown_sect_class = orig_markdown_sect_class + '_markdown';
1427        var toggle_link_class = orig_markdown_sect_class + '_toggle';
1428
1429        markdown_sects = markdown_sects + 1;
1430
1431        // Add class to old section
1432        $(el).wrapInner('<div class="' + orig_markdown_sect_class + '">');
1433        $('.' + orig_markdown_sect_class).hide();
1434
1435        result = unescape_for_showdown_display(result)
1436
1437        // Put in new section
1438        // var new_sect = $("<div style='color: red;'>" + result + "</div>");
1439        var new_sect = $("<div>" + result + "</div>");
1440        new_sect.addClass(new_markdown_sect_class);
1441        $('.' + orig_markdown_sect_class).after(new_sect);
1442
1443        // Put in toggle links
1444        var links = $(`
1445            <span class="` + toggle_link_class + `">
1446                <div class="active_toggle_button clickable" title="Toggle whether the text is formatted by Markdown or not.">markdown</div>
1447            </span>
1448        `);
1449        links.push($(`
1450            <span class="` + toggle_link_class + `">
1451                <div class="inactive_toggle_button clickable" title="Toggle whether the text is formatted by Markdown or not.">markdown</div>
1452            </span>
1453        `).hide());
1454
1455        var wrapper = $('<div style="float: right;"></div>');
1456        links.insertBefore('.' + orig_markdown_sect_class);
1457        links.wrap(wrapper);
1458
1459        var clear = $('<div></div>').addClass('clear');
1460        clear.insertBefore('.' + orig_markdown_sect_class);
1461
1462        $('.' + toggle_link_class).click(function () {
1463            $('[class^=' + orig_markdown_sect_class).toggle();
1464        })
1465
1466        if (show_orig) {
1467            $('[class^=' + orig_markdown_sect_class).toggle();
1468        }
1469    }
1470}
1471
1472
1473function toggle_monospace(el) {
1474    $(el).toggleClass('monospace');
1475    $(el).find('.toggle_monospace').toggle();
1476}
1477
1478
1479function process_monospace(i, el) {
1480
1481    // Put in toggle links
1482    var links = $();
1483    links.push($(`
1484        <span class="toggle_monospace">
1485            <div class="inactive_toggle_button clickable" title="Toggle whether the font letter-width is fixed or proportional.">
1486                monospace
1487            </div>
1488        </span>
1489    `).hide());
1490    links.push($(`
1491        <span class="toggle_monospace">
1492            <div class="active_toggle_button clickable" title="Toggle whether the font letter-width is fixed or proportional.">
1493                monospace
1494            </div>
1495        </span>
1496    `));
1497
1498    var wrapper = $('<div style="float: right;"></div>');
1499    links.prependTo(wrapper);
1500    wrapper.prependTo(el);
1501
1502    // This may already be there from the markdown
1503    if ($(el).find('.clear').length == 0){
1504        var clear = $('<div></div>').addClass('clear');
1505        clear.insertAfter(wrapper);
1506    }
1507
1508    $(el).find('.toggle_monospace').click(function() {toggle_monospace(el);})
1509    toggle_monospace(el);
1510}
1511
1512
1513function _______MULTI_BLOCK_BUTTONS______() {}
1514
1515
1516function delete_mb_entry(min, max, button_class, uniq, div_class, to_delete, delete_name) {
1517
1518    /* The 'delete entry' button was pressed; don't clear deleted fields. */
1519
1520    var to_delete = $("#" + to_delete)
1521    to_delete.animate({
1522        "height": "toggle"
1523    }, "slow", function() {
1524        to_delete.removeClass(div_class)
1525        to_delete.parent().append(to_delete)
1526        jQuery("#" + delete_name).val("1")
1527        fix_multi_block_buttons(min, max, div_class, button_class, uniq)
1528    })
1529    return false
1530}
1531
1532
1533function delete_mb_entry_and_clear(min, max, button_class, uniq, div_class, to_delete, delete_name) {
1534
1535    /* The 'delete entry' button was pressed; clear out deleted fields. */
1536
1537    var to_delete = $("#" + to_delete)
1538    to_delete.animate({
1539        "height": "toggle"
1540    }, "slow", function() {
1541        to_delete.find('input').val('') // Clear fields in the deleted entry. (What about textarea and select?)
1542        to_delete.removeClass(div_class)
1543        to_delete.parent().append(to_delete)
1544        jQuery("#" + delete_name).val("1")
1545        fix_multi_block_buttons(min, max, div_class, button_class, uniq)
1546    })
1547    return false
1548}
1549
1550
1551function get_tinymce_id_settings_map_for_container(container) {
1552    // Find any tinyMCE instances in container.
1553    // store their settings and remove the instance.
1554
1555    var tinymce_settings_map = {};
1556    var editors = tinymce.editors;
1557
1558    $.each(editors, function(i, editor) {
1559        if (typeof editor != 'undefined') {
1560            var editor_id = editor.id;
1561            var editor_dom_ob = $('#' + editor_id)
1562            if (container.has(editor_dom_ob).length > 0) {
1563                tinymce_settings_map[editor_id] = editor.settings;
1564                tinymce.remove('#' + editor_id);
1565            }
1566        }
1567    })
1568    return tinymce_settings_map;
1569}
1570
1571
1572function tinymce_editor_exists(tinymce_id) {
1573    for (var i in tinymce.editors) {
1574        if (tinymce.editors[i].id == tinymce_id) {return true}
1575    }
1576    return false
1577}
1578
1579
1580function restore_tinymce_instances_from_map(tinymce_settings_map) {
1581    // Restore the tinymce instances.
1582    for (var tinymce_id in tinymce_settings_map) {
1583        if (!tinymce_settings_map.hasOwnProperty(tinymce_id)) {
1584            //The current property is not a direct property of tinymce_settings_map
1585            continue;
1586        }
1587        // Do not make it if there is already one with that id.
1588        if (tinymce_editor_exists(tinymce_id)) {
1589            continue;
1590        }
1591
1592        var editor = new tinymce.Editor(tinymce_id, tinymce_settings_map[tinymce_id], tinymce.EditorManager);
1593        editor.render();
1594    }
1595}
1596
1597
1598function handle_move_mb_entry_up(min, max, button_class, uniq, div_class, current) {
1599
1600    /* The 'move entry up' button was pressed. */
1601
1602    var tinymce_settings_map = {};
1603
1604    var current = $("#" + current);
1605    var index = current.children("." + uniq + "_reorder").val();
1606    if(index > 0) {
1607        current.animate({
1608            "opacity": "toggle"
1609        }, "slow", function() {
1610            // Find any tinyMCE instances that will be moved.
1611            // store their settings and remove the instance.
1612            var moving_el = current.prev("." + div_class);
1613            tinymce_settings_map = get_tinymce_id_settings_map_for_container(moving_el);
1614            current.after(current.prev("." + div_class));
1615        }).animate({
1616            "opacity": "toggle"
1617        }, "slow", function() {
1618            fix_multi_block_buttons(min, max, div_class, button_class, uniq);
1619            restore_tinymce_instances_from_map(tinymce_settings_map);
1620        });
1621    }
1622    return false;
1623}
1624
1625
1626function handle_move_mb_entry_down(min, max, button_class, uniq, div_class, current) {
1627
1628    /* The 'move entry down' button was pressed. */
1629    var tinymce_settings_map = {};
1630
1631    var current = $("#" + current);
1632    var curr_max = $("." + div_class).length-2; //length-2 because of template div and zero-indexing.
1633    var index = current.children("." + uniq + "_reorder").val();
1634    if(index < curr_max) {
1635        current.animate({
1636            "opacity": "toggle"
1637        }, "slow", function() {
1638            var moving_el = current;
1639            // Find any tinyMCE instances that will be moved.
1640            // store their settings and remove the instance.
1641            tinymce_settings_map = get_tinymce_id_settings_map_for_container(moving_el);
1642            current.next("." + div_class).after(current);
1643        }).animate({
1644            "opacity": "toggle"
1645        }, "slow", function() {
1646            fix_multi_block_buttons(min, max, div_class, button_class, uniq);
1647            restore_tinymce_instances_from_map(tinymce_settings_map);
1648        });
1649    }
1650    return false;
1651}
1652
1653
1654function get_multi_block_count(div_class) {
1655
1656    /* Get the count of active, non-template/deleted multi-blocks whether the
1657    whole thing is hidden or not. */
1658
1659    var blocks = $("."+div_class);
1660    var count = 0;
1661    blocks.each(function() {
1662        var delete_field = $(this).find("[name$='delete']");
1663        // ignore template block
1664        if (delete_field.attr('name').indexOf('%%') == -1) {
1665            // ignore deleted blocks
1666            if (delete_field.val() != 1) {
1667                count += 1;
1668            }
1669        }
1670    })
1671    return count;
1672}
1673
1674
1675function fix_multi_block_buttons(min, max, div_class, button_class, uniq) {
1676
1677    /* Fix which up/down reorder buttons, delete buttons, and add entry
1678    buttons are showing. <min> and <max> are the required min and max allowed.
1679    */
1680
1681    // This is unreliable as we are hiding auth sections.
1682    // var count = $("."+div_class+":visible").length
1683    var count = get_multi_block_count(div_class)
1684
1685    // Fix delete buttons.
1686    if(count <= min) {
1687        $("."+uniq+'_delete_button').hide()
1688    } else {
1689        $("."+uniq+'_delete_button').show()
1690    }
1691
1692    // Fix add entry button.
1693    if(max == 0) {
1694        // No max.
1695        $("."+button_class).show()
1696    } else {
1697        if(count >= max) {
1698            $("."+button_class).hide()
1699        } else {
1700            $("."+button_class).show()
1701        }
1702    }
1703
1704    // Fix reorder buttons.
1705    if(count <= 1) {
1706        // No need for reorder buttons.
1707        $("."+uniq+"_reorder_button_up").hide()
1708        $("."+uniq+"_reorder_button_down").hide()
1709    } else {
1710        // Up button on all but first.
1711        $("."+uniq+"_reorder_button_up").show()
1712        $("."+uniq+"_reorder_button_up:first").hide()
1713        // Down button on all but last (last visible).
1714        $("."+uniq+"_reorder_button_down").show()
1715        $("."+uniq+"_reorder_button_down:visible:last").hide()
1716    }
1717
1718    // When we load these pages we want the numbers to straighten themselves out in case the user doesn't change anything pre-submit.
1719    $("." + uniq + "_reorderable > ." + uniq + "_reorder").each(function(i) {
1720        $(this).val(i);
1721    });
1722}
1723
1724
1725function _______OTHER______() {}
1726
1727
1728// Return a helper with preserved width of cells
1729var fixHelper = function(e, ui) {
1730    ui.children().each(function() {
1731        $(this).width($(this).width());
1732    });
1733    return ui;
1734};
1735
1736
1737// This is for dhtmlx grids.
1738function str_natural(a, b, order) {
1739  function chunkify(t) {
1740    var tz = new Array();
1741    var x = 0, y = -1, n = 0, i, j;
1742
1743    while (i = (j = t.charAt(x++)).charCodeAt(0)) {
1744      var m = (i == 46 || (i >=48 && i <= 57));
1745      if (m !== n) {
1746        tz[++y] = "";
1747        n = m;
1748      }
1749      tz[y] += j;
1750    }
1751    return tz;
1752  }
1753
1754  var aa = chunkify(a.toLowerCase());
1755  var bb = chunkify(b.toLowerCase());
1756
1757  for (x = 0; aa[x] && bb[x]; x++) {
1758    if (aa[x] !== bb[x]) {
1759      var c = Number(aa[x]), d = Number(bb[x]);
1760      if (c == aa[x] && d == bb[x]) {
1761        return (c - d)*(order=="asc"?1:-1);
1762      } else return ((aa[x] > bb[x]) ? 1 : -1)*(order=="asc"?1:-1);
1763    }
1764  }
1765  return (aa.length - bb.length)*(order=="asc"?1:-1);
1766}
1767
1768
1769function _______SET_UP_SORTABLES______() {}
1770
1771/*
1772we have two different sortable tools,
1773jquery_ui_sortable doesn't work in IE 9 & 10
1774and the other doesn't work in colorboxes in safari.
1775Decide here which we are using based on the users browser.
1776
1777new one from:
1778https://github.com/RubaXa/Sortable
1779
1780A note about args that are functions: jquery_ui_sortable sends two arguments into
1781functions: <event>, <ui>, while the other just sends <event>. The two <event>
1782variables are not equivilant though. In jquery_ui_sortable the object that was
1783just sorted is ui.item, and it is event.item in the new one. If you're going to
1784use the arguments passed in you need to give two functions that use the
1785appropriate arguments.
1786*/
1787
1788$(function() { // Wait till the page is loaded so we're sure both kinds are there.
1789	if ($.browser.msie  && parseInt($.browser.version, 10) > 8) {
1790      jQuery.fn.extend({
1791          rm_sortable: function(args) {
1792			  var new_args = {}
1793			  $.each(args, function(key, val) {
1794				  if (key == 'start' && !('onStart' in args)) {
1795					  new_args['onStart'] = val;
1796				  } else if (key == 'stop' && !('onEnd' in args)) {
1797					  new_args['onEnd'] = val;
1798				  } else if (key == 'update' && !('onUpdate' in args)) {
1799					  new_args['onUpdate'] = val;
1800				  } else if (key == 'helper') {
1801					  // ignore this
1802				  } else {
1803					  new_args[key] = val;
1804				  }
1805			  });
1806              return this.sortable(new_args);
1807          }
1808      });
1809	} else {
1810      jQuery.fn.extend({
1811          rm_sortable: function(args) {
1812			  var new_args = {}
1813			  $.each(args, function(key, val) {
1814				  if (key == 'onStart' && !('start' in args)) {
1815					  new_args['start'] = val;
1816				  } else if (key == 'onEnd' && !('stop' in args)) {
1817					  new_args['stop'] = val;
1818				  } else if (key == 'onUpdate' && !('update' in args)) {
1819					  new_args['update'] = val;
1820				  } else if (key == 'ghostClass') {
1821					  // ignore this
1822				  } else {
1823					  new_args[key] = val;
1824				  }
1825			  });
1826              return this.jquery_ui_sortable(new_args);
1827          }
1828      });
1829	}
1830})
1831
1832// When the document is ready set up our sortable with it's inherant function(s)
1833// function make_table_sortable(table_id) {
1834//     var width = jQuery("#" + table_id).width()
1835//     $("#" + table_id + " tbody > tr:even").removeClass('odd even').addClass('odd')
1836//     $("#" + table_id + " tbody > tr:odd").removeClass('odd even').addClass('even')
1837//
1838//     $("#" + table_id + " tbody").sortable({
1839//         // axis: 'y', // This causes strange behaviour.
1840//         start: function(event, ui) {
1841//             $("body").disableSelection()
1842//             $("#" + table_id).width(width)
1843//         },
1844//         stop: function(event, ui) { $("body").enableSelection() },
1845//         helper: fixHelper,
1846//         handle: '.ui-icon-arrowthick-2-n-s',
1847//         update: function(event, ui) {
1848//             $(":input[name$='order']:hidden").each(function(i) {
1849//                 $(this).val(i);
1850//             });
1851//             $("#" + table_id + " tbody > tr:even").removeClass('odd even').addClass('odd');
1852//             $("#" + table_id + " tbody > tr:odd").removeClass('odd even').addClass('even');
1853//             // Don't give even-odd classes to the header rows.
1854//             $("#" + table_id + " tbody > tr > th").parents("#" + table_id + " tbody > tr").removeClass('odd even');
1855//         }
1856//     })
1857//     // In the beginning, fill in any missing order values.
1858//     $(":input[name$='order']:hidden").each(function(i) {
1859//         $(this).val(i);
1860//     });
1861//     // Don't give even-odd classes to the header rows.
1862//     $("#" + table_id + " tbody > tr > th").parents("#" + table_id + " tbody > tr").removeClass('odd even');
1863// }
1864
1865// jQuery UI sortable breaks when you have to scroll in IE.
1866// This version uses the sortable from https://github.com/RubaXa/Sortable.
1867function make_table_sortable(table_id) {
1868    var width = jQuery("#" + table_id).width()
1869    $("#" + table_id + " > tbody > tr:even").removeClass('odd even').addClass('odd')
1870    $("#" + table_id + " > tbody > tr:odd").removeClass('odd even').addClass('even')
1871
1872    $("#" + table_id + " > tbody").rm_sortable({
1873		// Shared options:
1874        handle: '.ui-icon-arrowthick-2-n-s',
1875
1876		start: function(event, ui) {
1877		    $("body").disableSelection()
1878		    $("#" + table_id).width(width)
1879		},
1880		stop: function(event, ui) { $("body").enableSelection() },
1881		update: function(event, ui) {
1882		    $(":input[name$='order']:hidden").each(function(i) {
1883		        $(this).val(i);
1884		    });
1885		    $("#" + table_id + " > tbody > tr:even").removeClass('odd even').addClass('odd');
1886		    $("#" + table_id + " > tbody > tr:odd").removeClass('odd even').addClass('even');
1887		    // Don't give even-odd classes to the header rows.
1888		    $("#" + table_id + " > tbody > tr > th").parents("#" + table_id + " > tbody > tr").removeClass('odd even');
1889		},
1890		// Options for jquery_ui_sortable:
1891		helper: fixHelper,
1892
1893		// Options for ie_sortable
1894		ghostClass: 'ghost'
1895    })
1896    // In the beginning, fill in any missing order values.
1897    $(":input[name$='order']:hidden").each(function(i) {
1898        $(this).val(i);
1899    });
1900    // Don't give even-odd classes to the header rows.
1901    $("#" + table_id + " tbody > tr > th").parents("#" + table_id + " tbody > tr").removeClass('odd even');
1902}
1903
1904
1905// Global cache for domain-affiliation used to autofill affiliations when a domain has already been used in a form.
1906var domain_aff_map = {}
1907function fill_dflt_aff_from_email(email_fld, aff_fld, mapping) {
1908
1909    // Helps with institution management when filling out contact info / 
1909sub forms.
1910    $(document).on('change blur', "input[name='"+email_fld+"']", function() {
1911        var email = $(this).attr('value')
1912        if (email && email.indexOf('@')) {
1913            var domain = email.slice(email.indexOf('@')+1)
1914            fill_dflt(aff_fld, domain)
1915        }
1916    })
1917    // Fill domain_aff_map on email field blur with domain - affiliation pair.
1918    $(document).on('blur', "input[name='"+email_fld+"']", function() {
1919        var email = $(this).attr('value')
1920        var aff = $("input[name='"+aff_fld+"']").val()
1921        if (aff && email && email.indexOf('@')) {
1922            var domain = email.slice(email.indexOf('@')+1)
1923            domain_aff_map[domain] = aff
1924        }
1925    })
1926    // Fill domain_aff_map on affiliation field blur with domain - affiliation pair.
1927    $(document).on('blur', "input[name='"+aff_fld+"']", function() {
1928        var email = $("input[name='"+email_fld+"']").val()
1929        var aff = $(this).val()
1930        if (aff && email && email.indexOf('@')) {
1931            var domain = email.slice(email.indexOf('@')+1)
1932            domain_aff_map[domain] = aff
1933        }
1934    })
1935
1936    function fill_dflt(aff_f, domain) {
1937        var aff_el = $("input[name='"+aff_f+"']")
1938        if (!aff_el.attr('value')) {
1939            var dflt = mapping[domain]
1940            if(dflt != undefined) {
1941                // Fill the affiliation using the site wide domain-aff map.
1942                aff_el.attr('value', dflt)
1943            } else {
1944                // If there isn't a value for the domain in the pre-approved mapping, use other examples from the same form.
1945                var dflt = domain_aff_map[domain]
1946                if(dflt != undefined) {
1947                    aff_el.attr('value', dflt)
1948                }
1949            }
1950        }
1951    }
1952}
1953
1954// Global cache for affiliation-country used to autofill countries when an aff has already been used in a form.
1955var aff_country_map = {}
1956function fill_dflt_country_from_aff(aff_fld, country_fld, mapping) {
1957
1958    $(document).on('change blur', "input[name='"+aff_fld+"']", function() {
1959        var aff = $(this).attr('value').toLowerCase();
1960        if (aff) {
1961            fill_dflt(country_fld, aff);
1962        }
1963    })
1964    // Fill aff_country_map on aff field blur with aff - country pair.
1965    $(document).on('blur', "input[name='"+aff_fld+"']", function() {
1966        var aff = $(this).attr('value').toLowerCase();
1967        var country = $("[name='"+country_fld+"']").val();
1968        if (aff && country) {
1969            aff_country_map[aff] = country;
1970        }
1971    })
1972    // Fill aff_country_map on country field blur with aff - country pair.
1973    $(document).on('blur', "[name='"+country_fld+"']", function() {
1974        var aff = $("input[name='"+aff_fld+"']").val().toLowerCase()
1975        var country = $(this).val()
1976        if (aff && country) {
1977            aff_country_map[aff] = country;
1978        }
1979    })
1980
1981    function fill_dflt(country_fld, aff) {
1982        var country_el = $("[name='"+country_fld+"']")
1983        if (!country_el.attr('value')) {
1984            var dflt = mapping[aff];
1985            if(dflt != undefined) {
1986                // Fill the affiliation using the site wide domain-aff map.
1987                country_el.attr('value', dflt)
1988            } else {
1989                // If there isn't a value for the domain in the pre-approved mapping, use other examples from the same form.
1990                var dflt = aff_country_map[aff];
1991                if(dflt != undefined) {
1992                    country_el.attr('value', dflt)
1993                }
1994            }
1995        }
1996    }
1997}
1998
1999state_country_map = {
2000    'alabama': 'United States of America',
2001    'alaska': 'United States of America',
2002    'arizona': 'United States of America',
2003    'arkansas': 'United States of America',
2004    'california': 'United States of America',
2005    'colorado': 'United States of America',
2006    'connecticut': 'United States of America',
2007    'delaware': 'United States of America',
2008    'florida': 'United States of America',
2009    'georgia': 'United States of America',
2010    'hawaii': 'United States of America',
2011    'idaho': 'United States of America',
2012    'illinois': 'United States of America',
2013    'indiana': 'United States of America',
2014    'iowa': 'United States of America',
2015    'kansas': 'United States of America',
2016    'kentucky': 'United States of America',
2017    'louisiana': 'United States of America',
2018    'maine': 'United States of America',
2019    'maryland': 'United States of America',
2020    'massachusetts': 'United States of America',
2021    'michigan': 'United States of America',
2022    'minnesota': 'United States of America',
2023    'mississippi': 'United States of America',
2024    'missouri': 'United States of America',
2025    'montana': 'United States of America',
2026    'nebraska': 'United States of America',
2027    'nevada': 'United States of America',
2028    'new hampshire': 'United States of America',
2029    'new jersey': 'United States of America',
2030    'new mexico': 'United States of America',
2031    'new york': 'United States of America',
2032    'north carolina': 'United States of America',
2033    'north dakota': 'United States of America',
2034    'ohio': 'United States of America',
2035    'oklahoma': 'United States of America',
2036    'oregon': 'United States of America',
2037    'pennsylvania': 'United States of America',
2038    'rhode island': 'United States of America',
2039    'south carolina': 'United States of America',
2040    'south dakota': 'United States of America',
2041    'tennessee': 'United States of America',
2042    'texas': 'United States of America',
2043    'utah': 'United States of America',
2044    'vermont': 'United States of America',
2045    'virginia': 'United States of America',
2046    'washington': 'United States of America',
2047    'west virginia': 'United States of America',
2048    'wisconsin': 'United States of America',
2049    'wyoming': 'United States of America',
2050    'al': 'United States of America',
2051    'ak': 'United States of America',
2052    'az': 'United States of America',
2053    'ar': 'United States of America',
2054    'ca': 'United States of America',
2055    'co': 'United States of America',
2056    'ct': 'United States of America',
2057    'de': 'United States of America',
2058    'fl': 'United States of America',
2059    'ga': 'United States of America',
2060    'hi': 'United States of America',
2061    'id': 'United States of America',
2062    'il': 'United States of America',
2063    'in': 'United States of America',
2064    'ia': 'United States of America',
2065    'ks': 'United States of America',
2066    'ky': 'United States of America',
2067    'la': 'United States of America',
2068    'me': 'United States of America',
2069    'md': 'United States of America',
2070    'ma': 'United States of America',
2071    'mi': 'United States of America',
2072    'mn': 'United States of America',
2073    'ms': 'United States of America',
2074    'mo': 'United States of America',
2075    'mt': 'United States of America',
2076    'ne': 'United States of America',
2077    'nv': 'United States of America',
2078    'nh': 'United States of America',
2079    'nj': 'United States of America',
2080    'nm': 'United States of America',
2081    'ny': 'United States of America',
2082    'nc': 'United States of America',
2083    'nd': 'United States of America',
2084    'oh': 'United States of America',
2085    'ok': 'United States of America',
2086    'or': 'United States of America',
2087    'pa': 'United States of America',
2088    'ri': 'United States of America',
2089    'sc': 'United States of America',
2090    'sd': 'United States of America',
2091    'tn': 'United States of America',
2092    'tx': 'United States of America',
2093    'ut': 'United States of America',
2094    'vt': 'United States of America',
2095    'va': 'United States of America',
2096    'wa': 'United States of America',
2097    'wv': 'United States of America',
2098    'wi': 'United States of America',
2099    'wy': 'United States of America',
2100
2101    'ab': 'Canada',
2102    'alberta': 'Canada',
2103    'bc': 'Canada',
2104    'british columbia': 'Canada',
2105    'mb': 'Canada',
2106    'manitoba': 'Canada',
2107    'nb': 'Canada',
2108    'new brunswick': 'Canada',
2109    'nf': 'Canada',
2110    'newfoundland': 'Canada',
2111    'nt': 'Canada',
2112    'northwest territories': 'Canada',
2113    'ns': 'Canada',
2114    'nova scotia': 'Canada',
2115    'on': 'Canada',
2116    'ontario': 'Canada',
2117    'pe': 'Canada',
2118    'prince edward island': 'Canada',
2119    'qc': 'Canada',
2120    'quebec': 'Canada',
2121    'sk': 'Canada',
2122    'saskatchewan': 'Canada',
2123    'yt': 'Canada',
2124    'yukon': 'Canada',
2125
2126    'australian capital territory': 'Australia',
2127    'act': 'Australia',
2128    'new south wales': 'Australia',
2129    'nsw': 'Australia',
2130    'northern territory': 'Australia',
2131    'nt': 'Australia',
2132    'queensland': 'Australia',
2133    'qld': 'Australia',
2134    'south australia': 'Australia',
2135    'sa': 'Australia',
2136    'tasmania': 'Australia',
2137    'tas': 'Australia',
2138    'victoria': 'Australia',
2139    'vic': 'Australia',
2140    'western australia': 'Australia',
2141    // 'wa': 'Australia',
2142
2143    'aguascalientes': 'Mexico',
2144    'ags': 'Mexico',
2145    'baja california': 'Mexico',
2146    // 'bc': 'Mexico',
2147    'baja california sur': 'Mexico',
2148    'bcs': 'Mexico',
2149    'campeche': 'Mexico',
2150    'camp': 'Mexico',
2151    'chiapas': 'Mexico',
2152    'chis': 'Mexico',
2153    'chihuahua': 'Mexico',
2154    'chih': 'Mexico',
2155    'coahuila': 'Mexico',
2156    'coah': 'Mexico',
2157    'colima': 'Mexico',
2158    'col': 'Mexico',
2159    'distrito federal': 'Mexico',
2160    'df': 'Mexico',
2161    'durango': 'Mexico',
2162    'dgo': 'Mexico',
2163    'guanajuato': 'Mexico',
2164    'gto': 'Mexico',
2165    'guerrero': 'Mexico',
2166    'gro': 'Mexico',
2167    'hidalgo': 'Mexico',
2168    'hgo': 'Mexico',
2169    'jalisco': 'Mexico',
2170    'jal': 'Mexico',
2171    // 'méxico': 'Mexico',
2172    'mexico': 'Mexico',
2173    'edm': 'Mexico',
2174    // 'michoacán': 'Mexico',
2175    'michoacan': 'Mexico',
2176    'mich': 'Mexico',
2177    'morelos': 'Mexico',
2178    'mor': 'Mexico',
2179    'nayarit': 'Mexico',
2180    'nay': 'Mexico',
2181    // 'nuevo león': 'Mexico',
2182    'nuevo leon': 'Mexico',
2183    'nl': 'Mexico',
2184    'oaxaca': 'Mexico',
2185    'oax': 'Mexico',
2186    'puebla': 'Mexico',
2187    'pue': 'Mexico',
2188    // 'querétaro': 'Mexico',
2189    'queretaro': 'Mexico',
2190    'qro': 'Mexico',
2191    'quintana roo': 'Mexico',
2192    'qroo': 'Mexico',
2193    'san luis potosi': 'Mexico',
2194    'slp': 'Mexico',
2195    'sinaloa': 'Mexico',
2196    'sin': 'Mexico',
2197    'sonora': 'Mexico',
2198    'son': 'Mexico',
2199    'tabasco': 'Mexico',
2200    'tab': 'Mexico',
2201    'tamaulipas': 'Mexico',
2202    'tamps': 'Mexico',
2203    'tlaxcala': 'Mexico',
2204    'tlax': 'Mexico',
2205    'veracruz': 'Mexico',
2206    'ver': 'Mexico',
2207    // 'yucatán': 'Mexico',
2208    'yucatan': 'Mexico',
2209    'yuc': 'Mexico',
2210    'zacatecas': 'Mexico',
2211    'zac': 'Mexico',
2212
2213    // These are counties not states.
2214    //'baden-württemberg': 'Germany',
2215    'baden-wurttemberg': 'Germany',
2216    'bw': 'Germany',
2217    'bayern': 'Germany',
2218    'by': 'Germany',
2219    'berlin': 'Germany',
2220    'be': 'Germany',
2221    'brandenburg': 'Germany',
2222    'bb': 'Germany',
2223    'bremen': 'Germany',
2224    'hb': 'Germany',
2225    'hamburg': 'Germany',
2226    'hh': 'Germany',
2227    'hessen': 'Germany',
2228    'he': 'Germany',
2229    'mecklenburg-vorpommern': 'Germany',
2230    'mv': 'Germany',
2231    'niedersachsen': 'Germany',
2232    'ni': 'Germany',
2233    'nordrhein-westfalen': 'Germany',
2234    'nw': 'Germany',
2235    'rheinland-pfalz': 'Germany',
2236    'rp': 'Germany',
2237    'saarland': 'Germany',
2238    'sl': 'Germany',
2239    'sachsen': 'Germany',
2240    'sn': 'Germany',
2241    'sachsen-anhalt': 'Germany',
2242    'st': 'Germany',
2243    'schleswig-holstein': 'Germany',
2244    'sh': 'Germany',
2245    // 'thüringen': 'Germany',
2246    'thuringen': 'Germany',
2247    'th': 'Germany',
2248
2249    // These are counties
2250    'carlow': 'Ireland',
2251    'cw': 'Ireland',
2252    'cavan': 'Ireland',
2253    'cn': 'Ireland',
2254    'clare': 'Ireland',
2255    'ce': 'Ireland',
2256    'cork': 'Ireland',
2257    'c': 'Ireland',
2258    'donegal': 'Ireland',
2259    'dl': 'Ireland',
2260    'dublin': 'Ireland',
2261    'd': 'Ireland',
2262    'galway': 'Ireland',
2263    'g': 'Ireland',
2264    'kerry': 'Ireland',
2265    // 'ky': 'Ireland',
2266    'kildare': 'Ireland',
2267    'ke': 'Ireland',
2268    'kilkenny': 'Ireland',
2269    'kk': 'Ireland',
2270    'laois': 'Ireland',
2271    'ls': 'Ireland',
2272    'leitrim': 'Ireland',
2273    'lm': 'Ireland',
2274    'limerick': 'Ireland',
2275    'lk': 'Ireland',
2276    'longford': 'Ireland',
2277    'ld': 'Ireland',
2278    'louth': 'Ireland',
2279    'lh': 'Ireland',
2280    'mayo': 'Ireland',
2281    // 'mo': 'Ireland',
2282    'meath': 'Ireland',
2283    'mh': 'Ireland',
2284    'monaghan': 'Ireland',
2285    // 'mn': 'Ireland',
2286    'offaly': 'Ireland',
2287    'oy': 'Ireland',
2288    'roscommon': 'Ireland',
2289    'rn': 'Ireland',
2290    'sligo': 'Ireland',
2291    'so': 'Ireland',
2292    'tipperary': 'Ireland',
2293    'ta': 'Ireland',
2294    'waterford': 'Ireland',
2295    'wd': 'Ireland',
2296    'westmeath': 'Ireland',
2297    'wh': 'Ireland',
2298    'wexford': 'Ireland',
2299    'wx': 'Ireland',
2300    'wicklow': 'Ireland',
2301    'ww': 'Ireland',
2302
2303    // These are provinces
2304    'drenthe': 'Netherlands',
2305    'dr': 'Netherlands',
2306    'flevoland': 'Netherlands',
2307    // 'fl': 'Netherlands',
2308    'friesland': 'Netherlands',
2309    'fr': 'Netherlands',
2310    'gelderland': 'Netherlands',
2311    'ge': 'Netherlands',
2312    'groningen': 'Netherlands',
2313    'gr': 'Netherlands',
2314    'limburg': 'Netherlands',
2315    'li': 'Netherlands',
2316    'noord-brabant': 'Netherlands',
2317    'nb': 'Netherlands',
2318    'noord-holland': 'Netherlands',
2319    // 'nh': 'Netherlands',
2320    'overijssel': 'Netherlands',
2321    'ov': 'Netherlands',
2322    'utrecht': 'Netherlands',
2323    'ut': 'Netherlands',
2324    'zeeland': 'Netherlands',
2325    'ze': 'Netherlands',
2326    'zuid-holland': 'Netherlands',
2327    'zh': 'Netherlands',
2328}
2329
2330// This fills in the country field based on the input in the state field.
2331function fill_country_from_state(state_fld_name, country_fld_name) {
2332
2333    var state_sel = '[name="' + state_fld_name + '"]'
2334    var country_sel = '[name="' + country_fld_name + '"]'
2335    $(document).on('blur', state_sel, function() {
2336        var country_ob = $(country_sel)
2337        // Only change country if there's nothing there yet.
2338        if (!country_ob.val()) {
2339            var state = $.trim($(state_sel).val().toLowerCase())
2340            var country = state_country_map[state]
2341            if (country != undefined) {
2342                country_ob.val(country);
2343            }
2344        }
2345    })
2346}
2347
2348
2349function fill_out_message_and_recips(on_load) {
2350    var message = $('#message_selector').val()
2351    if(message == 'new') {
2352        $('#attendance_section').show();
2353        $('#message_contents_section').show();
2354        $('#preview_message_buttons').show();
2355
2356        //$('.recipient_set_cb').parent().show()
2357        $('.recipient_set_cb').each(function(i) {
2358            $(this).attr('checked',false)
2359        })
2360        if (on_load != true) {
2361            $('[name="subject"]').val('')
2362            $('[name="body"]').val('')
2363        }
2364        $('#recipient_instructions').html('')
2365    }
2366    else if(message == 'registered_no_meals') {
2367        $('#attendance_section').hide();
2368        $('#message_contents_section').show();
2369        $('#preview_message_buttons').show();
2370
2371        //$('.recipient_set_cb').parent().hide()
2372        $('.recipient_set_cb:eq(1)').attr('checked',true) // need something checked to not set off form error checking
2373        $('[name="subject"]').val('Register for REGISTERED_MEETINGS meals')
2374        $('[name="body"]').val('You have registered to attend the REGISTERED_MEETINGS meeting(s), but have not yet indicated whether you will attend any meals/events or not.  Please do so right away - otherwise we will ignore your meeting registration and assume you are not able to attend the meeting(s) after all.  Remember: please only sign up for meals/events that you are sure you will attend. \n\nDo so here: SIGNUP_URL')
2375        $('#recipient_instructions').html('The recipient list will be people who have registered to attend the meeting(s) but have not completed <b>any</b> meal/event sign-ups.')
2376    }
2377    else if(message == 'registered_incomplete_meals') {
2378        $('#attendance_section').hide();
2379        $('#message_contents_section').show();
2380        $('#preview_message_buttons').show();
2381
2382        $('.recipient_set_cb:eq(1)').attr('checked',true) // need something checked to 
2382not set off form error checking
2383        $('[name="subject"]').val('Finish registering for REGISTERED_MEETINGS meals')
2384        $('[name="body"]').val('You have registered for the following meals/events at the REGISTERED_MEETINGS meeting(s):\nMEALS_ATTENDING\nThere are still some meals/events for which you have not indicated whether you will attend or not. Please log in to the committee website and update them right away.  Reminder: please only sign up for meals/events that you are sure you will attend. \n\nDo so here: SIGNUP_URL')
2385        $('#recipient_instructions').html('The recipient list will be people who have registered to attend the meeting(s) and have completed <b>some but not all</b> meal/event sign-ups.')
2386    }
2387    else if(message == 'meals_not_registered') {
2388        $('#attendance_section').hide();
2389        $('#message_contents_section').show();
2390        $('#preview_message_buttons').show();
2391
2392        $('.recipient_set_cb:eq(1)').attr('checked',true) // need something checked to not set off form error checking
2393        $('[name="subject"]').val('Register for NO_RESPONSE_MEETINGS meeting(s)')
2394        $('[name="body"]').val('You have registered for meals/events at the NO_RESPONSE_MEETINGS meeting(s), but have not yet signed up to attend.  Please do so right away - otherwise we will ignore your meal/event signups and assume you are not able to attend the meeting(s) after all. Remember: please only sign up for meals/events that you are sure you will attend. \n\nDo so here: SIGNUP_URL')
2395        $('#recipient_instructions').html('The recipient list will be people who have signed up for meals/events for a meeting they are no longer registered to attend.')
2396    }
2397    else if(message == 'invited_no_response') {
2398        $('#attendance_section').hide();
2399        $('#message_contents_section').show();
2400        $('#preview_message_buttons').show();
2401
2402        $('.recipient_set_cb:eq(1)').attr('checked',true) // need something checked to 
2402not set off form error checking
2403        $('[name="subject"]').val('Will you attend NO_RESPONSE_MEETINGS meeting(s)?')
2404        $('[name="body"]').val('You have not indicated whether or not you will be attending the NO_RESPONSE_MEETINGS meeting(s).  Please log in to the committee website and either sign up for the meeting(s) AND meals/events, or indicate that you will not be able to attend.  This information is important for our meeting planning, so please do it right away. \n\nDo so here: SIGNUP_URL')
2405        $('#recipient_instructions').html('The recipient list will be people who have been invited to the meeting(s) but have not yet indicated whether they will attend, nor have they signed up for meals/events at the meeting(s).')
2406    }
2407    else if(message == 'registered_and_mealed') {
2408        $('#attendance_section').hide();
2409        $('#message_contents_section').show();
2410        $('#preview_message_buttons').show();
2411
2412        $('.recipient_set_cb:eq(1)').attr('checked',true) // need something checked to not set off form error checking
2413        $('[name="subject"]').val('Reminder: REGISTERED_MEETINGS meeting(s)')
2414        $('[name="body"]').val('You have registered for the following meals/events at the REGISTERED_MEETINGS meeting(s):\nMEALS_ATTENDING\nIf your travel plans have changed and the list is no longer accurate, please log in to the committee website and update them right away.  Reminder: please only sign up for meals/events that you are sure you will attend. \n\nDo so here: SIGNUP_URL')
2415        $('#recipient_instructions').html('The recipient list will be people who have registered to attend the meeting(s) and have completed their meal/event sign-ups.')
2416    }
2417    else {
2418        $('#attendance_section').hide();
2419        $('#message_contents_section').hide();
2420        $('#preview_message_buttons').hide();
2421    }
2422}
2423
2424
2425function toggle_visible(class_type) {
2426    var preview = $('.' + class_type)
2427    preview.toggle()
2428    $('.link' + class_type).html()
2429}
2430
2431
2432function hide_message_previews() {
2433    $('div[class^="usr"]').hide()
2434}
2435
2436function ______AJAX_UTILITIES________() {}
2437
2438function decode_return_data(data) {
2439
2440    // This tries to do a better job of handling parsing errors caused by python bugs.
2441    // This handles responses created by util.package_ajax_response() and
2442    // improves handling python errors in response to ajax requests.
2443
2444    // This has alredy been decoded?
2445    if (typeof data == 'object') {
2446        return data;
2447    }
2448
2449    try {
2450        var return_data = jQuery.parseJSON(data);
2451    }
2452    catch(err) {
2453        var return_data = {};
2454        return_data['response'] = 'fail';
2455        return_data['message'] = data;
2456    }
2457
2458    // We're always expecting an object ot be returned.
2459    if (typeof return_data === 'undefined' || return_data === null) {
2460        return_data = {};
2461    }
2462
2463    return return_data
2464}
2465
2466
2467function put_file(url, file, upload_event_action_map, success_callback, timelimit, onerror_callback) {
2468
2469    // This is made specifically for uploading files.
2470    // <file> can be a file object or a blob.
2471    // This is a wrapper around XMLHttpRequest that will retry the request
2472    // if it fails.  It will also retry if the request times out.
2473    // It will retry until the timelimit is reached.
2474    // It will retry with exponential backoff.
2475    // If the request fails with a 403, it will throw an exception.
2476    // If the timelimit is reached, it will throw an exception.
2477
2478    let delay;
2479    let request = new XMLHttpRequest();
2480
2481    request.addEventListener('loadend', function (event) {
2482        if (request.status < 200 || request.status >= 300) {
2483            if (request.status == 403) {
2484                // If it's a bad url, we don't want to retry.
2485                onerror_callback();
2486            }
2487            openAndSend(request)
2488        } else {
2489            if (success_callback) {
2490                success_callback(request)
2491            }
2492            return request;
2493        }
2494    });
2495
2496    for (let e in upload_event_action_map) {
2497        request.upload.addEventListener(e, upload_event_action_map[e]);
2498    }
2499
2500    function openAndSend(request) {
2501        if (timelimit <= 0) {
2502            onerror_callback();
2503        } else {
2504            if (delay === undefined) {
2505                delay = 0;
2506            } else if (delay === 0) {
2507                // delay = Math.random() * 500;
2508                delay = 500;
2509            } else {
2510                delay = delay * 2;
2511            }
2512
2513            setTimeout(function() {
2514                timelimit = timelimit - delay;
2515                request.open('PUT', url, true);
2516                request.send(file);
2517            }, delay);
2518        }
2519
2520    }
2521    openAndSend(request)
2522}
2523
2524
2525function _______FUNCTIONS_CALLED_ON_READY______() {}
2526
2527// Handle 'return' key with care: explicitly click the first button after the
2528// focussed element.
2529$(function() {
2530    //$(document).keypress(function(event) {
2531    $(document).keypress(function(event) {
2532        if (event.keyCode == 13) {
2533            if (event && event.target && event.target.type && event.target.type == 'textarea') {
2534                return true // Allow return character in textareas.
2535            }
2536            if (event && event.target && $(event.target).hasClass("no_submit")) {
2537                // target specifies that we stop form submission
2538                event.cancelBubble = true;
2539                event.returnValue = false;
2540                // but we won't return false yet... we might still click a button
2541            }
2542            if ($.browser.msie && parseFloat($.browser.version) < 8) {
2543                // Turn off all return-key form submission for old versions of IE.
2544                event.cancelBubble = true;
2545                event.returnValue = false;
2546                return false;
2547            }
2548            if (!$(event.target).is(":input")) {
2549                // Only pay attention to return keys when form elements are in focus.
2550                event.cancelBubble = true;
2551                event.returnValue = false;
2552                return false;
2553            }
2554            clicked = false
2555            focus_el = $(document.activeElement)
2556            if (focus_el == $('body')) { alert('body in focus') }
2557            // Loop through focus_el and all its ancestor elements.
2558            anc = $($.makeArray(focus_el.add(focus_el.parents())).reverse())
2559            anc = anc.slice(0, anc.length-2)   // Ignore whole-page focus case.
2560            anc.each(function(index, element) {
2561                // If an ancestor has the no_submit class, don't keep looking.
2562                // Buttons contained in the ancestor with the no_submit class
2563                // will still be submitted.
2564                if ($(element).hasClass("no_submit")) {
2565                    event.cancelBubble = true;
2566                    event.returnValue = false;
2567                    return false;
2568                }
2569                // Loop through 'this' and all of its later siblings.
2570                $(element).add($(element).nextAll()).each(function(index2, el2) {
2571                    submits = $(el2).find(':submit, :button').filter(':visible')
2572                    if (submits.length) {
2573                        submits.first().focus().click()
2574                        clicked = true
2575                        return false;    // Stop the 'each' loop.
2576                    }
2577                })
2578                if (clicked) {
2579                    return false;    // Stop the 'each' loop.
2580                }
2581            })
2582            // The above didn't find a button; don't submit anything.
2583            event.cancelBubble = true;
2584            event.returnValue = false;
2585            return false;
2586        }
2587    });
2588});
2589
2590// Allow tabs in textareas with the 'll_tabbable' class
2591$(function() {
2592    $(document).delegate('.ll_tabbable textarea', 'keydown', function(e) {
2593      var keyCode = e.keyCode || e.which;
2594
2595      if (keyCode == 9) {
2596        e.preventDefault();
2597        var start = $(this).get(0).selectionStart;
2598        var end = $(this).get(0).selectionEnd;
2599
2600        // set textarea value to: text before caret + tab + text after caret
2601        $(this).val($(this).val().substring(0, start)
2602                    + "    "
2603                    + $(this).val().substring(end));
2604
2605        // put caret at right position again
2606        $(this).get(0).selectionStart =
2607        $(this).get(0).selectionEnd = start + 4;
2608      }
2609    });
2610})
2611
2612
2613// When a form is submitted start a timer for 10 seconds during which form resubmission is disallowed.
2614// Better: get confirmation and skip the timer.
2615$(function() {
2616    let allow_form_submission = true;
2617    let skip_all_checks = false;
2618
2619
2620    // function start_allow_countdown() {
2621    //     setTimeout(
2622    //         function() {
2623    //             allow_form_submission = true;
2624    //         },
2625    //         10000
2626    //     );
2627    // }
2628
2629    $(document).on('submit', '#thisForm', function(e) {
2630    // TODO: Update this to respond to any form submission.
2631    // $(document).on('submit', 'form', function(e) {
2632
2633        // We set this to true on the callback from the colorbox modal, so that we don't
2634        // get stuck in a continuous loop after programatically clicking the submit button.
2635        // We're clicking the submit button rather than calling submit() directly because
2636        // the submit behavior depends on the parameters of the <input> button that was clicked.
2637        if (skip_all_checks) {
2638            skip_all_checks = false;
2639            return true;
2640        }
2641
2642        // Block form submission if the clicked button is a blocking button
2643        // and errors are detected in the page.
2644
2645        // Get the clicked button
2646        let clicked_button = $(e.originalEvent.submitter);
2647
2648        let do_block = false;
2649        let do_submit = false;
2650
2651        for (let blocking_handler of BLOCKING_FORM_HANDLERS) {
2652            if(blocking_handler.block_submit_event_if_errors(clicked_button)) {
2653                do_block = true;
2654            }
2655        }
2656
2657        if (do_block) {
2658            e.preventDefault();
2659            return false;
2660        }
2661
2662        // Allow skipping this confirmation for some buttons.
2663        // The __skip_multi_click_check flag is an optional global setting from sr_cgi.
2664        // TODO: We CAN, in fact, get the button that was clicked to trigger this, so
2665        //       this could be transitioned to a button-specific behavior.
2666
2667        if (window.__skip_multi_click_check) {
2668            window.__skip_multi_click_check = false;
2669            do_submit = true;
2670        } else if (allow_form_submission) {
2671            allow_form_submission = false;
2672            do_submit = true;
2673        } else {
2674            // If we reached here, we detected that the user is trying to re-submit the form.
2675            // Present them a confirmation dialog to ask if they really want to do this.
2676            do_submit = confirm('Your data is being processed; are you sure you want to re-submit?');
2677        }
2678
2679        if (do_submit) {
2680            let showed_modal = show_submit_waiting_modal_if_has_def(
2681                clicked_button,
2682                {
2683                    onComplete: function() {
2684                        // Re-click the clicked_button to resubmit the form, but pass
2685                        // a flag so that the event handler immediately returns true.
2686                        // We're needing to re-click the button for Safari.  If instead we return true;
2687                        // directly, Safari will stop processing the modal dialog before it's
2688                        // finished displaying.
2689                        skip_all_checks = true;
2690                        clicked_button.click();
2691                    },
2692                }
2693            );
2694            if (showed_modal) {
2695                // We're preventing the first click from registering so that clicked_button.click() is
2696                // used instead.
2697                e.preventDefault();
2698                return false;
2699            } else {
2700                return true;
2701            }
2702
2703        } else {
2704            e.preventDefault();
2705            return false;
2706        }
2707    });
2708});
2709
2710
2711// Process any elements that should be displayed using a markdown interpreter
2712$(function () {
2713    $('.markdown_display').each(process_markdown);
2714    $('.monospace').each(process_monospace);
2715})
2716
2717// $(function() {
2718//   show_touch_elements_for_touch_devices();
2719// });
2720
2721
2722function ________COMBOBOX_STUFF________() {}
2723
2724$(function(){
2725	// Improve the click listener on the combo-box widget to focus on the text-area when it is clicked anywhere.
2726	$(document).on('click', '.jqx-combobox-content', function(event_ob) {
2727		var target = $(event_ob.target);
2728		var ta = target.find('input[type=textarea]');
2729		if (ta.length > 0) {
2730			ta[0].focus();
2731		}
2732	})
2733
2734	// Set up the checkbox version of combo-boxes to open when you click on the whitespace too.
2735	$(document).on('click', '.jqx_checkbox', function(event_ob) {
2736		var target = $(event_ob.target);
2737		var jqx_ob = $(target[0]).parents('.jqx_checkbox');
2738		if (jqx_ob.jqxComboBox('isOpened')) {
2739			jqx_ob.jqxComboBox('close');
2740		} else {
2741			jqx_ob.jqxComboBox('open');
2742		}
2743	})
2744
2745	// Focus back on the textarea when something changes.
2746    // This is causing trouble in Chrome, when two comboboxes have contents it won't let anything else have focus.
2747	// $(document).on('select unselect', '.jqx_multiselect', function(event) {
2748	// 	$(event.target).focus();
2749	// })
2750
2751	// $('.jqx_detector').on('open', function(event) {
2752	// 	$(event.target).focus();
2753	// })
2754
2755	// $(window).on('keydown', function(event) {
2756	// 	if(event.keyCode == 8 & $(event.target).hasClass('jqx-combobox-input')) {
2757	// 		$(event.target).focus();
2758	// 	}
2759	// });
2760
2761	window.setInterval(init_combo_boxes, 500);
2762
2763})
2764
2765function init_combo_boxes() {
2766	// Comboboxes can't be initialized while they're hidden.
2767	$('.jqx_detector:visible').each(function(i, el) {
2768		var cbox = $(el);
2769		// console.log(cbox)
2770		// This could be jqx-widget
2771		if (!cbox.hasClass('jqx-combobox')) {
2772			var cbox_id = cbox.attr('id');
2773			var init_func = 'init_combo_box_' + cbox_id + '()';
2774            // This gets called sometimes when there isn't an init_func.
2775            // eval(init_func);
2776            try {
2777                eval(init_func);
2778            }
2779            catch(err) {}
2780		}
2781	})
2782}
2783
2784// var combo_box_inits = []
2785//
2786// function init_combo_boxes() {
2787// 	var i = 0
2788// 	for (i = 0; i < combo_box_inits.length; i++) {
2789// 	    combo_box_inits[i]();
2790// 	}
2791// 	// reset it for next time.
2792// 	combo_box_inits = []
2793// }
2794
2795
2796function build_combo_box_data(disp_vals, val_vals) {
2797    var data = new Array();
2798    var k = 0;
2799    for (var i = 0; i < disp_vals.length; i++) {
2800        var row = {};
2801        row["disp"] = disp_vals[k];
2802        row["val"] = val_vals[k];
2803        data[i] = row;
2804        k++;
2805    }
2806    var source =
2807    {
2808        localdata: data,
2809        datatype: "array"
2810    };
2811    var dataAdapter = new $.jqx.dataAdapter(source);
2812
2813    return dataAdapter;
2814}
2815
2816
2817function _______TO_DO_LIST_______() {}
2818
2819function show_hide_no_to_dos_rows() {
2820    var rows = $('#to_do_table tr:visible');
2821    if (rows.length > 0) {
2822        $('#no_to_dos_row').fadeOut();
2823    } else {
2824        $('#no_to_dos_row').fadeIn();
2825    }
2826    var done_rows = $('#finished_table .to_do_item');
2827    // > 1 here because there's the TH row.
2828    if (done_rows.length > 0) {
2829        $('#no_done_to_dos_row').fadeOut();
2830        $('#finished_table_header').fadeIn();
2831    } else {
2832        $('#finished_table_header').fadeOut();
2833        $('#no_done_to_dos_row').fadeIn();
2834    }
2835}
2836
2837
2838function update_to_do_alert() {
2839
2840    var to_do_alert = $('#to_do_alert');
2841    var new_num = $('#to_do_table .to_do_item').length;
2842    if (new_num > 0) {
2843        var alert_span = $('#to_do_alert_num');
2844        to_do_alert.fadeIn(); // This may or may not be hidden.
2845        alert_span.html(new_num);
2846    } else {
2847        to_do_alert.fadeOut();
2848    }
2849
2850}
2851
2852function check_to_do_item(event, check_url, uncheck_url) {
2853    var cb = $(event.target);
2854	if (!cb.hasClass('to_do_cb')) {
2855		cb = $(cb.parent());
2856	}
2857    var to_do_id = cb.attr('to_do_id');
2858    var row = cb.parents('.to_do_item');
2859
2860    // someone clicked something where we couldn't find an id - just bail.
2861    if (typeof to_do_id == "undefined") {
2862        return
2863    }
2864
2865    // Figure out whether it's being checked off or unchecked.
2866//    if (cb.attr('checked')) {
2867    if (cb.attr('status') == 'unchecked') {
2868        row.css('color', 'gray');
2869        var button = 'button_' + check_url;
2870        var success_func = function(cb_) {
2871            // var row = cb_.parents('.to_do_item');
2872            row.fadeOut(500, function() {
2873                row.find('.disp_ts').html('just now');
2874                row.css('color', 'gray');
2875                var dest_table = $('#finished_table');
2876				if (dest_table.length > 0) {
2877					// This is when we're in the to-do page.
2878	                dest_table.find('#finished_table_header').after(row);
2879	                row.fadeIn(500);
2880				} else {
2881					// This is when we're just in the to-do box.
2882					row.remove();
2883				}
2884                show_hide_no_to_dos_rows();
2885                update_to_do_alert();
2886            });
2887        }
2888		cb.attr('status', 'checked');
2889		cb.find('.action_verb').html('restore');
2890    } else {
2891        row.css('color', 'black');
2892        var button = 'button_' + uncheck_url;
2893        var success_func = function(cb_) {
2894            // var row = cb_.parents('.to_do_item');
2895            row.fadeOut(500, function() {
2896                row.find('.disp_ts').html('');
2897                row.css('color', 'black');
2898                var dest_table = $('#to_do_table')
2899                dest_table.find('tr:first').before(row);
2900                show_hide_no_to_dos_rows();
2901                row.fadeIn(500);
2902                update_to_do_alert();
2903            });
2904        }
2905		cb.attr('status', 'unchecked');
2906		cb.find('.action_verb').html('hide item');
2907    }
2908
2909    // Gather args.
2910    var data = {
2911        'uniq_param': (new Date()).getTime(),   // This prevents IE from caching.
2912        'do_not_print': 'True', // Not sure if this is needed.
2913        'to_do_id': to_do_id,
2914    }
2915    data[button] = 'Submit'
2916
2917    // Encode arguments.
2918    data = jQuery.param(data);
2919
2920    // show_updating()
2921
2922    // Make request
2923    return $.ajax({
2924        url: ".",
2925        type: "POST",
2926        cache: false,
2927        data: data
2928    }).done(function(return_data, textStatus, jqXHR) {
2929        // var return_data = jQuery.parseJSON(return_data);
2930        var return_data = decode_return_data(return_data);
2931
2932        if (return_data.response == 'success') {
2933            success_func(cb);
2934        } else {
2935            $.colorbox({
2936                html: return_data.message,
2937                overlayClose: false,
2938                trapFocus: true,
2939                closeButton: false,
2940                opacity: .3,
2941                className: 'modal_form_colorbox',
2942                onComplete: setup_info_links
2943            })
2944            return
2945        }
2946
2947    })
2948
2949}
2950
2951
2952function _______OTHER______() {}
2953
2954
2955function check_allow_other(other_field_name) {
2956
2957    // This disables the other field when the other checkbox isn't checked in checkbox widgets.
2958
2959    var checkbox = $("input[value='"+other_field_name+"']")
2960    var text_field = $("input[name='"+other_field_name+"']")
2961
2962    if (checkbox.is(':checked')) {
2963        text_field.removeAttr('disabled')
2964    } else {
2965        text_field.attr('disabled', 'disabled')
2966        text_field.val('')
2967    }
2968}
2969
2970
2971function setup_check_select_other(select_name, other_name) {
2972
2973    var s_selector = '[name="' + select_name + '"]'
2974    var s = $(s_selector)
2975    var t = $('[name="' + other_name + '"]')
2976
2977    check_select_other(s, t)
2978    $(document).on('change', s_selector, function () {
2979        check_select_other(s, t)
2980    })
2981}
2982
2983
2984function check_select_other(s, t) {
2985
2986    if (s.val()) {
2987        t.val('')
2988        t.attr('disabled', 'disabled')
2989    } else {
2990        t.removeAttr('disabled')
2991    }
2992
2993}
2994
2995
2996function import_reservation(date_id, start_hour, start_min, end_hour, end_min, room) {
2997
2998    /* This is for the prog_pour. It moves defaults for a space
2999    reservation into a session's info. */
3000
3001    $('[name="date"]').val(date_id)
3002    $('[name="start_hour"]').val(start_hour)
3003    $('[name="start_min"]').val(start_min)
3004    $('[name="end_hour"]').val(end_hour)
3005    $('[name="end_min"]').val(end_min)
3006    $('[name="room"]').val(room)
3007    $('[name="time"]').val('')
3008}
3009
3010
3011function set_nat_width(image_ob) {
3012
3013    // This should only be called on the load event of image_ob. Otherwise the width will be 0.
3014    // Sets the property 'nat_width' for <image_ob>. This is used to make a good min-width and max for resizing.
3015    // This could be expanded to set <nat_height> and reset min-height too.
3016
3017    alert('set_nat_width is not working, do not use it')
3018
3019    var off_screen_image = new Image();
3020    off_screen_image.src = image_ob.attr("src");
3021    $(off_screen_image).on('load', function() {
3022        image_ob.attr('nat_width', off_screen_image.width)
3023        reset_min_width(image_ob)
3024    })
3025}
3026
3027
3028function reset_min_width(image_ob) {
3029
3030    // resets the default min-width for image_ob to the natural size of the image if it is smaller than the min-width.
3031
3032    alert('reset_min_width is not working, do not use it')
3033
3034    var min = image_ob.css('min-width')
3035    var min_num = min.slice(0, min.indexOf('px'))
3036    var real_size = image_ob.attr('nat_width')
3037    if (parseInt(min_num) > parseInt(real_size)) {
3038        image_ob.css('min-width', real_size + 'px')
3039        $(window).resize() // Trigger a resize to redraw the image.
3040    }
3041}
3042
3043
3044
3045function _______USER_GROUPING_IFRAME_WIDGET______() {}
3046
3047
3048
3049function _____GENERAL_HELPER_FUNCTIONS_________() {}
3050
3051
3052function get_input_field_type(field) {
3053    // Find the input type of a jquery object <field>.
3054    // can be radio, text, checkbox, textarea, select
3055
3056    if (field.length < 1) {
3057        console.error('get_input_field_type: field not found');
3058        return '';
3059    }
3060
3061    type = field.attr('type')
3062    if (type) {
3063        type = type.toLowerCase() // For 'radio', 'text', 'checkbox', 'file', 'password'
3064    } else if (jQuery.inArray(type, ['text', 'checkbox', 'radio', 'file']) == -1) {
3065        type = field.get(0).nodeName.toLowerCase() // For 'textarea', 'select'
3066    }
3067    return type
3068}
3069
3070
3071function get_input_field_first_opt(field) {
3072
3073    // Get the first non-"" option of a select, radio, or checkbox field.
3074
3075    // for radios / checkboxes, field will be a list of inputs.
3076
3077    let type = get_input_field_type(field);
3078    if (type == 'select') {
3079        let val = '';
3080        jQuery(field.find('option')).each(function (index, opt) {
3081            if (opt.value != '') {
3082                val = opt.value;
3083                return false;
3084            }
3085        });
3086        return val;
3087    } else if (type == 'radio') {
3088        return jQuery(field.first()).val();
3089    } else if (type == 'checkbox') {
3090        return field.first().val();
3091    } else {
3092        console.error('get_input_field_first_opt: field type not implented');
3093    }
3094}
3095
3096
3097function tanslate_seconds_to_min_secs(seconds) {
3098    var mins = Math.floor(seconds / 60);
3099    var secs = seconds - (mins * 60);
3100
3101    // if(mins.toString().length == 1) mins = '0' + mins;
3102    // if(secs.toString().length == 1) secs = '0' + secs;
3103
3104    // return mins + ':' + secs;
3105    // return mins + ':' + secs + ' min';
3106    return mins + 'min ' + secs + 's';
3107}
3108
3109
3110function _____REQUIRED_INPUT_FIELD________() {}
3111
3112
3113var setup_required_input_field_checks_has_been_called = false;
3114function setup_required_input_field_checks() {
3115
3116    // This only needs to be called once per page.
3117    if (setup_required_input_field_checks_has_been_called) {
3118        return false;
3119    }
3120    setup_required_input_field_checks_has_been_called = true;
3121
3122    // tinymce.on( 'AddEditor', function(ed) {
3123    //     console.log(ed)
3124    //     ed.on('keyup', function (e) {
3125    //         console.log('yo')
3126    //         check_required_input_filled(e)
3127    //     })
3128    // })
3129
3130    jQuery(document).ready(function () {
3131        // setup events.
3132        jQuery(document).on('change blur focus', 'input, select, textarea, .jqx_detector', function () {
3133            check_required_input_filled(jQuery(this));
3134        })
3135
3136        jQuery(document).on('keyup', 'input, textarea', function () {
3137            check_required_input_filled(jQuery(this));
3138        })
3139
3140        $('input, select, textarea, .jqx_detector').each(function (index, el) {
3141            // If this isn't an input in a multi-block template then check it now.
3142            if ($(this).attr('name') && $(this).attr('name').indexOf('%%') == -1) {
3143                check_required_input_filled($(this));
3144            }
3145        })
3146
3147        BLOCKING_FORM_HANDLERS = setup_blocking_form_handlers();
3148    });
3149}
3150
3151
3152function check_required_input_filled(caller_field) {
3153
3154    // This checks to see whether <caller_field>, which is required in some way, has a value (so requirement is filled).
3155
3156    // Make sure this isn't a multi-field template
3157    if (!$(caller_field).attr('name') || $(caller_field).attr('name').indexOf('%%') != -1) {return}
3158
3159    var values = get_field_values(caller_field)
3160
3161    // straight-forward label
3162    var label_ob = jQuery("label[for='" + caller_field.attr('id') + "']")
3163
3164    // This may be an 'other' field.
3165    var caller_id = caller_field.attr('id')
3166    if (caller_id && caller_id.indexOf('_other') != -1) {
3167        orig_id = caller_id.substring(0, caller_id.length - 6)
3168        label_ob = $("label[for='" + orig_id + "']")
3169    }
3170
3171    // check for checkbox label first - allow both labels and spans
3172    // var checkboxes_label_ob = jQuery("label[cbs_for='" + caller_field.attr('class') + "']")
3173    var checkboxes_label_ob = jQuery("[cbs_for='" + caller_field.attr('class') + "']")
3174    if (checkboxes_label_ob.length > 0) {
3175        label_ob = checkboxes_label_ob
3176    } else if (label_ob.length == 0) {
3177        label_ob = jQuery("label[for='" + caller_field.attr('name') + "']")
3178    }
3179
3180    // Change classes as needed.
3181    // if (values.length > 0) {
3182    if (values[0] != '') {
3183        label_ob.removeClass('required_if_font')
3184        // label_ob.children().removeClass('error_font')
3185    } else if (label_ob.hasClass('required_flag')) {
3186        label_ob.addClass('required_if_font')
3187    }
3188}
3189
3190
3191function check_fields_dependant_on(input_label) {
3192
3193    // <input_label> is a label for an input object.
3194    // This figures out what it is and calls check_required_input_filled on the appropriate dom element.
3195
3196    dependant_input = input_label.attr('cbs_for')
3197
3198    if (typeof dependant_input == 'undefined') {
3199        dependant_input = input_label.attr('for')
3200        var dependant_input_objects = jQuery('#' + dependant_input)
3201        if (dependant_input_objects.length != 0) {
3202            // Do we need this?
3203            // Include the other field in this. I'm assuming there won't be any inputs that are only other fields.
3204            // $.merge(dependant_input_objects, jQuery('#' + dependant_input + '_other'))
3205
3206            jQuery.each(dependant_input_objects, function (index, element) {
3207                check_required_input_filled(jQuery(element))
3208            })
3209            return false
3210            // return true
3211        }
3212    }
3213
3214    // <input_label> is the label for a collection of checkboxes.
3215    var dependant_input_objects = jQuery('.' + dependant_input)
3216    $.merge(dependant_input_objects, $('.' + dependant_input + "_other"))
3217    if (dependant_input_objects.length != 0) {
3218        jQuery.each(dependant_input_objects, function (index, element) {
3219            check_required_input_filled(jQuery(element))
3220        })
3221    }
3222}
3223
3224
3225function get_field_values(field_ob) {
3226    // Returns the values of <field_ob>.
3227    // Returns [''] if no value is found (we want an empty input field to be reliably testable).
3228
3229    if (field_ob.length == 0) {
3230        return ['']
3231    }
3232
3233    var values = []
3234	if (field_ob.hasClass('jqx-combobox')) {
3235		// Two cases: multi or single.
3236		if (field_ob.jqxComboBox('multiSelect')) {
3237			// This returns an array of objects.
3238			var values_ = field_ob.jqxComboBox('getSelectedItems');
3239			if (values_.length > 0) {
3240				for (var i=0; i<values_.length; i++) {
3241					val_ob = values_[i];
3242					if (typeof(val_ob) == 'object') {
3243						values.push(val_ob.value);
3244					}
3245				}
3246			}
3247		} else {
3248			var value_ = field_ob.jqxComboBox('getSelectedItem');
3249			if (value_ != undefined) {
3250				values.push(value_.value)
3251			}
3252		}
3253	} else {
3254	    // Find the type of field_ob
3255	    var type = get_input_field_type(field_ob)
3256		// console.log('type', type)
3257
3258	    // Find the current value(s) for <field_ob>.
3259	    if (jQuery.inArray(type, ['text', 'textarea', 'hidden', 'file', 'password']) != -1) {
3260	        var file_field_name = field_ob.attr("name") + '_infn'
3261	        var file_field_ob = jQuery("[name='"+file_field_name+"']")
3262
3263	        // console.log('triggered')
3264	        // var field_id = field_ob.attr('id')
3265	        // var tinymce_ob = tinyMCE.get(field_id)
3266	        // if (field_id && typeof(tinymce_ob) != 'undefined') {
3267	        //     values = tinymce.get(field_id).getContent() || ['']
3268	        //     console.log(values)
3269	        // } else {
3270	        //     values = field_ob.val() || file_field_ob.val() || ['']
3271	        // }
3272
3273	        values = field_ob.val() || file_field_ob.val() || ['']
3274
3275	    } else if (type == 'select') {
3276	        var selected = field_ob.find('option:selected')
3277	        // This version only gets one value (I believe).
3278	        // values = selected.val() || []
3279	        selected.each(function(i) {
3280	            var value = jQuery(this).val()
3281	            if (value != '') {
3282	                values[i] = value
3283	            }
3284	        })
3285
3286	        // If we couldn't find a value in the select list, see if there's something in the 'other' text field.
3287	        if (values.length == 0) {
3288	            select_id = field_ob.attr('id')
3289	            other_id = select_id + '_other'
3290	            other_ob = $('#' + other_id)
3291	            if (other_ob.length > 0 && other_ob.val()) {
3292	                values.push(other_ob.val())
3293	            }
3294	        }
3295	    } else if (jQuery.inArray(type, ['checkbox', 'radio']) != -1) {
3296	        var name = field_ob.attr("name")
3297	        var checked = jQuery("input[name='"+name+"']:checked")
3298	        // values = checked.val() || [] // returns the first matched elements value
3299	        // This version gets all the values.
3300	        checked.each(function(i) {values[i] = jQuery(this).val()}) || ['']
3301	    }
3302	}
3303
3304    if (!jQuery.isArray(values)) {
3305        values = [values]
3306    }
3307
3308    if (values.length == 0) { values = [''] }
3309
3310    return values
3311
3312    // // It's ok to count the 'other' checkbox as having a value when its checked even if the text is empty.
3313    // // Checkboxes use a particular pattern for the 'other' checkbox, we want to get the real value, not the convention.
3314    // var new_values = []
3315    // for (var i=0; i<values.length; i++) {
3316    //     var val_str = values[i]
3317    //     if (val_str.match("_other$")) {
3318    //         var new_val = $('[name="' + val_str + '"]').val()
3319    //         if (new_val) {new_values.push(new_val)}
3320    //     } else {
3321    //         new_values.push(val_str)
3322    //     }
3323    // }
3324    // console.log(field_ob, new_values)
3325    // return new_values
3326}
3327
3328
3329function set_field_val(field_ob, val) {
3330    // Sets the selected value of <field_ob> to <val>.
3331
3332    if (field_ob.hasClass('jqx-combobox')) {
3333        console.error('combobox Not Implemented for set_field_val()');
3334    } else {
3335        // Find the type of field_ob
3336        var type = get_input_field_type(field_ob)
3337
3338        if (jQuery.inArray(type, ['text', 'textarea', 'hidden', 'file', 'password']) != -1) {
3339            var file_field_name = field_ob.attr("name") + '_infn'
3340            var file_field_ob = jQuery("[name='" + file_field_name + "']")
3341
3342            field_ob.val(val);
3343            if (file_field_ob.length > 0) {
3344                file_field_ob.val(val);
3345            }
3346
3347        } else if (type == 'select') {
3348            field_ob.val(val);
3349
3350            // TODO - improve this to set the other field if we can't find the value in a select.
3351
3352            // // If we couldn't find a value in the select list, see if there's something in the 'other' text field.
3353            // if (values.length == 0) {
3354            //     select_id = field_ob.attr('id')
3355            //     other_id = select_id + '_other'
3356            //     other_ob = $('#' + other_id)
3357            //     if (other_ob.length > 0 && other_ob.val()) {
3358            //         values.push(other_ob.val())
3359            //     }
3360            // }
3361        } else if (jQuery.inArray(type, ['checkbox', 'radio']) != -1) {
3362            var name = field_ob.attr("name")
3363            var checked = jQuery("input[name='" + name + "'][value='" + val + "']").attr('checked', true)
3364        }
3365    }
3366
3367}
3368
3369
3370function setup_check_required(label_selector, dependent_field_id) {
3371
3372    // This does the basic is required check.
3373
3374    var label_ob = jQuery(label_selector)
3375    label_ob.addClass('required_if_font required_flag')
3376    check_fields_dependant_on(jQuery('#' + dependent_field_id))
3377}
3378
3379
3380function check_req_if(field_ob, label_selector, vals, dependent_field_id) {
3381
3382    // This checks the value in <field_ob> against <vals>, and if it is in <vals>, turns <label_selector> red to indicate it is now required.
3383    // <label_selector> is the label of the dependent field.
3384    // <dependent_field_id> is the id of the field required if <field_ob>.val() in <vals>. In the case of checkboxes it is the id of the field's label.
3385
3386    // Get the values of the field object
3387    var values = get_field_values(field_ob);
3388
3389    var found = false;
3390    var label_ob = jQuery(label_selector);
3391    jQuery.each(vals, function(i1, v1) {
3392        jQuery.each(values, function(i2, v2) {
3393            if (v1 == v2) {
3394                label_ob.addClass('required_if_font required_flag')
3395                found = true
3396                return false    // This tells .each() to stop (like break).
3397            }
3398        })
3399        if (found) return false // Break out of outer loop.
3400    })
3401    if (!found) {
3402        label_ob.removeClass('required_if_font required_flag')
3403    }
3404
3405    // If this is required but filled we don't want to make it red
3406    check_fields_dependant_on(jQuery('#' + dependent_field_id))
3407}
3408
3409
3410function get_other_field_selector(field_ob) {
3411
3412    // If <field_ob> has an attached 'other' field than this returns a selector string to get it.
3413
3414    var field_id = field_ob.attr('id')
3415    var other_id = field_id + '_other'
3416    other_ob = $('#' + other_id)
3417    if (other_ob.length == 1) {
3418        return '#' + other_id
3419    } else {
3420        return get_other_field_selector_by_name(field_ob)
3421    }
3422}
3423
3424function get_other_field_selector_by_name(field_ob)
3425{
3426
3427    // If <field_ob> has an attached 'other' field than this returns a selector string to get it.
3428
3429    var field_name = field_ob.attr('name')
3430    var other_name = field_name + '_other'
3431    other_ob = $('[name="' + other_name + '"]')
3432    if (other_ob.length == 1) {
3433        return '[name="' + other_name + '"]'
3434    } else {
3435        return false
3436    }
3437}
3438
3439
3440function setup_other_check_req_if(field_selector, label_selector, vals, dependent_field_id) {
3441
3442    // This sets up the check_req_if for the 'other' field case. It doesn't
3443    // need to deal with the multi vs. plain trouble because that is dealt with in the caller.
3444
3445    var field_ob = $(field_selector)
3446    // // If there is an 'other' field, get that selector and set that up too.
3447    // var other_selector = get_other_field_selector(field_ob)
3448    // if (other_selector) {setup_check_req_if(other_selector, label_selector, vals, dependent_field_id)}
3449
3450    // var type = get_input_field_type(field_ob)
3451
3452    // if (jQuery.inArray(type, ['text', 'textarea']) != -1) {
3453    //     $(document).on('keyup', field_selector,
3454    //         function () {
3455    //             check_req_if(field_ob, label_selector, vals, dependent_field_id)
3456    //         }
3457    //     )
3458    // }
3459    // $(document).on('change', field_selector,
3460    //     function () {
3461    //         check_req_if(field_ob, label_selector, vals, dependent_field_id)
3462    //     }
3463    // )
3464
3465    $(document).on('keyup change', field_selector,
3466        function () {
3467            check_req_if(field_ob, label_selector, vals, dependent_field_id)
3468        }
3469    )
3470
3471    check_req_if(field_ob, label_selector, vals, dependent_field_id)
3472}
3473
3474
3475function setup_check_req_if(field, label_selector, vals, dependent_field_id, multi_suffix) {
3476
3477    field_selector = "[name='" + field + "']"
3478
3479    var field_ob = $(field_selector)
3480    // If we haven't found the trigger field try the multi case.
3481    if (field_ob.length == 0) {
3482        field_selector = "[name='" + field + multi_suffix + "']"
3483        field_ob = $(field_selector)
3484    }
3485
3486    // If there is an 'other' field, get that selector and set that up too.
3487    var other_selector = get_other_field_selector(field_ob)
3488    if (other_selector) {setup_other_check_req_if(other_selector, label_selector, vals, dependent_field_id)}
3489
3490    // var type = get_input_field_type(field_ob)
3491
3492    // if (jQuery.inArray(type, ['text', 'textarea']) != -1) {
3493    //     $(document).on('keyup', field_selector,
3494    //         function () {
3495    //             check_req_if(field_ob, label_selector, vals, dependent_field_id)
3496    //         }
3497    //     )
3498    // }
3499    // $(document).on('change', field_selector,
3500    //     function () {
3501    //         check_req_if(field_ob, label_selector, vals, dependent_field_id)
3502    //     }
3503    // )
3504
3505    $(document).on('keyup change', field_selector,
3506        function () {
3507            check_req_if(field_ob, label_selector, vals, dependent_field_id)
3508        }
3509    )
3510
3511    check_req_if(field_ob, label_selector, vals, dependent_field_id)
3512}
3513
3514
3515function check_req_if_not(field_ob, label_selector, vals, dependent_field_id) {
3516
3517    // This checks the value in <field_ob> against <vals>, and if it is in <vals>, turns <label_selector> black to indicate it is no longer required.
3518    // <label_selector> is the label of the dependent field.
3519    // <dependent_field_id> is the id of the field required if <field_ob>.val() not in <vals>. In the case of checkboxes it is the id of the field's label.
3520
3521    // Get the values of the field object
3522    var values = get_field_values(field_ob)
3523    var found = false
3524    var label_ob = jQuery(label_selector)
3525    jQuery.each(vals, function(i1, v1) {
3526        jQuery.each(values, function(i2, v2) {
3527            if (v1 == v2) {
3528                found = true
3529                return false    // This tells .each() to stop (like break).
3530            }
3531        })
3532        if (found) return false // Break out of outer loop.
3533    })
3534    if (found) {
3535        label_ob.removeClass('required_if_font required_flag')
3536    } else {
3537        label_ob.addClass('required_if_font required_flag')
3538    }
3539
3540    // If this is required but filled we don't want to make it red
3541    check_fields_dependant_on(jQuery('#' + dependent_field_id))
3542
3543}
3544
3545
3546function setup_other_check_req_if_not(field_selector, label_selector, vals, dependent_field_id) {
3547
3548    // This sets up the check_req_if_not for the 'other' field case. It doesn't
3549    // need to deal with the multi vs. plain trouble because that is dealt with in the caller.
3550
3551    var field_ob = $(field_selector)
3552
3553    // // If there is an 'other' field, get that selector and set that up too.
3554    // var other_selector = get_other_field_selector(field_ob)
3555    // if (other_selector) {setup_check_req_if_not(other_selector, label_selector, vals, dependent_field_id)}
3556
3557    // var type = get_input_field_type(field_ob)
3558
3559    // if (jQuery.inArray(type, ['text', 'textarea']) != -1) {
3560    //     $(document).on('keyup', field_selector,
3561    //         function () {
3562    //             check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3563    //         }
3564    //     )
3565    // } else {
3566    //     $(document).on('change', field_selector,
3567    //         function () {
3568    //             check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3569    //         }
3570    //     )
3571    // }
3572
3573    $(document).on('keyup change', field_selector,
3574        function () {
3575            check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3576        }
3577    )
3578
3579    check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3580}
3581
3582
3583function setup_check_req_if_not(field, label_selector, vals, dependent_field_id, multi_suffix) {
3584
3585    field_selector = "[name='" + field + "']"
3586
3587    var field_ob = $(field_selector)
3588    // If we haven't found the trigger field try the multi case.
3589    if (field_ob.length == 0) {
3590        field_selector = "[name='" + field + multi_suffix + "']"
3591        field_ob = $(field_selector)
3592    }
3593
3594    // If there is an 'other' field, get that selector and set that up too.
3595    var other_selector = get_other_field_selector(field_ob)
3596    if (other_selector) {setup_other_check_req_if_not(other_selector, label_selector, vals, dependent_field_id)}
3597
3598    // var type = get_input_field_type(field_ob)
3599
3600    // if (jQuery.inArray(type, ['text', 'textarea']) != -1) {
3601    //
3602    //     $(document).on('keyup', field_selector,
3603    //         function () {
3604    //             check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3605    //         }
3606    //     )
3607    // } else {
3608    //     $(document).on('change', field_selector,
3609    //         function () {
3610    //             check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3611    //         }
3612    //     )
3613    // }
3614
3615    // I don't know why we were checking for the type of field above instead of just attaching it to both events.
3616    $(document).on('keyup change', field_selector,
3617        function () {
3618            check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3619        }
3620    )
3621
3622    check_req_if_not(field_ob, label_selector, vals, dependent_field_id)
3623}
3624
3625
3626function check_req_if_none(label_selector, dependent_field_id, new_fields) {
3627
3628    // <label_selector> is the label of the dependent field.
3629    // <dependent_field_id> is the id of the field required if <new_fields> don't have any values. In the case of checkboxes it is the id of the field's label.
3630    var found = false
3631    for (var i=0; i<new_fields.length; i++) {
3632        var field = new_fields[i]
3633
3634        // Get the values of the field object
3635        var values = get_field_values(field)
3636
3637        // if (values.length > 0) {
3638        if (values[0] != '') {
3639            found = true
3640            break
3641        }
3642    }
3643    var label_ob = jQuery(label_selector)
3644    if (found) {
3645        label_ob.removeClass('required_if_font required_flag')
3646    } else {
3647        label_ob.addClass('required_if_font required_flag')
3648    }
3649
3650    // If this is required but filled we don't want to make it red
3651    check_fields_dependant_on(jQuery('#' + dependent_field_id))
3652}
3653
3654
3655function setup_check_req_if_none(fields, label_selector, dependent_field_id) {
3656
3657    // <fields> are the fields that need to be empty for dependent_field_id to be required.
3658
3659    var new_fields = []
3660
3661    // populate new_fields with jquery objects.
3662    for (var i=0; i<fields.length; i++) {
3663        var new_field_ob = jQuery("[name='"+fields[i]+"']")
3664        // fields includes fields that might not exist depending on whether they are multi-fields or not.  Only include fields we can find.
3665        if (new_field_ob.length > 0) {
3666            new_fields.push(new_field_ob)
3667
3668            // If <new_field_ob> has an attached 'other' field, include that in these checks too
3669            var other_selector = get_other_field_selector(new_field_ob)
3670            if (other_selector) {new_fields.push($(other_selector))}
3671        }
3672    }
3673
3674    check_req_if_none(label_selector, dependent_field_id, new_fields)
3675    jQuery.each(new_fields, function(key, val) {
3676        // type = get_input_field_type(val)
3677        // if (jQuery.inArray(type, ['text', 'textarea']) != -1) {
3678        //     val.keyup(function() {check_req_if_none(label_selector, dependent_field_id, new_fields)})
3679        // } else {
3680        //     val.change(function() {check_req_if_none(label_selector, dependent_field_id, new_fields)})
3681        // }
3682
3683        $(document).on('keyup change', val, function() {check_req_if_none(label_selector, dependent_field_id, new_fields)})
3684    })
3685}
3686
3687
3688function check_req_if_any(label_selector, dependent_field_id, new_fields) {
3689
3690    // <label_selector> is the label of the dependent field.
3691    // <dependent_field_id> is the id of the field required if any of <new_fields> have a value. In the case of checkboxes it is the id of the field's label.
3692
3693    var found = false
3694    for (var i=0; i<new_fields.length; i++) {
3695        var field = new_fields[i]
3696
3697        // Get the values of the field object
3698        var values = get_field_values(field)
3699
3700        // if (values.length > 0) {
3701        if (values[0] != '') {
3702            found = true
3703            break
3704        }
3705    }
3706    var label_ob = jQuery(label_selector)
3707    if (found) {
3708        label_ob.addClass('required_if_font required_flag')
3709    } else {
3710        label_ob.removeClass('required_if_font required_flag')
3711    }
3712
3713    // If this is required but filled we don't want to make it red
3714    // check_fields_dependant_on(jQuery('#' + dependent_field_id))
3715    // We are always getting label_ids now:
3716    check_fields_dependant_on(jQuery(label_selector))
3717}
3718
3719
3720function setup_check_req_if_any(fields, label_selector, dependent_field_id) {
3721
3722    // <fields> are the fields that make dependent_field_id required if any have any value.
3723
3724    var new_fields = [];
3725
3726    // populate new_fields with jquery objects.
3727    for (var i=0; i<fields.length; i++) {
3728        var field_name = fields[i];
3729        var field_object = jQuery("[name='"+field_name+"']");
3730        // fields includes fields that might not exist depending on whether they are multi-fields or not.  Only include fields we can find.
3731        if (field_object.length > 0) {
3732            new_fields.push(field_object);
3733
3734            // If <field_object> has an attached 'other' field, include that in these checks too
3735            var other_selector = get_other_field_selector(field_object);
3736            if (other_selector) {new_fields.push($(other_selector))};
3737        }
3738    }
3739
3740    check_req_if_any(label_selector, dependent_field_id, new_fields);
3741
3742    jQuery.each(new_fields, function(key, val) {
3743        // val.change(function() {check_req_if_any(label_selector, dependent_field_id, new_fields)})
3744        // type = get_input_field_type(val)
3745        // if (jQuery.inArray(type, ['text', 'textarea']) != -1) {
3746        //     val.keyup(function() {check_req_if_any(label_selector, dependent_field_id, new_fields)})
3747        // }
3748        $(document).on('keyup change', val, function() {check_req_if_any(label_selector, dependent_field_id, new_fields)});
3749    })
3750}
3751
3752
3753
3754function ______BLOCKING_FIELD_REQUIREMENTS_V2______() {}
3755
3756let BLOCKING_FORM_HANDLERS = [];
3757let blocking_form_handler_id_counter = 0;
3758
3759class BlockingFormHandler {
3760    constructor(form) {
3761        this.form = form;
3762
3763        blocking_form_handler_id_counter++;
3764        this.id = blocking_form_handler_id_counter;
3765
3766        this.blocking_buttons_selector = '.blocking-error-check';
3767        this.blocking_buttons = this.form.find(this.blocking_buttons_selector);
3768
3769        // Make a unique id for the error message div and span.
3770        this.blocking_error_msg_div_id = 'blocking_error_message_' + this.id;
3771        this.blocking_error_msg_span_id = 'blocking_error_message_contents_' + this.id;
3772        this.blocking_error_msg_div = null;
3773        this.blocking_error_msg_span = null;
3774    }
3775
3776    setup_blocking_error_behavior() {
3777        if (!this.blocking_buttons || this.blocking_buttons.length == 0) {
3778            return;
3779        }
3780
3781        let first_blocking_button = this.blocking_buttons.first();
3782        let first_blocking_button_div = first_blocking_button.closest('div');
3783        this.blocking_error_msg_div = jQuery(`<div id="${this.blocking_error_msg_div_id}" class="error_msg_box centered" style="margin:15px;"></div>`);
3784        this.blocking_error_msg_span = jQuery(`<span id="${this.blocking_error_msg_span_id}"></span>`);
3785        this.blocking_error_msg_div.append(this.blocking_error_msg_span);
3786        this.blocking_error_msg_div.insertBefore(first_blocking_button_div);
3787        this.blocking_error_msg_div.hide();
3788
3789        this.blocking_error_msg_span.on('click', () => {
3790            jump_to_first_form_error_2(this.form);
3791        });
3792
3793        this.form.on('change blur focus', 'input, select, textarea, .jqx_detector', () => {
3794            // Having to setTimeout = 0 to force this event to be handled last.
3795            // Otherwise, it fires before the $(document).on('change blur focus') events, for some reason,
3796            // even though it is registered after them.
3797            setTimeout(() => {
3798                this.update_blocking_error_message();
3799            }, 0);
3800        });
3801
3802        this.form.on('keyup', 'input, textarea', () => {
3803            // Having to setTimeout = 0 to force this event to be handled last.
3804            // Otherwise, it fires before the $(document).on('keydown') events, for some reason,
3805            // even though it is registered after them.
3806            setTimeout(() => {
3807                this.update_blocking_error_message();
3808            }, 0);
3809        });
3810
3811        this.update_blocking_error_message();
3812    }
3813
3814    block_submit_event_if_errors(elem) {
3815        // If no, returns false.
3816
3817        // If this handler has no blocking buttons, ignore.
3818        if (!this.has_blocking_buttons()) {
3819            return false;
3820        }
3821
3822        // Test whether this calling element (button) is in this form. If not, do not block.
3823        if (!this.form.has(elem).length) {
3824            return false;
3825        }
3826
3827        // Test whether this calling element is a blocking button. If not, do not block.
3828        if (!elem.is(this.blocking_buttons_selector)) {
3829            return false;
3830        }
3831
3832        // Get form errors
3833        let active_errors = get_active_form_errors(this.form);
3834
3835        // If there are no unresolved requirements, do not block.
3836        if (!active_errors || active_errors.length == 0) {
3837            return false;
3838        }
3839
3840        // Looks like there is at least one error.
3841        // Flash the error message and return true.
3842        this.flash_blocking_error_message();
3843        return true;
3844    }
3845
3846    has_blocking_buttons() {
3847        return (this.blocking_buttons || this.blocking_buttons.length > 0);
3848    }
3849
3850    flash_blocking_error_message() {
3851        // this.blocking_error_msg_div.focus();
3852        jump_to_highlight_obj(this.blocking_error_msg_div);
3853    }
3854
3855    update_blocking_error_message() {
3856        if (!this.has_blocking_buttons()) {
3857            return;
3858        }
3859
3860        // // Get unresolved reqs (required and required_if-type fields)
3861        // let unresolved_reqs = get_unresolved_required_fields(this.form);
3862
3863        /// Get all active form errors
3864        let active_errors = get_active_form_errors(this.form);
3865
3866        // If there are no unresolved requirements, hide the error message.
3867        if (!active_errors || active_errors.length == 0) {
3868            this.blocking_error_msg_div.hide();
3869            return;
3870        } else {
3871            let error_text = this.build_blocking_error_msg(active_errors.length);
3872
3873            // Set the error message text if it has changed
3874            let current_error_text = this.blocking_error_msg_span.html();
3875            if (current_error_text != error_text) {
3876                this.blocking_error_msg_span.html(error_text);
3877            }
3878            this.blocking_error_msg_div.show();
3879        }
3880    }
3881
3882    build_blocking_error_msg(error_count) {
3883        let plural = (error_count > 1) ? 's' : '';
3884        let error_msg = `Found ${error_count} error${plural}. <a href="javascript:void(0)">Click to see first one</a>.`;
3885        return error_msg;
3886    }
3887}
3888
3889
3890function jump_to_first_form_error_2(form) {
3891
3892    // Jump to the first unresolved required field.
3893
3894    let first_error = get_first_form_error(form);
3895    jump_to_highlight_obj(first_error);
3896}
3897
3898
3899function get_first_form_error(form) {
3900
3901    let active_form_errors = get_active_form_errors(form);
3902
3903    if (!active_form_errors || active_form_errors.length == 0) {
3904        return null;
3905    }
3906
3907    let highest_error = null;
3908    let highest_offset_top = Number.POSITIVE_INFINITY;
3909
3910    active_form_errors.each(function() {
3911        let error = jQuery(this);
3912        let offset_top = error.offset().top;
3913
3914        if (offset_top < highest_offset_top) {
3915            highest_error = error;
3916            highest_offset_top = offset_top;
3917        }
3918    });
3919
3920    return highest_error;
3921}
3922
3923
3924function get_active_form_errors(form) {
3925    let unresolved_reqs = get_unresolved_required_fields(form);
3926    // TODO: Turn this on once we have tooltips working
3927    // let email_problems = get_email_field_problems(form);
3928    let email_problems = [];
3929
3930    if (
3931        (!unresolved_reqs || unresolved_reqs.length == 0)
3932        && (!email_problems || email_problems.length == 0)
3933    ) {
3934        return null;
3935    }
3936
3937    // Combine the two lists together and return
3938    let errors = jQuery.merge(unresolved_reqs, email_problems);
3939    return errors;
3940}
3941
3942
3943function form_has_errors(form) {
3944    let errors = get_active_form_errors(form);
3945    if (errors && errors.length > 0) {
3946        return true;
3947    }
3948    return false;
3949}
3950
3951
3952function first_unresolved_required_field(form) {
3953
3954    // Returns the first unresolved required field label.
3955    //   - class 'required_if_font' is present
3956    //   - class 'required_flag' is present
3957    //   - element is not hidden
3958    //   - element is not disabled
3959
3960
3961    let first_unresolved = form.find('.required_if_font.required_flag:visible:not(:disabled)').first();
3962
3963    // Filter first_unresolved by elements without tag data_non_blocking_error="True"
3964    first_unresolved = first_unresolved.filter(function() {
3965        return !(jQuery(this).attr('data_non_blocking_error') == 'True');
3966    });
3967
3968    // If there are no unresolved requirements, return null.
3969    if (!first_unresolved || first_unresolved.length == 0) {
3970        return null;
3971    }
3972    return first_unresolved;
3973}
3974
3975
3976function get_unresolved_required_fields(form) {
3977
3978    // Returns all unresolved required field labels
3979    //   - class 'required_if_font' is present
3980    //   - class 'required_flag' is present
3981    //   - element is not hidden
3982    //   - element is not disabled
3983
3984    let unresolved_reqs = form.find('.required_if_font.required_flag:visible:not(:disabled)');
3985
3986    // Filter first_unresolved by elements without tag data_non_blocking_error="True"
3987    unresolved_reqs = unresolved_reqs.filter(function() {
3988        return !(jQuery(this).attr('data_non_blocking_error') == 'True');
3989    });
3990
3991    return unresolved_reqs;
3992
3993}
3994
3995
3996function has_unresolved_required_fields(form) {
3997
3998    // Returns true if there are any unresolved required fields found in the form.
3999
4000    let first_unresolved = first_unresolved_required_field(form);
4001    if (first_unresolved) {
4002        return true;
4003    }
4004    return false;
4005
4006}
4007
4008
4009function get_active_required_fields(form) {
4010
4011    // Returns all required and required_if-type fields (their label elements, specifically) in the form.
4012
4013    let reqs = form.find('.required_flag:visible:not(:disabled)');
4014
4015    // Filter first_unresolved by elements without tag data_non_blocking_error="True"
4016    reqs = reqs.filter(function() {
4017        return !jQuery(this).attr('data_non_blocking_error');
4018    });
4019
4020    return reqs;
4021}
4022
4023
4024function filter_labels_by_looks_like_email(label_fields) {
4025
4026    // Returns all required and required_if-type fields that look like email fields.
4027    // Test is based on whether 'email' is in the label text.
4028
4029    let email_fields = label_fields.filter(function() {
4030        // Search the contents of the <label> tag for the 'email' string
4031        // Should match both 'email' and 'e-mail'
4032        return jQuery(this).text().match(/e-?mail/i);
4033    });
4034    return email_fields;
4035}
4036
4037
4038function get_email_field_problems(form) {
4039
4040    // Returns a list of email fields that do not pass email parsing
4041    // Uses the field_labels if provided, otherwise uses the form to find the fields.
4042
4043    let active_reqs = get_active_required_fields(form);
4044    if (!active_reqs || active_reqs.length == 0) {
4045        return null;
4046    }
4047
4048    let email_field_problems = [];
4049    active_reqs.each(function() {
4050        let field_label = jQuery(this);
4051
4052        let email_field_label = filter_labels_by_looks_like_email(field_label);
4053
4054        if (!email_field_label || email_field_label.length == 0) {
4055            return;
4056        }
4057
4058        let field = get_input_connected_to_label(field_label);
4059        if (!field || field.length == 0) {
4060            return;
4061        }
4062
4063        let field_type = get_input_field_type(field);
4064        if (field_type != 'text') {
4065            return;
4066        }
4067
4068        let field_value = field.val();
4069        if (!field_value) {
4070            return;
4071        }
4072
4073        let email = parse_email(field_value);
4074        if (!email) {
4075            email_field_problems.push(field_label);
4076        }
4077    });
4078
4079    // Convert this list of jQuery objects to a jQuery object.
4080    email_field_problems = jQuery(email_field_problems);
4081    return email_field_problems;
4082}
4083
4084
4085function parse_email(email_str) {
4086
4087        // Returns the email string if it is a valid email address, otherwise returns null.
4088        // This is a very simple test for email validity.
4089
4090        let email_regex = /\S+@\S+\.\S+/;
4091        if (email_str.match(email_regex)) {
4092            return email_str;
4093        }
4094        return null;
4095}
4096
4097
4098function setup_blocking_form_handlers() {
4099    let forms = jQuery('form');
4100    let blocking_form_handlers = [];
4101
4102    forms.each(function() {
4103        let form = jQuery(this);
4104        let handler = new BlockingFormHandler(form);
4105        handler.setup_blocking_error_behavior();
4106        blocking_form_handlers.push(handler);
4107    });
4108
4109    return blocking_form_handlers;
4110}
4111
4112
4113
4114function ______SHOW_IF___________() {}
4115
4116
4117function get_show_hide_targets(target_field_id, is_target) {
4118
4119    if (is_target) {
4120        return jQuery('#' + target_field_id);
4121    }
4122    // We need different targets for different layout classes.
4123    var target_obs = jQuery('.input_format_tr.left_right_cols');
4124    jQuery.merge(target_obs, jQuery('.input_format_tr.one_col'));
4125    jQuery.merge(target_obs, jQuery('.input_format_tr.two_cols > td'));
4126    jQuery.merge(target_obs, jQuery('.input_format_tr.three_cols > td'));
4127    jQuery.merge(target_obs, jQuery('.input_format_tr.four_cols > td'));
4128    target_obs = target_obs.has('#' + target_field_id)
4129
4130    return target_obs
4131}
4132
4133
4134function arrays_overlap(array1, array2) {
4135
4136    var found = false;
4137    jQuery.each(array1, function(i1, v1) {
4138        jQuery.each(array2, function(i2, v2) {
4139            if (v1 == v2) {
4140                found = true;
4141                return false;   // This tells .each() to stop (like break).
4142            }
4143        })
4144        if (found) return false; // Break out of outer loop.
4145    })
4146
4147    return found;
4148}
4149
4150
4151function setup_check_show_if(target_field_id, target_label_id, trigger_field_name, trigger_vals, multi_suffix, is_target) {
4152
4153    var field_selector = "[name='" + trigger_field_name + "']";
4154
4155    var trigger_field_ob = $(field_selector)
4156    // If we haven't found the trigger field try the multi case.
4157    if (trigger_field_ob.length == 0) {
4158        field_selector = "[name='" + trigger_field_name + multi_suffix + "']"
4159        trigger_field_ob = $(field_selector)
4160    }
4161
4162    // If there is an 'other' field, get that selector and set that up too.
4163    var other_selector = get_other_field_selector(trigger_field_ob)
4164    if (other_selector) {
4165        setup_other_check_show_if(target_field_id, target_label_id, other_selector, trigger_vals, is_target);
4166    }
4167
4168    $(document).on('keyup change', field_selector,
4169        function () {
4170            check_show_if(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target);
4171        }
4172    )
4173    check_show_if(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target);
4174}
4175
4176
4177function setup_other_check_show_if(target_field_id, target_label_id, trigger_field_selector, trigger_vals, is_target) {
4178
4179    // This sets up the check_req_if for the 'other' field case. It doesn't
4180    // need to deal with the multi vs. plain trouble because that is dealt with in the caller.
4181
4182    var trigger_field_ob = $(trigger_field_selector)
4183
4184    $(document).on('keyup change', trigger_field_selector,
4185        function () {
4186            check_show_if(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target)
4187        }
4188    )
4189    check_show_if(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target)
4190}
4191
4192
4193function check_show_if(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target) {
4194
4195    var values = get_field_values(trigger_field_ob);
4196    var target_obs = get_show_hide_targets(target_field_id, is_target);
4197
4198    if (arrays_overlap(trigger_vals, values)) {
4199        target_obs.show();
4200    } else {
4201        target_obs.hide();
4202    }
4203}
4204
4205
4206function setup_check_show_if_not(target_field_id, target_label_id, trigger_field_name, trigger_vals, multi_suffix, is_target) {
4207
4208    var field_selector = "[name='" + trigger_field_name + "']";
4209
4210    var trigger_field_ob = $(field_selector)
4211    // If we haven't found the trigger field try the multi case.
4212    if (trigger_field_ob.length == 0) {
4213        field_selector = "[name='" + trigger_field_name + multi_suffix + "']"
4214        trigger_field_ob = $(field_selector)
4215    }
4216
4217    // If there is an 'other' field, get that selector and set that up too.
4218    var other_selector = get_other_field_selector(trigger_field_ob)
4219    if (other_selector) {
4220        setup_other_check_show_if_not(target_field_id, target_label_id, other_selector, trigger_vals, is_target);
4221    }
4222
4223    $(document).on('keyup change', field_selector,
4224        function () {
4225            check_show_if_not(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target);
4226        }
4227    )
4228    check_show_if_not(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target);
4229}
4230
4231
4232function setup_other_check_show_if_not(target_field_id, target_label_id, trigger_field_selector, trigger_vals, is_target) {
4233
4234    // This sets up the check_req_if for the 'other' field case. It doesn't
4235    // need to deal with the multi vs. plain trouble because that is dealt with in the caller.
4236
4237    var trigger_field_ob = $(trigger_field_selector)
4238
4239    $(document).on('keyup change', trigger_field_selector,
4240        function () {
4241            check_show_if_not(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target)
4242        }
4243    )
4244    check_show_if_not(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target)
4245}
4246
4247
4248function check_show_if_not(target_field_id, target_label_id, trigger_field_ob, trigger_vals, is_target) {
4249
4250    var values = get_field_values(trigger_field_ob);
4251    var target_obs = get_show_hide_targets(target_field_id, is_target);
4252
4253    if (arrays_overlap(trigger_vals, values)) {
4254        target_obs.hide();
4255    } else {
4256        target_obs.show();
4257    }
4258}
4259
4260
4261function setup_check_show_if_none(target_field_id, trigger_fields, is_target) {
4262
4263    var new_fields = []
4264
4265    // populate new_fields with jquery objects.
4266    for (var i=0; i<trigger_fields.length; i++) {
4267        var new_field_ob = jQuery("[name='"+trigger_fields[i]+"']")
4268        // fields includes fields that might not exist depending on whether they are multi-fields or not.  Only include fields we can find.
4269        if (new_field_ob.length > 0) {
4270            new_fields.push(new_field_ob)
4271
4272            // If <new_field_ob> has an attached 'other' field, include that in these checks too
4273            var other_selector = get_other_field_selector(new_field_ob)
4274            if (other_selector) {new_fields.push($(other_selector))}
4275        }
4276    }
4277
4278    check_show_if_none(target_field_id, new_fields, is_target)
4279    jQuery.each(new_fields, function(key, val) {
4280        $(document).on('keyup change', val, function() {check_show_if_none(target_field_id, new_fields, is_target)})
4281    })
4282}
4283
4284
4285function check_show_if_none(target_field_id, trigger_fields, is_target) {
4286    var found = false;
4287    for (var i=0; i<trigger_fields.length; i++) {
4288        var field = trigger_fields[i];
4289        // Get the values of the field object
4290        var values = get_field_values(field);
4291        if (values[0] != '') {
4292            found = true;
4293            break;
4294        }
4295    }
4296
4297    var target_obs = get_show_hide_targets(target_field_id, is_target);
4298    if (found) {
4299        target_obs.hide();
4300    } else {
4301        target_obs.show();
4302    }
4303}
4304
4305
4306function setup_check_show_if_any(target_field_id, trigger_fields, is_target) {
4307
4308    var new_fields = []
4309
4310    // populate new_fields with jquery objects.
4311    for (var i=0; i<trigger_fields.length; i++) {
4312        var new_field_ob = jQuery("[name='"+trigger_fields[i]+"']")
4313        // fields includes fields that might not exist depending on whether they are multi-fields or not.  Only include fields we can find.
4314        if (new_field_ob.length > 0) {
4315            new_fields.push(new_field_ob)
4316
4317            // If <new_field_ob> has an attached 'other' field, include that in these checks too
4318            var other_selector = get_other_field_selector(new_field_ob)
4319            if (other_selector) {new_fields.push($(other_selector))}
4320        }
4321    }
4322
4323    check_show_if_any(target_field_id, new_fields, is_target)
4324    jQuery.each(new_fields, function(key, val) {
4325        $(document).on('keyup change', val, function() {check_show_if_any(target_field_id, new_fields, is_target)})
4326    })
4327}
4328
4329
4330function check_show_if_any(target_field_id, trigger_fields, is_target) {
4331    var found = false;
4332    for (var i=0; i<trigger_fields.length; i++) {
4333        var field = trigger_fields[i];
4334        // Get the values of the field object
4335        var values = get_field_values(field);
4336        if (values[0] != '') {
4337            found = true;
4338            break;
4339        }
4340    }
4341
4342    var target_obs = get_show_hide_targets(target_field_id, is_target);
4343    if (found) {
4344        target_obs.show();
4345    } else {
4346        target_obs.hide();
4347    }
4348}
4349
4350
4351function ______FORM_SUBMIT_WAITING_MODAL_______() {}
4352
4353const SUBMIT_WAITING_MODAL_CONTENTS_ID_TAG_NAME = 'data-waiting-modal-contents-id';
4354const SUBMIT_WAITING_MODAL_TITLE_ID_TAG_NAME = 'data-waiting-modal-title-id';
4355
4356
4357function show_submit_waiting_modal_if_has_def(linked_elem, modal_args={}) {
4358
4359    // Check whether this element has the tag, 'data-waiting-modal-def-id'. If it
4360    // does, and it has a value, find the element with that id. That is our modal
4361    // definition element.
4362    // Parse that waiting_modal_def element to extract the title and contents.
4363    // Modal def format:
4364    // <div class="waiting_modal_def">
4365    //     <div class="waiting_modal_title">Title</div>
4366    //     <div class="waiting_modal_contents">Contents</div>
4367    // </div>
4368
4369    let modal_contents_id = linked_elem.attr(SUBMIT_WAITING_MODAL_CONTENTS_ID_TAG_NAME);
4370    if (!modal_contents_id) {
4371        return false;
4372    }
4373
4374    let modal_title_id = linked_elem.attr(SUBMIT_WAITING_MODAL_TITLE_ID_TAG_NAME);
4375    let modal_title_contents = '';
4376    if (modal_title_id) {
4377        modal_title_contents = jQuery('#' + modal_title_id).html();
4378    }
4379
4380    show_submit_waiting_modal(modal_contents_id, modal_title_contents, modal_args);
4381    return true;
4382}
4383
4384
4385function show_submit_waiting_modal(contents_id = '', title_html = '', args = {}) {
4386
4387    jQuery.colorbox({
4388        width:'600px',
4389        height:'400px',
4390        top: '100px',
4391        inline:true,
4392        href:'#' + contents_id,
4393        // html: contents_html,
4394        title: title_html,
4395        overlayClose: false,
4396        trapFocus: true,
4397        closeButton: true,
4398        returnFocus: true,
4399        speed: 50,
4400        ...args,
4401        // onComplete: function() {
4402        //     // Callback that fires right after loaded content is displayed.
4403        //     %s
4404        // },
4405        // onCleanup: function() {}
4406        // onClosed: function() {
4407        //     // Callback that fires once ColorBox is closed.
4408        //     %s
4409        // }
4410    });
4411}
4412
4413
4414
4415
4416function ______OTHER_FIELD_TESTERS______() {}
4417
4418
4419function contains_html(field_sel) {
4420    // Returns true if any fields selected by <field_sel> contains any HTML tags.
4421    // note that this detects "<ANYTHING>" as html, but not "< div>" which is standard browser behavior
4422
4423    var found = false;
4424
4425    $(field_sel).each(function() {
4426        // I don't know why there's a try here
4427        try {
4428            var contents = $(this).val() // this is a string
4429            contents = contents.trim(); // Get rid of starting / trailing white space
4430
4431            if (contents.length == 0) {
4432              // No contents - skip to the next field
4433              return true;
4434            }
4435
4436            var parsed = $.parseHTML(contents);  // Returns an array of contents elements
4437            if (parsed.length > 1) {
4438              // multiple items means that there is more than just text.
4439              // If it was just text it would have a single #text element
4440              found = true;
4441            } else if ($(parsed[0]).nodeName.toLowerCase() != '#text') {
4442              // if the parsed text isn't #text then it is wrapped in a tag.
4443              found = true;
4444            }
4445            return !found   // Breaks out of each if found.
4446        } catch(err) {}
4447    });
4448    return found
4449}
4450
4451
4452function ______MISC_____() {}
4453
4454
4455var tag_regex = new RegExp("<\s*/?\s*[a-zA-Z]+.*?>", 'gs')
4456function strip_html(s, replace_with=' ') {
4457    // This can't deal with the script tag properly - it returns the contents of the script. Improve when needed.
4458    if (typeof s == 'undefined') { return ''; }
4459    // We should improve this to account for "spacy" tags vs non "spacy" tags.
4460    var ans = s.replace(tag_regex, replace_with);
4461    return ans;
4462}
4463
4464var entityMap = {
4465  "&": "&amp;",
4466  "<": "&lt;",
4467  ">": "&gt;",
4468  '"': '&quot;',
4469  "'": '&#39;',
4470  "/": '&#x2F;'
4471};
4472
4473function escape_html(string) {
4474  return String(string).replace(/[&<>"'\/]/g, function (s) {
4475    return entityMap[s];
4476  });
4477}
4478
4479
4480function _____STARS_RATING_WIGET_____(){}
4481
4482/* These routines are used by star_rating_widget.py */
4483
4484
4485function make_selectable_stars(stars_id) {
4486
4487    var stars_el = $('#'+stars_id);
4488    var star = stars_el.children('.rm_star');
4489
4490    star.hover(function() {
4491        // hover enter
4492
4493        var $this = $(this);
4494
4495        // when starting a hover we store the saved non-hover value in '_tmp_saved_val'
4496        var non_hover_saved_val = $this.parent().data('_tmp_saved_val');
4497        if (!non_hover_saved_val) {
4498            var lit_stars = $this.parent().children('.fa-star');
4499            curr_val = lit_stars.length;
4500            $this.parent().data('_tmp_saved_val', curr_val);
4501        }
4502
4503        // hide show correct stars
4504        $this.removeClass('fa-star-o fa-star-half-o');
4505        $this.addClass('fa-star');
4506
4507        $prev_stars = $this.prevAll('.rm_star');
4508        $prev_stars.removeClass('fa-star-o fa-star-half-o');
4509        $prev_stars.addClass('fa-star');
4510
4511        $next_stars = $this.nextAll('.rm_star');
4512        $next_stars.removeClass('fa-star fa-star-half-o');
4513        $next_stars.addClass('fa-star-o');
4514
4515        // fire custom dom event
4516        var curr_hover_val = $prev_stars.length + 1;
4517        $this.parent().trigger('rm-stars-hover-val-update', [curr_hover_val]);
4518
4519    }, function(){
4520        // hover exit
4521
4522        var $this = $(this);
4523
4524        // return stars to non-hover value
4525        var non_hover_saved_val = $this.parent().data('_tmp_saved_val');
4526        if (non_hover_saved_val || non_hover_saved_val === 0) {
4527            $this.parent().data('_tmp_saved_val', null);
4528            $this.parent().trigger('rm-stars-hover-val-update', [non_hover_saved_val]);
4529
4530            update_stars_display_for_value(stars_el, non_hover_saved_val);
4531        }
4532    });
4533
4534    star.click(function(){
4535        // trigger custom dom event on stars element with value of clicked star
4536        var $this = $(this);
4537
4538        // we get the value of the clicked star by counting sibling stars
4539        // that are previous to it in the dom
4540        var num_previous_stars = $this.prevAll('.rm_star').length;
4541        var clicked_star_value = num_previous_stars + 1;
4542
4543        // current policy is that if the user clicks the currently
4544        // selected star, then we clear the star value -- set it to 0
4545        var curr_non_hover_val = $this.parent().data('_tmp_saved_val');
4546        if (clicked_star_value == curr_non_hover_val) {
4547            // clicked_star_value = 0;
4548            // Giving this a value of 0 breaks the required js
4549            clicked_star_value = '';
4550        }
4551
4552        stars_el.trigger('rm-stars-val-update', [clicked_star_value]);
4553    });
4554
4555    stars_el.on('rm-stars-val-update', function(e, new_val){
4556        $this = $(this);
4557        $this.data('_tmp_saved_val', new_val);
4558        update_stars_display_for_value(stars_el, new_val);
4559    });
4560
4561    function update_stars_display_for_value(stars_el, value) {
4562        var stars = stars_el.children('.rm_star');
4563
4564        stars.slice(0, value)
4565            .removeClass('fa-star-o fa-star-half-o')
4566            .addClass('fa-star');
4567
4568        stars.slice(value)
4569            .removeClass('fa-star fa-star-half-o')
4570            .addClass('fa-star-o')
4571    }
4572}
4573
4574
4575function make_selectable_labeled_stars(labeled_stars_id, stars_opts) {
4576
4577    var labeled_stars = $('#'+labeled_stars_id);
4578    var stars_label = labeled_stars.find('.rm_stars_label');
4579    var stars = labeled_stars.find('.rm_stars');
4580
4581    stars.on('rm-stars-hover-val-update', function(e, val) {
4582        stars_label.html(stars_opts[val]);
4583
4584        // Try to improve the min-width as different labels are shown.
4585        var width = stars_label.css('width').slice(0, -2);
4586        var old_min_width = stars_label.css('min-width').slice(0, -2);
4587        width = parseInt(width);
4588        old_min_width = parseInt(old_min_width);
4589        var new_min = Math.max(width, old_min_width);
4590        stars_label.css('min-width', new_min);
4591    });
4592
4593    stars.on('rm-stars-val-update', function(e, val) {
4594        var new_label = stars_opts[val]
4595        if (val == '') {
4596            // We've replaced 0 with '' as a val to let required_js work.
4597            new_label = stars_opts[0];
4598        }
4599        stars_label.html(new_label);
4600    });
4601}
4602
4603
4604function connect_stars_input_hidden_field(input_widget_id, hidden_field_id) {
4605
4606    var input_widget_el = $('#'+input_widget_id);
4607    var hidden_field_el = $('#'+hidden_field_id);
4608    var stars = input_widget_el.find('.rm_stars');
4609    stars.on('rm-stars-val-update', function(e, val) {
4610        hidden_field_el.val(val);
4611        // This isn't triggered on the hidden text field so we have to trigger
4612        // it by hand.
4613        hidden_field_el.trigger( "change" );
4614    });
4615}
4616
4617
4618function _____TOUCH_CHECKING___(){}
4619
4620/* Modernizr 2.8.3 (Custom Build) | MIT & BSD
4621 * Build: http://modernizr.com/download/#-touch-cssclasses-teststyles-prefixes-cssclassprefix:modernizr!
4622
4623Includings only touch device detection.
4624Example: if (Modernizr.touch) { alert("touch") }
4625
4626 */
4627;window.Modernizr=function(a,b,c){function w(a){j.cssText=a}function x(a,b){return w(m.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}var d="2.8.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;
4627var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:t(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c};for(var B in n)v(n,B)&&(s=B.toLowerCase(),e[s]=n[B](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)v(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" modernizr-"+(b?"":"no-")+a),e[a]=b}return e},w(""),i=k=null,e._version=d,e._prefixes=m,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" modernizr-js modernizr-"+q.join(" modernizr-"):""),e}(this,this.document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.