Files
mrbs-equbao-2026/js/edit_entry.js.php
T
人事系统开发 1ba6efd8ed MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、JS空集合保护、
会话过期体验优化(403 JSON)、display_errors 关闭、
固定 key 根治 Integrity check failed 等全部改动

注意:config.inc.php/.htaccess/.user.ini 含敏感信息,
通过 .gitignore 排除,勿推送到公开仓库。
2026-09-09 16:55:02 +08:00

1844 lines
58 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS;
require "../defaultincludes.inc";
http_headers(array("Content-type: application/x-javascript"),
60*30); // 30 minute expiry
?>
'use strict';
var isBookAdmin;
<?php
// Create a JavaScript version of the PHP $is_mandatory_fields variable
echo "const mandatoryFields = {";
if (!empty($is_mandatory_field))
{
$lines = [];
foreach ($is_mandatory_field as $key => $value)
{
list($table, $field_name) = explode('.', $key, 2);
if (($table === 'entry') && !empty($value))
{
// Prefix custom field names
if (!in_array($field_name, $standard_fields['entry']))
{
$field_name = VAR_PREFIX . $field_name;
}
$line = " $field_name: ";
if (is_bool($value))
{
$line .= ($value) ? 'true' : 'false';
}
else
{
$line .= '[' . implode(', ', $value) . ']';
}
$lines[] = $line;
}
}
if (!empty($lines))
{
echo "\n" . implode(",\n", $lines) . "\n";
}
}
echo "};\n";
?>
<?php
// Set (if set is true) or clear (if set is false) a timer
// to check for conflicts periodically in case someone else
// books the slot you are looking at. If setting the timer
// it also performs an immediate check.
?>
var conflictTimer = function conflictTimer(set) {
<?php
if (!empty($ajax_refresh_rate))
{
?>
if (set)
{
<?php
// (Note the config variable is in seconds, but the setInterval() function
// uses milliseconds)
// Only set the timer if the page is visible
?>
if (!isHidden())
{
checkConflicts(true);
conflictTimer.id = window.setInterval(function() {
checkConflicts(true);
}, <?php echo $ajax_refresh_rate * 1000 ?>);
}
}
else if (typeof conflictTimer.id !== 'undefined')
{
window.clearInterval(conflictTimer.id);
}
<?php
}
?>
};
<?php
// Function to (1) add 'required' attributes to the rep_interval and rep_end_date fields
// if it's a repeating booking, (2) show/hide the repeat end date and skip fields and
// (3) display the secondary repeat type fieldset appropriate to the selected repeat type.
?>
var changeRepTypeDetails = function changeRepTypeDetails() {
var repType = parseInt($('input[name="rep_type"]:checked').val(), 10);
var isRepeat = (repType !== <?php echo RepeatRule::NONE ?>);
<?php
// Add a 'required' attribute to the rep_interval input to prevent users entering an
// empty string. But remove it if it's not a repeating entry, because if they happen
// to have an empty string they won't see the validation message since the input will
// be hidden.
?>
$('#rep_interval').prop('required', isRepeat);
<?php
// Add a 'required' attribute to the rep_end_date input if shown to prevent users
// entering an empty string. Show/hide the repeat end date and skip fields
?>
$('#rep_end_date').prop('required', isRepeat).parent().toggle(isRepeat);
$('#skip').parent().toggle(isRepeat);
<?php // Show the appropriate details ?>
$('.rep_type_details').hide();
switch (repType)
{
case <?php echo RepeatRule::WEEKLY ?>:
$('#rep_weekly').show();
break;
case <?php echo RepeatRule::MONTHLY ?>:
$('#rep_monthly').show();
break;
default:
break;
}
};
<?php
// Function to change the units for the repeat interval to match the repeat type.
?>
var changeRepIntervalUnits = function changeRepIntervalUnits() {
var repType = parseInt($('input[name="rep_type"]:checked').val(), 10);
var repInterval = parseInt($('input[name="rep_interval"]').val(), 10);
var units = $('#interval_units');
var text;
switch (repType)
{
case <?php echo RepeatRule::DAILY ?>:
text = (repInterval === 1) ? '<?php echo get_js_vocab('day') ?>' : '<?php echo get_js_vocab('days') ?>';
break;
case <?php echo RepeatRule::WEEKLY ?>:
text = (repInterval === 1) ? '<?php echo get_js_vocab('week') ?>' : '<?php echo get_js_vocab('weeks') ?>';
break;
case <?php echo RepeatRule::MONTHLY ?>:
text = (repInterval === 1) ? '<?php echo get_js_vocab('month') ?>' : '<?php echo get_js_vocab('months') ?>';
break;
case <?php echo RepeatRule::YEARLY ?>:
text = (repInterval === 1) ? '<?php echo get_js_vocab('year_lc') ?>' : '<?php echo get_js_vocab('years') ?>';
break;
default:
text = units.text();
break;
}
units.text(text);
units.parent().toggle(repType !== <?php echo RepeatRule::NONE ?>);
};
// areaConfig returns the properties ('enable_periods', etc.) for an area,
// by default the current area
var areaConfig = function areaConfig(property, areaId) {
var properties = ['enable_periods', 'n_periods', 'default_duration', 'max_duration_enabled',
'max_duration_secs', 'max_duration_periods', 'max_duration_qty',
'max_duration_units', 'timezone'];
var i, p, room;
if ($.inArray(property, properties) < 0)
{
throw new Error("areaConfig(): invalid property '" + property + "' passed to areaConfig");
}
if (areaId === undefined)
{
areaId = $('#area').val();
}
if (areaConfig.data === undefined)
{
areaConfig.data = [];
}
if (areaConfig.data[areaId] === undefined)
{
areaConfig.data[areaId] = {};
room = $('#rooms' + areaId);
for (i=0; i<properties.length; i++)
{
p = properties[i];
areaConfig.data[areaId][p] = room.data(p);
}
}
return areaConfig.data[areaId][property];
};
<?php
// Check to see whether any time slots should be removed from the time
// select on the grounds that they don't exist due to a transition into DST.
// Don't do this if we're using periods, because it doesn't apply then
//
// jqDate a jQuery object for the datepicker in question
?>
function checkTimeSlots(jqDate)
{
if (!areaConfig('enable_periods'))
{
var siblings = jqDate.siblings();
var select = jqDate.parent().parent().siblings('select:visible');
var slots = [];
select.find('option').each(function() {
slots.push($(this).val());
});
<?php
// We pass the id of the element as the request id so that we can match
// the result to the request
?>
var params = {csrf_token: getCSRFToken(),
id: select.attr('id'),
day: parseInt(siblings.filter('input[id*="day"]').val(), 10),
month: parseInt(siblings.filter('input[id*="month"]').val(), 10),
year: parseInt(siblings.filter('input[id*="year"]').val(), 10),
tz: areaConfig('timezone'),
slots: slots};
if(args.site)
{
params.site = args.site;
}
$.post('ajax/check_slot.php', params, function(result) {
$.each(result.slots, function(key, value) {
$('#' + result.id).find('option[value="' + value + '"]').remove();
});
<?php
// Now that we've removed some options we need to equalise the widths
?>
adjustWidth($('#start_seconds'),
$('#end_seconds'));
}, 'json');
} <?php // if (!areaConfig('enable_periods')) ?>
}
<?php
// Executed when the user clicks on the all_day checkbox.
?>
function onAllDayClick()
{
var form = $('#main');
if (form.length === 0)
{
return;
}
var startSelect = form.find('#start_seconds'),
endSelect = form.find('#end_seconds'),
allDay = form.find('#all_day');
var startDatepicker = form.find('#start_date'),
endDatepicker = form.find('#end_date');
var date, firstSlot, lastSlot;
if (allDay.is(':checked')) // If checking the box...
{
<?php
// Save the old values, disable the inputs and, to avoid user confusion,
// show the start and end times as the beginning and end of the booking
?>
firstSlot = parseInt(startSelect.find('option').first().val(), 10);
lastSlot = parseInt(endSelect.find('option').last().val(), 10);
onAllDayClick.oldStart = parseInt(startSelect.val(), 10);
onAllDayClick.oldStartDatepicker = startDatepicker.val();
startSelect.val(firstSlot);
startSelect.prop('disabled', true);
onAllDayClick.oldEnd = parseInt(endSelect.val(), 10);
onAllDayClick.oldEndDatepicker = endDatepicker.val();
endSelect.val(lastSlot);
if ((lastSlot < firstSlot) &&
(onAllDayClick.oldStartDatepicker === onAllDayClick.oldEndDatepicker))
{
<?php
// If the booking day spans midnight then the first and last slots
// are going to be on different days.
// This code works because new Date() with just a date string generates a UTC
// date and toISOString() always returns a UTC datetime.
?>
if (onAllDayClick.oldStart < firstSlot)
{
date = new Date(onAllDayClick.oldStartDatepicker);
date.setDate(date.getDate() - 1);
startDatepicker.val(date.toISOString().split('T')[0]);
}
else
{
date = new Date(onAllDayClick.oldEndDatepicker);
date.setDate(date.getDate() + 1);
endDatepicker.val(date.toISOString().split('T')[0]);
}
}
endSelect.prop('disabled', true);
}
else <?php // restore the old values and re-enable the inputs ?>
{
startSelect.val(onAllDayClick.oldStart);
startDatepicker.val(onAllDayClick.oldStartDatepicker);
startSelect.prop('disabled', false);
endSelect.val(onAllDayClick.oldEnd);
endDatepicker.val(onAllDayClick.oldEndDatepicker);
endSelect.prop('disabled', false);
}
adjustSlotSelectors(); <?php // need to get the duration right ?>
}
<?php
// Set the error messages to be used for the various fields. We do this twice:
// once to redefine the HTML5 error message and once for JavaScript alerts, for those
// browsers not supporting HTML5 field validation.
?>
function validationMessages(areaId)
{
var field, label;
<?php
// First, create a property in the vocab object for each of the mandatory fields. The name and rooms field are
// implicitly mandatory.
?>
validationMessages.vocab = {};
validationMessages.vocab['name'] = '';
validationMessages.vocab['rooms'] = '';
for (const [fieldName, value] of Object.entries(mandatoryFields))
{
if ((value === true) || (Array.isArray(value) && value.includes(parseInt(areaId))))
{
validationMessages.vocab[fieldName] = '';
}
}
<?php
// Then (a) fill each of those properties with an error message and (b) redefine
// the HTML5 error message
?>
for (var key in validationMessages.vocab)
{
if (validationMessages.vocab.hasOwnProperty(key))
{
label = $("label[for=" + key + "]");
if (label.length > 0)
{
validationMessages.vocab[key] = label.text();
validationMessages.vocab[key] = '"' + validationMessages.vocab[key] + '" ';
validationMessages.vocab[key] += '<?php echo get_js_vocab("is_mandatory_field") ?>';
field = document.getElementById(key);
if (field.setCustomValidity && field.willValidate)
{
<?php
// We define our own custom event called 'validate' that is triggered on the
// 'change' and 'input' events. We need both events because (a) the change event
// for text input is only triggered when the element loses focus and we want the
// validation to happen whenever a character is input, (b) autocomplete in some
// browsers, eg Firefox, does not trigger the input event and (c) the input event
// is not triggered for checkboxes or select elements.
?>
$(field).on('validate', function(e) {
<?php
// need to clear the custom error message otherwise the browser will
// assume the field is invalid
?>
e.target.setCustomValidity("");
if (!e.target.validity.valid)
{
e.target.setCustomValidity(validationMessages.vocab[$(e.target).attr('id')]);
}
});
$(field).on('change input', function() {
$(this).trigger('validate');
});
<?php
// When a form validation fails we need to clear the submit flag because
// otherwise checkConflicts() won't do anything (because we don't check
// for conflicts on a submit)
?>
$(field).on('invalid', function() {
$(this).closest('form').removeData('submit');
});
<?php
// Trigger the validate event when the form is first loaded
?>
$(field).trigger('validate');
}
} <?php // if (label.length > 0) ?>
} <?php // if (validationMessages.vocab.hasOwnProperty(key)) ?>
} <?php //for ?>
}
<?php
// do a little form verifying
?>
function validate(form)
{
var testInput = document.createElement("input");
var testSelect = document.createElement("select");
var validForm = true;
<?php
// Mandatory fields (INPUT elements, except for checkboxes).
// Only necessary if the browser doesn't support the HTML5 pattern or
// required attributes
?>
if (!("pattern" in testInput) || !("required" in testInput))
{
form.find('input').not('[type="checkbox"]').each(function() {
var id = $(this).attr('id');
if (validationMessages.vocab[id])
{
if (<?php echo REGEX_TEXT_NEG ?>.test($(this).val()))
{
window.alert(validationMessages.vocab[id]);
validForm = false;
return false;
}
}
});
if (!validForm)
{
return false;
}
}
<?php
// Mandatory fields (INPUT elements, checkboxes only).
// Only necessary if the browser doesn't support the HTML5 required attribute
?>
if (!("required" in testInput))
{
form.find('input').filter('[type="checkbox"]').each(function() {
var id = $(this).attr('id');
if (validationMessages.vocab[id])
{
if (!$(this).is(':checked'))
{
window.alert(validationMessages.vocab[id]);
validForm = false;
return false;
}
}
});
if (!validForm)
{
return false;
}
}
<?php
// Mandatory fields (TEXTAREA elements).
// Note that the TEXTAREA element only supports the "required" attribute and not
// the "pattern" attribute. So we need to do these tests in all cases because
// the browser will let through a string consisting only of whitespace.
?>
form.find('textarea').each(function() {
var id = $(this).attr('id');
if (validationMessages.vocab[id])
{
if (<?php echo REGEX_TEXT_NEG ?>.test($(this).val()))
{
window.alert(validationMessages.vocab[id]);
validForm = false;
return false;
}
}
});
if (!validForm)
{
return false;
}
<?php
// Mandatory fields (SELECT elements).
// Only necessary if the browser doesn't support the HTML5 required attribute
?>
if (!("required" in testSelect))
{
form.find('select').each(function() {
var id = $(this).attr('id');
if (validationMessages.vocab[id])
{
if ($(this).val() === '')
{
window.alert(validationMessages.vocab[id]);
validForm = false;
return false;
}
}
});
if (!validForm)
{
return false;
}
}
<?php // Check that the start date is not after the end date ?>
var dateDiff = getDateDifference();
if (dateDiff < 0)
{
window.alert("<?php echo get_js_vocab('start_after_end_long')?>");
return false;
}
<?php // Repeat checks ?>
var repType = form.find('input:radio[name=rep_type]:checked').val();
if ((repType !== undefined) && (parseInt(repType, 10) !== <?php echo RepeatRule::NONE ?>))
{
<?php
// Check that there's a sensible value for rep_interval. Only necessary
// if the browser doesn't support the HTML5 min and step attributes
?>
if ((!("min" in testInput) || !(("step" in testInput))) &&
(form.find('#rep_interval').val() < 1))
{
window.alert("<?php echo get_js_vocab('invalid_rep_interval') ?>");
return false;
}
<?php
// Check that the repeat end date has been set (people often forget to do so). If it's the
// same as the entry end date then it probably hasn't.
?>
if ($('input[name="rep_end_date"]').val() === $('input[name="end_date"]').val())
{
if (!window.confirm("<?php echo get_js_vocab('confirm_rep_end_date') ?>"))
{
return false;
}
}
<?php
// Check that the repeat end date is not before the start date. If it is, nothing
// will be booked, which is probably not what was intended.
?>
if ($('input[name="rep_end_date"]').val() < $('input[name="start_date"]').val())
{
window.alert("<?php echo get_js_vocab('rep_end_date_before_start_date') ?>");
return false;
}
}
<?php
// Form submit can take some time, especially if mails are enabled and
// there are more than one recipient. To avoid users doing weird things
// like clicking more than one time on submit button, we hide it as soon
// it is clicked.
?>
form.find('input[type=submit]').prop('disabled', true);
<?php
// would be nice to also check date to not allow Feb 31, etc...
?>
return true;
}
<?php
// function to check whether the proposed booking would (a) conflict with any other bookings
// and (b) conforms to the booking policies. Makes an Ajax call to edit_entry_handler but does
// not actually make the booking.
//
// If optional is true then the check is not carried out if there's already an
// outstanding request in the queue
?>
function checkConflicts(optional)
{
<?php // Get the value of the field in the form ?>
function getFormValue(formInput)
{
var value;
<?php
// Scalar parameters (three types - checkboxes, radio buttons and the rest)
?>
if (formInput.attr('name').indexOf('[]') === -1)
{
if (formInput.filter(':checkbox').length > 0)
{
value = formInput.is(':checked') ? '1' : '';
}
else if (formInput.filter(':radio').length > 0)
{
value = formInput.filter(':checked').val();
}
else
{
value = formInput.val();
}
}
<?php
// Array parameters (two types - checkboxes and the rest, which could be
// <select> elements or else multiple ordinary inputs with a *[] name
?>
else
{
value = [];
formInput.each(function() {
if ((formInput.filter(':checkbox').length === 0) || $(this).is(':checked'))
{
var thisValue = $(this).val();
if ($.isArray(thisValue))
{
$.merge(value, thisValue);
}
else
{
value.push($(this).val());
}
}
});
}
return value;
} <?php // function getFormValue()
// Keep track of how many requests are still with the server. We don't want
// to keep sending them if they're not coming back
?>
if (checkConflicts.nOutstanding === undefined)
{
checkConflicts.nOutstanding = 0;
}
<?php
// If this is an optional request and there are already some check requests
// in the queue, then don't bother with this one.
?>
if (optional && checkConflicts.nOutstanding)
{
return;
}
<?php
// We set a small timeout on checking the booking in order to allow time for
// the click handler on the Submit buttons to set the data in the form. We then
// test the data and if it is set we don't validate the booking because we're going off
// somewhere else. [This isn't an ideal way of doing this. The problem is that
// the change event for a text input can be fired when the user clicks the submit
// button - but how can you tell that it was the clicking of the submit button that
// caused the change event?]
?>
var timeout = 200; <?php // ms ?>
window.setTimeout(function() {
var params = {};
var form = $('form#main');
<?php
// Don't do anything if (a) the form doesn't exist (which it won't if the user
// hasn't logged in) or (b) if the submit button has been pressed
?>
if ((form.length === 0) || form.data('submit'))
{
return;
}
// Some browsers, eg Firefox, allow a 'multiple' select element to have nothing selected,
// even though it has the required attribute. This won't happen when the form is submitted,
// but can happen when the user is selecting options. If this happens when there is no option
// selected, then don't bother checking for conflicts because the data are obviously
// meaningless. An example is the rooms[] field.
let requiredSelectsComplete = true;
form.find('select:required').not(':disabled').each(function() {
if ($(this).prop('multiple') && ($(this).val().length === 0))
{
requiredSelectsComplete = false;
return false; // break out of the .each loop
}
});
if (!requiredSelectsComplete)
{
return;
}
<?php
// Load the params object with the values of all the form fields that are not
// disabled and are not submit buttons of one kind or another
?>
var relevantFields = form.find('[name]').not(':disabled, [type="submit"], [type="button"], [type="image"]');
relevantFields.each(function() {
<?php
// Go through each of the fields and if we haven't got the value for a name
// then go and get it. (Remember that arrays can give more than one field
// with the same name
?>
var fieldName = $(this).attr('name');
if (params[fieldName] === undefined)
{
params[fieldName] = getFormValue(relevantFields.filter('[name=' + fieldName.replace('[', '\\[').replace(']', '\\]') + ']'));
}
});
<?php
// For some reason I don't understand, posting an empty array will
// give you a PHP array of ('') at the other end. So to avoid
// that problem, delete the property if the array (really an object) is empty
?>
$.each(params, function(i, val) {
if ((typeof(val) === 'object') && ((val === null) || (val.length === 0)))
{
delete params[i];
}
});
if(args.site)
{
params.site = args.site;
}
checkConflicts.nOutstanding++;
$.post('edit_entry_handler.php', params)
.fail(function() {
$('#checks').hide();
})
.done(function(result) {
if (!result)
{
$('#checks').hide();
}
else {
$('#checks').show();
checkConflicts.nOutstanding--;
var conflictDiv = $('#conflict_check');
var scheduleDetails = $('#schedule_details');
var policyDetails = $('#policy_details');
var titleText, detailsHTML;
if (result.conflicts.length === 0)
{
conflictDiv.attr('class', 'good');
titleText = '<?php echo escape_js(html_entity_decode(get_vocab("no_conflicts"))) ?>';
detailsHTML = titleText;
}
else
{
conflictDiv.attr('class', 'bad');
detailsHTML = "<p>";
titleText = '<?php echo escape_js(html_entity_decode(get_vocab("conflict"))) ?>' + "\n\n";
detailsHTML += titleText + "<\/p>";
var conflictsList = getErrorList(result.conflicts);
detailsHTML += conflictsList.html;
titleText += conflictsList.text;
}
conflictDiv.attr('title', titleText);
scheduleDetails.html(detailsHTML);
<?php
// Display the results of the policy check. Set the class to "good" if there
// are no policy violations at all; to "notice" if there are no errors, but some
// notices (this happens when an admin user makes a booking that an ordinary user
// would not be allowed to); otherwise "bad". Content and styling are supplied by CSS.
?>
var policyDiv = $('#policy_check');
var rulesList;
if (result.violations.errors.length === 0)
{
if (result.violations.notices.length === 0)
{
policyDiv.attr('class', 'good');
titleText = '<?php echo escape_js(html_entity_decode(get_vocab("no_rules_broken"))) ?>';
detailsHTML = titleText;
}
else
{
policyDiv.attr('class', 'notice');
detailsHTML = "<p>";
titleText = '<?php echo escape_js(html_entity_decode(get_vocab("rules_broken_notices"))) ?>' + "\n\n";
detailsHTML += titleText + "<\/p>";
rulesList = getErrorList(result.violations.notices);
detailsHTML += rulesList.html;
titleText += rulesList.text;
}
}
else
{
policyDiv.attr('class', 'bad');
detailsHTML = "<p>";
titleText = '<?php echo escape_js(html_entity_decode(get_vocab("rules_broken"))) ?>' + "\n\n";
detailsHTML += titleText + "<\/p>";
rulesList = getErrorList(result.violations.errors);
detailsHTML += rulesList.html;
titleText += rulesList.text;
}
policyDiv.attr('title', titleText);
policyDetails.html(detailsHTML);
} <?php // if (!result) else ?>
}, 'json');
}, timeout); <?php // setTimeout() ?>
} <?php // function checkConflicts()
// Get the current vocab (in the appropriate language) for periods,
// minutes, hours and days
?>
var vocab = {};
vocab.periods = {singular: '<?php echo get_js_vocab("period_lc") ?>',
plural: '<?php echo get_js_vocab("periods") ?>'};
vocab.minutes = {singular: '<?php echo get_js_vocab("minute_lc") ?>',
plural: '<?php echo get_js_vocab("minutes") ?>'};
vocab.hours = {singular: '<?php echo get_js_vocab("hour_lc") ?>',
plural: '<?php echo get_js_vocab("hours") ?>'};
vocab.days = {singular: '<?php echo get_js_vocab("day") ?>',
plural: '<?php echo get_js_vocab("days") ?>'};
<?php
// Removes any trailing zeroes after the decimal point.
?>
function durFormat(r)
{
var lastChar;
r = r.toFixed(2);
r = parseFloat(r);
r = r.toLocaleString(args.langPrefs);
if ((r.indexOf('.') >= 0) || (r.indexOf(',') >= 0))
{
while (r.slice(-1) === '0')
{
r = r.slice(0, -1);
}
lastChar = r.slice(-1);
if ((lastChar === '.') || (lastChar === ','))
{
r = r.slice(0, -1);
}
}
return r;
}
<?php
// Returns a string giving the duration having chosen sensible units,
// translated into the user's language, and formatted the number, taking
// into account the user's locale. Note that when using periods one
// is added to the duration because the model is slightly different
// - from the start time (in seconds since the start of the day
// - to the end time (in seconds since the start of the day)
// - days the number of days difference
?>
function getDuration(from, to, days)
{
var duration, durUnits;
var text = '';
var currentArea = $('#area').data('current');
var enablePeriods = areaConfig('enable_periods');
var durDays;
var minutesPerDay = <?php echo MINUTES_PER_DAY ?>;
durUnits = (enablePeriods) ? '<?php echo "periods" ?>' : '<?php echo "minutes" ?>';
duration = Math.floor((to - from) / 60);
if (enablePeriods)
{
duration++; <?php // a period is a period rather than a point ?>
}
<?php
// Adjust the days and duration so that 0 <= duration < minutesPerDay. If we're using
// periods then if necessary add/subtract multiples of the number of periods in a day
?>
durDays = Math.floor(duration/minutesPerDay);
if (durDays !== 0)
{
days += durDays;
duration -= durDays * ((enablePeriods) ? $('#start_seconds' + currentArea).find('option').length : minutesPerDay);
}
if (!enablePeriods && (duration >= 60))
{
durUnits = "hours";
duration = durFormat(duration/60);
}
<?php
// As durFormat returns a string, duration can now be either
// a number or a string, so convert it to a string so that we
// know what we are dealing with
?>
duration = duration.toString();
if (days !== 0)
{
text += days + ' ';
text += (days === 1) ? vocab.days.singular : vocab.days.plural;
if (duration !== '0')
{
text += ', ';
}
}
if (duration !== '0')
{
text += duration + ' ';
text += (duration === '1') ? vocab[durUnits].singular : vocab[durUnits].plural;
}
return text;
}
<?php
// Returns the number of days between the start and end dates
?>
function getDateDifference()
{
var diff,
secondsPerDay = <?php echo SECONDS_PER_DAY ?>,
start = $('#start_date').val().split('-'),
startDate = new Date(parseInt(start[0], 10),
parseInt(start[1], 10) - 1,
parseInt(start[2], 10),
12),
endDate = $('#end_date'),
end;
if (endDate.length === 0)
{
<?php
// No end date selector, so assume the end date is
// the same as the start date
?>
diff = 0;
}
else
{
end = endDate.val().split('-');
endDate = new Date(parseInt(end[0], 10),
parseInt(end[1], 10) - 1,
parseInt(end[2], 10),
12);
diff = (endDate - startDate)/(secondsPerDay * 1000);
diff = Math.round(diff);
}
return diff;
}
<?php
// Make two jQuery objects the same width.
?>
function adjustWidth(a, b)
{
<?php
// Note that we set the widths of both objects, even though it would seem
// that just setting the width of the smaller should be sufficient.
// But if you don't set both of them then you end up with a few
// pixels difference. In other words doing a get and then a set
// doesn't leave you where you started - not quite sure why.
// The + 2 is a fudge factor to make sure that the option text in select
// elements isn't truncated - not quite sure why it is necessary.
// The width: auto is necessary to get the elements to resize themselves
// according to their new contents.
?>
a.css({width: "auto"});
b.css({width: "auto"});
var aWidth = a.width();
var bWidth = b.width();
var maxWidth = Math.max(aWidth, bWidth) + 2;
a.width(maxWidth);
b.width(maxWidth);
}
var reloadSlotSelector = function reloadSlotSelector(select, area) {
select.html($('#' + select.attr('id') + area).html())
.val(select.data('current'));
};
var updateSelectorData = function updateSelectorData(){
var selectors = ['area', 'start_seconds', 'end_seconds'];
var i, select;
for (i=0; i<selectors.length; i++)
{
select = $('#' + selectors[i]);
select.data('previous', select.data('current'));
select.data('current', select.val());
}
};
function adjustSlotSelectors()
{
<?php
// Adjust the start and end time slot select boxes.
// (a) If the start time has changed then adjust the end time so
// that the duration is still the same, provided that the endtime
// does not go past the start of the booking day
// (b) If the end time has changed then adjust the duration.
// (c) Make sure that you can't have an end time before the start time.
// (d) Tidy up the two select boxes so that they are the same width
// (e) if oldArea etc. are set, then we've switched areas and we want
// to have a go at finding a time/period in the new area as close
// as possible to the one that was selected in the old area.
?>
var area = $('#area'),
oldArea = area.data('previous'),
currentArea = area.data('current');
var enablePeriods = areaConfig('enable_periods'),
oldEnablePeriods = areaConfig('enable_periods', oldArea),
defaultDuration = areaConfig('default_duration');
var startSelect = $('#start_seconds'),
endSelect = $('#end_seconds'),
allDay = $('#all_day');
var startKeepDisabled = startSelect.hasClass('keep_disabled'),
endKeepDisabled = endSelect.hasClass('keep_disabled'),
allDayKeepDisabled = allDay.hasClass('keep_disabled');
var oldStartValue = parseInt(startSelect.data('previous'), 10),
oldEndValue = parseInt(endSelect.data('previous'), 10);
var nbsp = '\u00A0',
startValue, endValue, firstValue, lastValue, optionClone;
if (startSelect.length === 0)
{
return;
}
<?php
// If All Day is checked then just set the start and end values to the first
// and last possible options.
?>
if (allDay.is(':checked'))
{
startValue = parseInt(startSelect.find('option').first().val(), 10);
endValue = parseInt(endSelect.find('option').last().val(), 10);
<?php
// If we've come here from another area then we need to make sure that the
// start and end selectors are disabled. (We won't change the old_end and old_start
// values, because there's a chance the existing ones may still work - for example if
// the user flicks from Area A to Area B and then back to Area A, or else if the time/
// period slots in Area B match those in Area.)
?>
if (oldArea !== currentArea)
{
startSelect.prop('disabled', true);
endSelect.prop('disabled', true);
}
}
<?php
// Otherwise what we do depends on whether we've come here as a result
// of the area being changed
?>
else if (oldArea !== currentArea)
{
<?php
// If we've changed areas and the modes are the same, we can try and match times/periods.
// We will try and be conservative and find a start time that includes the previous start time
// and an end time that includes the previous end time. This means that by default the
// booking period will include the old booking period (unless we've hit the start or
// end of day). But it does mean that as you switch between areas the booking period
// tends to get bigger: if you switch fromn Area 1 to Area 2 and then back again it's
// possible that the booking period for Area 1 is longer than it was originally.
?>
if (oldEnablePeriods === enablePeriods)
{
<?php
// Step back through the start options until we find one that is less than or equal to the previous value,
// or else we've got to the first option
?>
startSelect.find('option').reverse().each(function() {
startValue = parseInt($(this).val(), 10);
if (startValue <= oldStartValue)
{
return false;
}
});
<?php
// And step forward through the end options until we find one that is greater than
// or equal to the previous value, or else we've got to the last option
?>
endSelect.find('option').each(function() {
endValue = parseInt($(this).val(), 10);
if (endValue >= oldEndValue)
{
return false;
}
});
}
<?php
// The modes are different, so it doesn't make any sense to match up old and new
// times/periods. The best we can do is choose some sensible defaults, which
// is to set the start to the first possible start, and the end to the start + the
// default duration (or the last possible end value if that is less)
?>
else
{
startValue = parseInt(startSelect.find('option').first().val(), 10);
if (enablePeriods)
{
endValue = startValue;
}
else
{
endValue = startValue + defaultDuration;
}
}
}
<?php
// We haven't changed areas. In this case get the currently selected start and
// end values
?>
else
{
startValue = parseInt(startSelect.val(), 10);
endValue = parseInt(endSelect.val(), 10);
<?php
// If the start value has changed then we adjust the end value
// to keep the duration the same. (If the end value has changed
// then the duration will be changed when we recalculate durations below)
?>
if (startValue !== oldStartValue)
{
endValue = endValue + (startValue - oldStartValue);
}
}
var dateDifference = getDateDifference();
<?php
// If All Day isn't checked then we need to work out whether the start
// and end dates are valid. If the end date is before the start date
// then we disable all the time selectors (start, end and All Day) until
// the dates are fixed.
?>
if (!allDay.is(':checked'))
{
var newState = (dateDifference < 0);
if (newState || startKeepDisabled)
{
startSelect.prop('disabled', true);
}
else
{
startSelect.prop('disabled', false);
}
if (newState || endKeepDisabled)
{
endSelect.prop('disabled', true);
}
else
{
endSelect.prop('disabled', false);
}
if (newState || allDayKeepDisabled)
{
allDay.prop('disabled', true);
}
else
{
allDay.prop('disabled', false);
}
}
<?php // Destroy and rebuild the start select ?>
startSelect.html($('#start_seconds' + currentArea).html());
startSelect.val(startValue);
startSelect.data('current', startValue);
<?php // Destroy and rebuild the end select ?>
endSelect.empty();
$('#end_time_error').text(''); <?php // Clear the error message ?>
$('#end_seconds' + currentArea).find('option').each(function(i) {
var thisValue = parseInt($(this).val(), 10),
nPeriods = areaConfig('n_periods'),
maxDurationEnabled = areaConfig('max_duration_enabled'),
maxDurationSecs = areaConfig('max_duration_secs'),
maxDurationPeriods = areaConfig('max_duration_periods'),
maxDurationQty = areaConfig('max_duration_qty'),
maxDurationUnits = areaConfig('max_duration_units'),
secondsPerDay = <?php echo SECONDS_PER_DAY ?>,
duration,
maxDuration;
<?php
// Limit the end slots to the maximum duration if that is enabled, if the
// user is not a booking admin
?>
if (!isBookAdmin)
{
if (maxDurationEnabled)
{
<?php
// Calculate the duration in periods or seconds
?>
duration = thisValue - startValue;
if (enablePeriods)
{
duration = duration/60 + 1; <?php // because of the way periods work ?>
duration += dateDifference * nPeriods;
}
else
{
duration += dateDifference * secondsPerDay;
}
maxDuration = (enablePeriods) ? maxDurationPeriods : maxDurationSecs;
if (duration > maxDuration)
{
if (i === 0)
{
endSelect.append($(this).val(thisValue).text(nbsp));
var errorMessage = '<?php echo get_js_vocab("max_booking_duration") ?>' + nbsp;
if (enablePeriods)
{
errorMessage += maxDurationPeriods + nbsp;
errorMessage += (maxDurationPeriods > 1) ? vocab.periods.plural : vocab.periods.singular;
}
else
{
errorMessage += maxDurationQty + nbsp + maxDurationUnits;
}
$('#end_time_error').text(errorMessage);
}
else
{
return false;
}
}
}
}
if ((thisValue > startValue) ||
((thisValue === startValue) && enablePeriods) ||
(dateDifference !== 0))
{
optionClone = $(this).clone();
if (dateDifference < 0)
{
optionClone.text('<?php echo get_js_vocab("start_after_end")?>');
}
else
{
optionClone.text($(this).text() + nbsp + nbsp +
'(' + getDuration(startValue, thisValue, dateDifference) +
')');
}
endSelect.append(optionClone);
}
});
firstValue = parseInt(endSelect.find('option').first().val(), 10);
lastValue = parseInt(endSelect.find('option').last().val(), 10);
if (isNaN(endValue)) <?php // Is this possible? ?>
{
endValue = lastValue;
}
else
{
<?php
// We constrain the end value to stay on the same day, but it might be
// better to move the end date selector back one day if the new end value
// would be before the beginning of the day, and similarly forward one day
// if it would be after the end of the day. This is what some other calendar
// systems, eg Outlook, do, but it gets a little more complicated when the
// booking day is less than 24 hours, as it is by default in MRBS.
?>
endValue = Math.max(endValue, firstValue);
endValue = Math.min(endValue, lastValue);
}
endSelect.val(endValue);
endSelect.data('current', endValue);
adjustWidth(startSelect, endSelect);
} <?php // function adjustSlotSelectors() ?>
var editEntryVisChanged = function editEntryVisChanged() {
<?php
// Clear the conflict timer and then restart it. We want
// a check to be performed immediately the page becomes
// visible again.
?>
conflictTimer(false);
conflictTimer(true);
};
function populateFromSessionStorage(form)
{
var storedData = sessionStorage.getItem('form_data');
if (storedData)
{
var form_data = JSON.parse(storedData);
<?php
// Before we populate the form we have to set the area select to the correct
// area and then change selects that depend on it, eg the room selects.
?>
$.each(form_data, function (index, field)
{
if (field.name === 'area')
{
$('#area').val(field.value).trigger('change');
return false; // We've found the area field so we can stop.
}
});
<?php // Now iterate through the data again and populate the form ?>
var selects = {};
$.each(form_data, function (index, field)
{
<?php // Don't change the CSRF token - the form will have its own one. ?>
if (field.name === 'csrf_token')
{
return;
}
var el = $('[name="' + field.name + '"]'),
tagName = el.prop('tagName'),
type;
<?php // Some of the variables, eg 'top', won't have a corresponding field ?>
if (tagName === undefined)
{
return;
}
<?php
// If it's a select element then these can be multi-valued. If we just do
// el.val() for each one it will change the value each time, rather than adding
// another one. So instead we need to assemble an array of values and do a single
// el.val() at the end.
?>
if (tagName.toLowerCase() === 'select')
{
if (!selects[field.name])
{
selects[field.name] = []
}
selects[field.name].push(field.value);
}
<?php // Otherwise we can just process them as they come ?>
else
{
type = el.attr('type');
switch (type)
{
case 'checkbox':
<?php // If the name ends in '[]' it's an array and needs to be handled differently ?>
if (field.name.match(/\[]$/))
{
el.filter('[value="' + field.value + '"]').attr('checked', 'checked');
}
else
{
el.attr('checked', 'checked');
}
break;
case 'radio':
el.filter('[value="' + field.value + '"]').attr('checked', 'checked');
break;
default:
el.val(field.value);
break;
}
}
});
<?php // Now assign values to the selects ?>
for (var property in selects)
{
$('[name="' + property + '"]').val(selects[property]).change();
}
<?php // Fix up the datalists so that the correct value is displayed ?>
form.find('datalist').each(function() {
<?php
// Datalists in MRBS have the structure
// <input type="text" list="yyy">
// <input type="hidden" name="xxx">
// <datalist id="yyy">
// and we want to copy the value from the hidden input to the visible one
?>
var prev1 = $(this).prev();
var prev2 = prev1.prev();
if ($(this).attr('id') === prev2.attr('list'))
{
prev2.val(prev1.val());
}
else
{
console.warn("MRBS: something has gone wrong - maybe the MRBS datalist structure has changed.")
}
});
<?php
// Fix up the flatpickr inputs. Although the dates in the hidden inputs will have been set to
// the correct values, we need to force the dates in the visible fields to be set, not just
// to the correct value, but also in the correct format.
?>
form.find('.flatpickr-input').each(function() {
document.querySelector('#' + $(this).attr('id'))._flatpickr.setDate($(this).val(), true);
});
}
}
$(document).on('page_ready', function() {
isBookAdmin = args.isBookAdmin;
var form = $('#main'),
areaSelect = $('#area'),
startAndEndDates = $('#start_date, #end_date'),
startSelect,
endSelect,
allDay;
<?php
// If there's only one enabled area in the database there won't be an area
// select input, so we'll have to create a dummy input because the code
// relies on it.
?>
if (areaSelect.length === 0)
{
areaSelect = $('<input id="area" type="hidden" value="' + args.area + '">');
$('#div_rooms').before(areaSelect);
}
$('#div_areas').show();
$('#start_seconds, #end_seconds')
.each(function() {
$(this).data('current', $(this).val());
$(this).data('previous', $(this).val());
})
.on('change', function() {
updateSelectorData();
reloadSlotSelector($(this), $('#area').val());
adjustSlotSelectors();
updateSelectorData();
});
areaSelect
.data('current', areaSelect.val())
.data('previous', areaSelect.val())
.on('change', function() {
var newArea = $(this).val();
updateSelectorData();
<?php // Switch room selects ?>
var roomSelect = $('#rooms');
roomSelect.html($('#rooms' + newArea).html());
<?php // Switch start time select ?>
reloadSlotSelector($('#start_seconds'), newArea);
<?php // Switch all day checkbox ?>
var allDayCheckbox = $('#all_day');
allDayCheckbox.html($('#all_day' + newArea).html());
<?php // Switch end time select ?>
reloadSlotSelector($('#end_seconds'), newArea);
<?php
// For each field which is only mandatory for some areas (i.e. the $is_mandatory_field value is an array),
// check whether the new area is in the array, and if so add the required attribute, otherwise remove it.
?>
for (const [fieldName, value] of Object.entries(mandatoryFields))
{
if (Array.isArray(value))
{
const field = $('[name="' + fieldName + '"]');
if (value.includes(parseInt(newArea)))
{
field.prop('required', true);
}
else
{
field.prop('required', false);
<?php // Clear any existing error messages ?>
field[0].setCustomValidity('');
}
}
}
<?php // Reset the validation messages ?>
validationMessages(newArea);
adjustSlotSelectors();
});
$('input[name="all_day"]').on('click', function() {
onAllDayClick();
});
<?php
// If we've got back here from edit_entry_handler.php then repopulate the form
// with the original data.
?>
if (form.data('back'))
{
populateFromSessionStorage(form);
}
<?php
// (1) Adjust the slot selectors
// (2) Add some Ajax capabilities to the form (if we can) so that when
// a booking parameter is changed MRBS checks to see whether there would
// be any conflicts
?>
adjustSlotSelectors();
<?php
// If this is an All Day booking then check the All Day box and disable the
// start and end time boxes
?>
startSelect = form.find('#start_seconds');
endSelect = form.find('#end_seconds');
allDay = form.find('#all_day');
if (allDay.is(':visible') &&
(allDay.is(':disabled') === false) &&
(startSelect.val() === startSelect.find('option').first().val()) &&
(endSelect.val() === endSelect.find('option').last().val()))
{
allDay.attr('checked', 'checked');
startSelect.prop('disabled', true);
endSelect.prop('disabled', true);
onAllDayClick.oldStart = startSelect.val();
onAllDayClick.oldEnd = endSelect.val();
onAllDayClick.oldStartDatepicker = form.find('#start_date').val();
onAllDayClick.oldEndDatepicker = form.find('#end_date').val();
}
<?php
// Set up the validation messages, but only if the function exists (which it
// won't if we're on the login page)
?>
if (typeof validationMessages === 'function')
{
validationMessages(args.area);
}
<?php
// If anything like a submit button is pressed then add a data flag to the form so
// that the function that checks for a valid booking can see if the change was
// triggered by a Submit button being pressed, and if so, not to send an Ajax request.
?>
form.find('[type="submit"], [type="button"], [type="image"]').on('click', function() {
var trigger = $(this).attr('name');
$(this).closest('form').data('submit', trigger);
});
form.on('submit', function()
{
var result = true;
if ($(this).data('submit') === 'save_button')
{
<?php // Only validate the form if the Save button was pressed ?>
result = validate($(this));
if (!result)
{
<?php // Clear the data flag if the validation failed ?>
$(this).removeData('submit');
}
}
<?php
// If we're OK to submit then store the form data in session storage so that
// we can repopulate the form if there's an error and we need to come back to
// the form from edit_entry_handler.php.
?>
if (result)
{
sessionStorage.setItem('form_data', JSON.stringify($(this).serializeArray()));
}
return result;
});
<?php
// When the start date changes, change the minDate on the repeat end date datepicker to match.
?>
$('#start_date').on('change', function() {
const rep_end_date_selector = '#rep_end_date';
const start_date = $(this).val();
const old_rep_end_date = $(rep_end_date_selector).val();
const fp = document.querySelector(rep_end_date_selector)._flatpickr;
fp.set('minDate', start_date);
// Note that although the flatpickr documentation says this should work on mobiles, it doesn't seem to.
// So we manually set the 'min' attribute for mobiles.
$(rep_end_date_selector).next('.flatpickr-input.flatpickr-mobile').attr('min', start_date);
<?php
// And if the old flatpickr date was less than the new minimum, set the flatpickr to the minimum,
// otherwise it will show a blank field.
?>
if (old_rep_end_date < start_date)
{
fp.setDate(start_date);
}
});
<?php
// Add a change event handler to each of the form fields - except for those that
// are disabled and anything that might be a submit button - so that when they change
// the validity of the booking is re-checked. (This probably causes more checking
// than is really necessary, eg when the brief description is changed, but on the other
// hand it (a) removes the need to know the names of the fields you want and (b) keeps
// the data available for policy checking as complete as possible just in case somebody
// decides to set a policy based on for example the brief description, for some reason).
//
// Use a click event for checkboxes as it seems that in some browsers the event fires
// before the value is changed.
?>
var formFields = form.find('input.date, [name]').not(':disabled, [type="submit"], [type="button"], [type="image"]');
formFields.filter(':checkbox')
.on('click', function() {
checkConflicts();
});
formFields.not(':checkbox')
.on('change', function() {
checkConflicts();
});
<?php
// and a div to hold the dialog box which gives more details. The dialog
// box contains a set of tabs. And because we want the tabs to act as the
// dialog box we add an extra tab where we're going to put the dialog close
// button and then we hide the dialog itself
?>
var tabsHTML =
'<div id="check_tabs">' +
'<ul id="details_tabs">' +
'<li><a href="#schedule_details"><?php echo get_js_vocab('schedule') ?></a></li>' +
'<li><a href="#policy_details"><?php echo get_js_vocab('policy') ?></a></li>' +
'<li id="ui-tab-dialog-close"></li>' +
'</ul>' +
'<div id="schedule_details"></div>' +
'<div id="policy_details"></div>' +
'</div>';
$('<div>').attr('id', 'check_results')
.css('display', 'none')
.html(tabsHTML)
.appendTo(form);
$('#conflict_check, #policy_check').on('click', function manageTabs() {
var tabId,
tabIndex,
checkResults = $('#check_results'),
checkTabs = $('#check_tabs');
<?php
// Work out which tab should be selected
// (Slightly long-winded using a switch, but there may be more tabs in future)
?>
switch ($(this).attr('id'))
{
case 'policy_check':
tabId = 'policy_details';
break;
case 'conflict_check':
default:
tabId = 'schedule_details';
break;
}
tabIndex = $('#details_tabs a[href="#' + tabId + '"]').parent().index();
<?php
// If we've already created the dialog and tabs, then all we have
// to do is re-open the dialog if it has previously been closed and
// select the tab corresponding to the div that was clicked
?>
if (manageTabs.alreadyExists)
{
if (!checkResults.dialog("isOpen"))
{
checkResults.dialog("open");
}
checkTabs.tabs('option', 'active', tabIndex);
return;
}
<?php
// We want to create a set of tabs that appear inside a dialog box,
// with the whole structure being draggable. Thanks to dbroox at
// http://forum.jquery.com/topic/combining-ui-dialog-and-tabs for the solution.
?>
checkTabs.tabs();
checkTabs.tabs('option', 'active', tabIndex);
checkResults.dialog({'width': 400,
'height': 200,
'minWidth': 300,
'minHeight': 150,
'draggable': true});
<?php //steal the close button ?>
var detailsTabs = $('#details_tabs');
detailsTabs.append($('button.ui-dialog-titlebar-close'));
<?php //move the tabs out of the content and make them draggable ?>
$('.ui-dialog').addClass('ui-tabs')
.prepend(detailsTabs)
.draggable('option', 'handle', '#details_tabs');
<?php //switch the titlebar class ?>
$('.ui-dialog-titlebar').remove();
detailsTabs.addClass('ui-dialog-titlebar');
manageTabs.alreadyExists=true;
});
<?php
// Finally, set a timer so that conflicts are periodically checked for,
// in case someone else books that slot before you press Save.
?>
conflictTimer(true);
<?php
// Actions to take when the start and end datepickers are closed
?>
startAndEndDates.on('change', function() {
<?php
// (1) If the end_datepicker isn't visible and we change the start_datepicker,
// then set the end date to be the same as the start date. (This will be
// the case if multi-day bookings are not allowed)
?>
var endDate = $('#end_date');
if ($(this).attr('id') === 'start_date')
{
if (endDate.css('visibility') === 'hidden')
{
endDate.val($(this).val());
}
}
<?php
// (2) If the start date is after the end date, then change the end date to match the
// start date if the start date was changed, or change the start date to match the
// end date if the end date was changed.
?>
if (getDateDifference() < 0)
{
var selector = ($(this).attr('id') === 'start_date') ? '#end_date' : '#start_date';
var fp = document.querySelector(selector)._flatpickr;
fp.setDate($(this).val());
}
<?php
// (3) Go and adjust the start and end time/period select options, because
// they are dependent on the start and end dates
?>
adjustSlotSelectors();
<?php
// (4) If we're doing Ajax checking of the form then we have to check
// for conflicts when the datepicker is closed
?>
checkConflicts();
<?php
// (5) Check to see whether any time slots should be removed from the time
// select on the grounds that they don't exist due to a transition into DST.
?>
checkTimeSlots($(this));
});
startAndEndDates.each(function() {
checkTimeSlots($(this));
});
$('input[name="rep_interval"]').on('change', changeRepIntervalUnits);
$('input[name="rep_type"]').on('change', function() {
changeRepTypeDetails();
changeRepIntervalUnits();
}).trigger('change');
<?php
// Add an event listener to detect a change in the visibility
// state. We can then suspend Ajax checking when the page is
// hidden to save on server, client and network load.
?>
var prefix = visibilityPrefix();
if (document.addEventListener &&
(prefix !== null))
{
document.addEventListener(prefix + "visibilitychange", editEntryVisChanged);
}
form.removeClass('js_hidden');
<?php
// Put the booking name field in focus (but only for new bookings,
// ie when the field is empty: if it's a new booking you have to
// complete that field, but if it's an existing booking you might
// want to edit any field)
?>
var nameInput = form.find('#name');
if (nameInput.length && !(nameInput.prop('disabled') || nameInput.val().length))
{
nameInput.trigger('focus');
}
<?php
// If the allow_registration checkbox is changed then we need to toggle the
// disabled property of all the controls in the registration fieldset. However,
// because some of them are also enabler groups themselves then if allow_registration
// is checked we need to trigger a change event on them so that they set their own
// disabled properties appropriately.
?>
var allowRegistration = $('#allow_registration');
allowRegistration.on('change', function() {
var registration = $('#registration');
var allowRegistrationChecked = $(this).is(':checked');
registration.find('input, select').not($(this)).prop('disabled', !allowRegistrationChecked);
if (allowRegistrationChecked)
{
registration.find('.enabler').trigger('change');
}
})
.trigger('change');
<?php
// Enable the checkboxes which may have been disabled, otherwise their values
// will not be posted.
?>
form.on('submit', function() {
$('#registration').find('input[type="checkbox"]').prop('disabled', false);
});
});