(function(window){
'use strict';
/* eslint no-console:0 */
var ACEDEBUG; // shaddup eslint
var debugTour = '
';
var $debugBar, $debugTour, $errorCount;
// stock details for JSON payload
var time = Date.now(); // for log systems
var humanTime = new Date(time); // for humans to read
var aceVersion = ACE.version();
var os = ACE.detectOS();
var browser = ACE.detectBrowser();
var url = window.location.href;
var hideForSession;
var errors = [];
var reportingJSON = {
'url': url + '',
'timestamp': time + '',
'time': humanTime + '',
'browser': browser.name + '',
'browserVersion': browser.version + '',
'os': os + '',
'aceVersion': aceVersion + '',
'errorCount': '',
'errors': {},
'exclusionCount': ''
};
var fakeID = 0;
var debugBar;
var DEBUG_PATH_EXCLUSIONS = {
'debug-test.html': ['#PathExcludedLabelExample', '#PathExcludedInputExample'],
'EditUserProfile.asp': ['form[name="frmUserProfile"] > fieldset:first-of-type'],
'UpdateMyDetails.asp': ['form[name="frmUpdateMyDetails"] > .ace-form-group-text'],
'UserInvitation_CreateUserProfile.asp': ['form[name="frmUserInvitation_CreateUserProfile"] > fieldset:nth-of-type(2)']
};
var pathstring = window.location.pathname.split('/');
var debugPathExclusions = DEBUG_PATH_EXCLUSIONS[pathstring[pathstring.length - 1]];
debugBar = $('
ACE DEBUG
');
debugBar.append('ACE version: ');
debugBar.append('ACE errors detected: ');
debugBar.append('');
if (debugPathExclusions) {
debugBar.append('ACE exclusions detected: ' + debugPathExclusions.length + '');
}
if (sessionStorage.getItem('aceDebugBarHide')) {
hideForSession = sessionStorage.getItem('aceDebugBarHide');
} else {
sessionStorage.setItem('aceDebugBarHide', 'false');
hideForSession = 'false';
}
function getId(el) {
return $(el).attr('id') || false;
}
function setError(message, el) {
var $el = $(el); // error element
var elHTML = $el.prop('outerHTML').replace(/\n|\r/g, ''); // HTML minus line breaks and carriage returns
var currentMessage = $el.attr('data-ace-debug');
var hidden = false;
fakeID++; // increment first is intentional, because we actually want to start from 1 to match visible list
if(currentMessage) {
message = currentMessage + ' ' + message;
}
if ($('#hidden').find($el).length) {
message += ' Element is inside #hidden.';
hidden = true;
}
if (!$el.is(':visible')) {
message += ' Element was not visible when tested (and will not be highlighted).';
hidden = true;
}
// add to errors for rendering
errors.push({
'message' : message,
'element': el,
'fakeID': fakeID,
'id': getId(el),
'hidden': hidden
});
// add to errors for reporting
reportingJSON.errors['error-' + fakeID] = {
'timestamp': time + '',
'time': humanTime + '',
'url': url + '',
'message' : message + '',
'debugID': fakeID + '',
'element': elHTML + '',
'hidden': hidden + ''
};
// update DOM with debug info
$el.attr('data-ace-debug', message);
$el.attr('data-ace-debug-id', fakeID);
// console
console.log(message, el);
}
ACEDEBUG = {
highlightErrors: function() {
if(hideForSession !== 'true') {
$('body').toggleClass('ace-debug-show-errors');
}
},
startTour: function() {
console.log('startTour has been removed from the debugger.');
},
testAce: function(scope, exclusions) {
var context = scope || $('html'),
exclusionFilter = function () {
var $element, exclusionMatch;
if (!exclusions) {
return true;
}
$element = $(this);
exclusionMatch = exclusions.some(function (selector) {
return $element.is(selector) || $element.closest(selector).length;
});
return !exclusionMatch;
};
// BASE MARKUP CHECKS
$('[id]', scope).each(function() {
var ids;
if (!ACEDEBUG.isTemplateId(this.id)) {
ids = $('[id="' + this.id + '"]');
if(ids.length > 1) {
setError('Element uses duplicate ID #' + this.id + '. ID attributes must be unique in the DOM.', this);
}
}
});
$('img', scope).each(function() {
if ( this.hasAttribute('alt') === false ) {
setError('IMG elements must have an alt attribute. Decorative images may have an alt of an empty string.', this);
}
});
// CORE
if ( $('html').find('meta[name="viewport"]').attr('content') !== 'width=device-width, initial-scale=1') {
setError('Document must have a meta viewport ""', $('html'));
}
// BUTTONS
$('.ace-button-group > *', context).filter(exclusionFilter).each(function() {
if ( !$(this).hasClass('ace-button') ) {
setError('ACE button groups can only contain ACE buttons.', this);
}
});
$('.ace-button', context).filter(exclusionFilter).each(function() {
if ( $(this).find('span').length === 0 ) {
setError('ACE buttons must contain a span.', this);
}
});
// FORMS
$('.ace-form', context).filter(exclusionFilter).each(function() {
var $el = $(this);
$el.children().each(function () {
var $child = $(this);
if ( !$child.hasClass('ace-form-section') && !$child.hasClass('ace-form-group') && !$child.hasClass('ace-form-hidden') ) {
setError('Only form sections,form groups and form hidden can be nested inside a form.', this);
}
});
});
$('.ace-form-section', context).filter(exclusionFilter).each(function() {
var $el = $(this);
$el.children().each(function (index) {
var $child = $(this);
if (index === 0) {
if ( !$child.hasClass('ace-form-legend') ) {
setError('Only form legends can be the first item nested inside a form section.', this);
}
} else {
if ( !$child.hasClass('ace-form-group') ) {
setError('Only form groups (and the form legend) can be nested inside a form section.', this);
}
}
});
});
$('.ace-form-group', context).filter(exclusionFilter).each(function() {
var $el = $(this);
if ( $el.parent('.ace-form-group').length !== 0) {
setError('Form groups must not be nested.', this);
}
if ( $el.hasClass('ace-form-error') && !$el.hasClass('ace-form-group-datepicker') ) {
if ( !$el.find('.ace-form-input.ace-form-error').length > 0 ) {
setError('Error state class must be added to both the form group and the input.', this);
}
}
$el.children().each(function () {
var $child = $(this);
if ( !$child.hasClass('ace-form-item') ) {
setError('Only form items can be nested inside a form group.', this);
}
});
});
$('.ace-form-item-label', context).filter(exclusionFilter).each(function() {
if ( !$(this).hasClass('ace-form-item') ) {
setError('The form item label div must also be a form item.', this);
}
});
$('.ace-form-item-input', context).filter(exclusionFilter).each(function() {
if ( !$(this).hasClass('ace-form-item') ) {
setError('The form item input div must also be a form item.', this);
}
});
$('.ace-form-subitem', context).filter(exclusionFilter).each(function() {
if ( !$(this).parent().hasClass('ace-form-item') ) {
setError('Form subitem must be nested inside a form item.', this);
}
});
$('.ace-form input[type="hidden"]', context).filter(exclusionFilter).each(function() {
var $el = $(this),
$elPrev = $el.prev(),
validMVCCheckboxCase = false,
elVal = $el.val().toLowerCase();
// We check for a checkbox with a matching name attribute that next to each other and exclude the MVC inputs from debug results.
if ($elPrev.length > 0 && $el.attr('name') === $elPrev.attr('name')) {
if ($elPrev.hasClass('ace-form-input-checkbox')) {
if(elVal === 'true' || elVal === 'false') {
validMVCCheckboxCase = true;
}
}
}
if (!validMVCCheckboxCase && !$(this).hasClass('ace-form-input-hidden') ) {
setError('All hidden inputs require the .ace-form-input-hidden class.', this);
}
});
$('.ace-form label', context).filter(exclusionFilter).each(function() {
var $el = $(this);
if (
!$el.hasClass('ace-form-label')
&& $el.closest('.ace-toggle').length === 0
) {
setError('All form labels require the .ace-form-label class.', this);
}
if ($el.hasClass('ace-form-label') && $el.text().trim().length === 0 ) {
setError('All form labels require human text. See http://ace.ansarada.com/forms-code.html for more information.', this);
}
});
$('.ace-form legend', context).filter(exclusionFilter).each(function() {
var $el = $(this);
if ($el.hasClass('ace-form-item-label') || $el.hasClass('ace-form-legend')) {
if ($el.text().trim().length === 0 ) {
setError('All form legend require human text. See http://ace.ansarada.com/forms-code.html for more information.', this);
}
}
});
$('.ace-form input:not([type="hidden"])', context).filter(exclusionFilter).each(function() {
var $el = $(this);
if (
$el.closest('.ace-toggle').length !== 0
|| $el.closest('.ace-datepicker').length !== 0
) {
return;
}
if ( !$el.hasClass('ace-form-input') ) {
setError('All form inputs require the .ace-form-input class.', this);
}
if ( $el.attr('type') === 'text' && !$el.hasClass('ace-form-input-text') ) {
setError('All text inputs require the .ace-form-input-text class.', this);
}
if ( $el.attr('type') === 'checkbox' && !$el.hasClass('ace-form-input-checkbox') ) {
setError('All checkboxes inputs require the .ace-form-input-checkbox class.', this);
}
if ( $el.attr('type') === 'radio' && !$el.hasClass('ace-form-input-radio') ) {
setError('All radio inputs require the .ace-form-input-radio class.', this);
}
});
$('.ace-form textarea', context).filter(exclusionFilter).each(function() {
if ( !$(this).hasClass('ace-form-input-textarea') ) {
setError('All textarea inputs require the .ace-form-input-textarea class.', this);
}
});
$('.ace-form-input:not([type="hidden"])', context).filter(exclusionFilter).each(function() {
if(!ACEDEBUG.isTemplateId($(this).attr('id'))) {
if (typeof $(this).attr('id') === 'undefined') {
setError('All form inputs need an ID attribute.', this);
// Don't require the ACE class name in the label check as it effectively
// doubles the error count if the class name check fails.
} else if ( $('label[for=' + $(this).attr('id') + ']').length === 0 ) {
setError('Input ID attribute does not match any label FOR attribute.', this);
}
}
if ( $(this).hasClass('ace-form-input-prefixedurl') ) {
if ( $(this).attr('type') === 'url' ) {
setError('Prefixed URLs should be set to type text, not type URL.', this);
}
}
});
$('.ace-form-label', context).filter(exclusionFilter).each(function() {
if ( $(this).attr('for') === undefined ) {
setError('All form labels need a FOR attribute.', this);
} else if (!ACEDEBUG.isTemplateId($(this).attr('for'))) {
// Don't require the ACE class name in this check as it effectively
// doubles the error count if the class name check fails.
if ($('#' + $(this).attr('for') ).length === 0) {
setError('Form label FOR attribute does not match any input ID attribute.', this);
}
}
});
// LAYOUT
$('.ace-item', context).filter(exclusionFilter).each(function() {
if ( $(this).parent('.ace-group').length === 0) {
setError('All ace-item elements must be contained in an ace-group.', this);
}
});
// ICONS
$('.ace-icon', context).filter(exclusionFilter).each(function() {
var $el = $(this);
if ( !$el.text().trim().length && !$el.parent().text().trim().length ) {
setError('Icon and icon parent element both missing accessible text.', this);
}
if ( $el.attr('class').trim() === 'ace-icon' ) {
setError('Icon needs additional class to set the glyph.', this);
}
});
// TABS
$('.ace-tabs-nav li', context).filter(exclusionFilter).each(function() {
var $el = $(this);
if ( !$el.attr('aria-controls') ) {
setError('Tab nav LIs must have an aria-controls attribute', this);
} else {
if ( $('#' + $el.attr('aria-controls')).length === 0 ) {
setError('aria-controls must match a tab panel ID - #' + $el.attr('aria-controls') + ' not found.', this);
}
}
});
// TABLES
$('.ace-table', context).filter(exclusionFilter).each(function() {
var $el = $(this);
if ( !$el.parent().hasClass('ace-table-container') ) {
setError('ACE tables must have an ace-table-container.', this);
}
if ( $el.find('th').length === 0 ) {
setError('Tables must contain TH cells', this);
}
if ( $el.find('tbody').length === 0 ) {
setError('Tables must contain TBODY', this);
}
if ( $el.find('table table').length > 0 ) {
setError('Tables should not be nested; and must not be nested more than two layers.', this);
}
});
},
/**
* Checks a element id to see if it common string template pattern.
* e.g. [foo], [[foo]], {foo}, {{foo}}
* @param {String} elementId - the id of the element
* @return {Boolean} Whether or not the id is template
*/
isTemplateId: function(elementId) {
var isTemplateId = false,
isTemplateIdRegEx = /[\[{].*[\]}]/g;
if (typeof elementId !== 'undefined') {
isTemplateId = isTemplateIdRegEx.test(elementId);
}
return isTemplateId;
},
init: function() {
if(hideForSession !== 'true') {
//Added this so the debug bar does not break the css footer.
if ($('#page')) {
$('#page').prepend(debugBar);
} else{
$('body').prepend(debugBar);
}
}
$('#hidden').append(debugTour);
$debugBar = $('#ace-debug-bar');
$debugTour = $('#ace-debug-tour');
$errorCount = $('#ace-debug-error-count');
$('#ace-debug-version').text(aceVersion);
$errorCount.text(errors.length);
$('#ace-debug-highlight').on('click keypress', function() {
ACEDEBUG.highlightErrors();
});
$('#ace-debug-bar-hide-button').on('click keypress', function() {
$debugBar.hide();
});
$('#ace-debug-bar-sessionhide-button').on('click keypress', function() {
sessionStorage.setItem('aceDebugBarHide', 'true');
$debugBar.hide();
});
$('#ace-debug-show-list').on('click keypress', function() {
if( $('#ace-debug-bar #ace-debug-tour').length ) {
$('#ace-debug-bar #ace-debug-tour').remove();
} else {
$('#ace-debug-tour').clone().appendTo('#ace-debug-bar');
$('#ace-debug-tour li a').on('click', function(e){
var $targetEl = $('[data-ace-debug-id="' + $(this).attr('data-ace-debug-href') + '"]');
$('[data-ace-debug-single]').removeAttr('data-ace-debug-single');
$targetEl.attr('data-ace-debug-single', 'true');
$('html, body').animate({
scrollTop: $targetEl.offset().top - 20
}, 100);
e.preventDefault();
return false;
});
}
});
console.log('ACE Debug initialised');
},
debug: function(context, pathExclusions) {
pathExclusions = pathExclusions || debugPathExclusions;
ACEDEBUG.testAce(context, pathExclusions);
$errorCount.text(errors.length);
if (errors.length !== 0) {
$debugBar.addClass('ace-debug-error');
$debugTour.empty();
$.each(errors, function(i) {
var $debugItem = $('').text(errors[i].message);
var $debugJump = $(' Go to element').attr('data-ace-debug-href', errors[i].fakeID);
if (errors[i].hidden === true) {
$debugItem.append(' Inspect the rendered DOM and look for data-ace-debug-id="' + errors[i].fakeID + '"');
} else {
$debugItem.append($debugJump);
}
$debugTour.append($debugItem);
});
} else {
$debugBar.removeClass('ace-debug-error');
}
console.log('ACE Debug complete. ' + errors.length + ' errors found.');
reportingJSON.errorCount = errors.length + '';
if (pathExclusions) {
reportingJSON.exclusionCount = pathExclusions.length + '';
console.log(pathExclusions.length + ' exclusions found.');
} else {
reportingJSON.exclusionCount = '0';
}
return JSON.stringify(reportingJSON);
}
};
window.ACEDEBUG = ACEDEBUG;
})(window);