(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 = $('
  1. ').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);