MDL-44226 editor_atto: refactored the accessibility checker to make it usable by users who don't know html

This commit is contained in:
Andrew Davis
2014-03-26 10:01:45 +08:00
committed by Damyon Wiese
parent 117f9f04dc
commit a279604330
5 changed files with 155 additions and 43 deletions
@@ -0,0 +1,4 @@
.accessibilitywarnings img {
max-width: 32px;
max-height: 32px;
}
@@ -36,6 +36,15 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
*/
dialogue : null,
/**
* Array of nodes that have an accessibility problem
*
* @property displayedwarnings
* @type Array
* @default null
*/
displayedwarnings: [],
/**
* Display the ui dialogue.
*
@@ -61,11 +70,26 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
dialogue.render();
} else {
dialogue = M.atto_accessibilitychecker.dialogue;
// Clear the array of previously displayed warnings.
M.atto_accessibilitychecker.displayedwarnings = [];
}
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
dialogue.centerDialogue();
// Add ability to select problem areas in the editor.
Y.all('.accessibilitywarnings li').on('click', function(e) {
e.preventDefault();
var index = e.target.getAttribute("data-index");
var node = M.atto_accessibilitychecker.displayedwarnings[index];
M.atto_accessibilitychecker.dialogue.hide();
if (node) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(node));
}
});
dialogue.show();
M.atto_accessibilitychecker.dialogue = dialogue;
},
@@ -88,16 +112,28 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
* @param Y.Node list - node to append the html to.
* @param String description - description of this failure.
* @param Y.Node[] nodes - list of failing nodes.
* @param boolean imagewarnings - true if the warnings are related to images, false if text.
*/
add_warnings : function(list, description, nodes) {
var warning, fails, i;
add_warnings : function(list, description, nodes, imagewarnings) {
var warning, fails, i, key, src, textfield;
if (nodes.length > 0) {
warning = Y.Node.create('<p>' + description + '</p>');
fails = Y.Node.create('<ol></ol>');
fails = Y.Node.create('<ol class="accessibilitywarnings"></ol>');
i = 0;
for (i = 0; i < nodes.length; i++) {
fails.append(Y.Node.create('<li>' + Y.Escape.html(nodes[i].get('outerHTML')) + '</li>'));
if (imagewarnings) {
key = 'image_'+i;
src = nodes[i].getAttribute('src');
fails.append(Y.Node.create('<li><a data-index="'+key+'" href="#"><img data-index="'+key+'" src="' + src + '" /> '+src+'</a></li>'));
} else {
key = 'text_'+i;
textfield = ('innerText' in nodes[i])? 'innerText' : 'textContent';
fails.append(Y.Node.create('<li><a href="#" data-index="'+key+'">' + nodes[i].get(textfield) + '</a></li>'));
}
M.atto_accessibilitychecker.displayedwarnings[key] = nodes[i];
}
warning.append(fails);
@@ -151,8 +187,7 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
var editable = M.editor_atto.get_editable_node(elementid);
// Missing alternatives.
var missingalt = [], dodgycontrast = [];
var problemnodes = [];
// Images with no alt text or dodgy alt text.
var alt;
@@ -160,14 +195,15 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
missingalt.push(img);
problemnodes.push(img);
}
}
}, this);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), missingalt);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), problemnodes, true);
// Contrast ratios.
problemnodes = [];
var foreground, background, lum1, lum2, ratio;
editable.all('*').each(function (node) {
// Check for non-empty text.
@@ -193,26 +229,26 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < dodgycontrast.length; i++) {
if (node.ancestors('*').indexOf(dodgycontrast[i]) !== -1) {
for (i = 0; i < problemnodes.length; i++) {
if (node.ancestors('*').indexOf(problemnodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (dodgycontrast[i].ancestors('*').indexOf(node) !== -1) {
} else if (problemnodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
dodgycontrast[i] = node;
problemnodes[i] = node;
found = true;
break;
}
}
if (!found) {
dodgycontrast.push(node);
problemnodes.push(node);
}
}
}
}, this);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), dodgycontrast);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), problemnodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', 'atto_accessibilitychecker') + '</p>');
@@ -1 +1 @@
YUI.add("moodle-atto_accessibilitychecker-button",function(e,t){M.atto_accessibilitychecker=M.atto_accessibilitychecker||{dialogue:null,display_ui:function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t);var n;M.atto_accessibilitychecker.dialogue?n=M.atto_accessibilitychecker.dialogue:(n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0,width:"800px"}),n.set("headerContent",M.util.get_string("pluginname","atto_accessibilitychecker")),n.render()),n.set("bodyContent",M.atto_accessibilitychecker.get_report(t)),n.centerDialogue(),n.show(),M.atto_accessibilitychecker.dialogue=n},init:function(e){var t=M.util.image_url("e/visual_blocks","core");M.editor_atto.add_toolbar_button(e.elementid,"accessibilitychecker",t,e.group,this.display_ui)},add_warnings:function(t,n,r){var i,s,o;if(r.length>0){i=e.Node.create("<p>"+n+"</p>"),s=e.Node.create("<ol></ol>"),o=0;for(o=0;o<r.length;o++)s.append(e.Node.create("<li>"+e.Escape.html(r[o].get("outerHTML"))+"</li>"));i.append(s),t.append(i)}},get_luminance_from_css_color:function(t){var n;t==="transparent"&&(t="#ffffff"),n=e.Color.toArray(e.Color.toRGB(t));var r=function(e){return e=parseInt(e,10)/255,e<=.03928?e/=12.92:e=Math.pow((e+.055)/1.055,2.4),e},i=r(n[0]),s=r(n[1]),o=r(n[2]);return.2126*i+.7152*s+.0722*o},list_warnings:function(t){var n=e.Node.create("<div></div>"),r=M.editor_atto.get_editable_node(t),i=[],s=[],o;r.all("img").each(function(e){o=e.getAttribute("alt"),(typeof o=="undefined"||o==="")&&e.getAttribute("role")!=="presentation"&&i.push(e)},this),this.add_warnings(n,M.util.get_string("imagesmissingalt","atto_accessibilitychecker"),i);var u,a,f,l,c;return r.all("*").each(function(t){if(e.Lang.trim(t.get("text"))!==""){u=t.getComputedStyle("color"),a=t.getComputedStyle("backgroundColor"),f=this.get_luminance_from_css_color(u),l=this.get_luminance_from_css_color(a),f>l?c=(f+.05)/(l+.05):c=(l+.05)/(f+.05);if(c<=4.5){var n=0,r=!1;for(n=0;n<s.length;n++){if(t.ancestors("*").indexOf(s[n])!==-1){r=!0;break}if(s[n].ancestors("*").indexOf(t)!==-1){s[n]=t,r=!0;break}}r||s.push(t)}}},this),this.add_warnings(n,M.util.get_string("needsmorecontrast","atto_accessibilitychecker"),s),n.hasChildNodes()||n.append("<p>"+M.util.get_string("nowarnings","atto_accessibilitychecker")+"</p>"),n},get_report:function(t){var n='<div style="word-wrap: break-word;"></div>',r=e.Node.create(n);return r.append(this.list_warnings(t)),r}}},"@VERSION@",{requires:["node","escape","color-base"]});
YUI.add("moodle-atto_accessibilitychecker-button",function(e,t){M.atto_accessibilitychecker=M.atto_accessibilitychecker||{dialogue:null,displayedwarnings:[],display_ui:function(t,n){t.preventDefault(),M.editor_atto.is_active(n)||M.editor_atto.focus(n);var r;M.atto_accessibilitychecker.dialogue?(r=M.atto_accessibilitychecker.dialogue,M.atto_accessibilitychecker.displayedwarnings=[]):(r=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0,width:"800px"}),r.set("headerContent",M.util.get_string("pluginname","atto_accessibilitychecker")),r.render()),r.set("bodyContent",M.atto_accessibilitychecker.get_report(n)),r.centerDialogue(),e.all(".accessibilitywarnings li").on("click",function(e){e.preventDefault();var t=e.target.getAttribute("data-index"),n=M.atto_accessibilitychecker.displayedwarnings[t];M.atto_accessibilitychecker.dialogue.hide(),n&&M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(n))}),r.show(),M.atto_accessibilitychecker.dialogue=r},init:function(e){var t=M.util.image_url("e/visual_blocks","core");M.editor_atto.add_toolbar_button(e.elementid,"accessibilitychecker",t,e.group,this.display_ui)},add_warnings:function(t,n,r,i){var s,o,u,a,f,l;if(r.length>0){s=e.Node.create("<p>"+n+"</p>"),o=e.Node.create('<ol class="accessibilitywarnings"></ol>'),u=0;for(u=0;u<r.length;u++)i?(a="image_"+u,f=r[u].getAttribute("src"),o.append(e.Node.create('<li><a data-index="'+a+'" href="#"><img data-index="'+a+'" src="'+f+'" /> '+f+"</a></li>"))):(a="text_"+u,l="innerText"in r[u]?"innerText":"textContent",o.append(e.Node.create('<li><a href="#" data-index="'+a+'">'+r[u].get(l)+"</a></li>"))),M.atto_accessibilitychecker.displayedwarnings[a]=r[u];s.append(o),t.append(s)}},get_luminance_from_css_color:function(t){var n;t==="transparent"&&(t="#ffffff"),n=e.Color.toArray(e.Color.toRGB(t));var r=function(e){return e=parseInt(e,10)/255,e<=.03928?e/=12.92:e=Math.pow((e+.055)/1.055,2.4),e},i=r(n[0]),s=r(n[1]),o=r(n[2]);return.2126*i+.7152*s+.0722*o},list_warnings:function(t){var n=e.Node.create("<div></div>"),r=M.editor_atto.get_editable_node(t),i=[],s;r.all("img").each(function(e){s=e.getAttribute("alt"),(typeof s=="undefined"||s==="")&&e.getAttribute("role")!=="presentation"&&i.push(e)},this),this.add_warnings(n,M.util.get_string("imagesmissingalt","atto_accessibilitychecker"),i,!0),i=[];var o,u,a,f,l;return r.all("*").each(function(t){if(e.Lang.trim(t.get("text"))!==""){o=t.getComputedStyle("color"),u=t.getComputedStyle("backgroundColor"),a=this.get_luminance_from_css_color(o),f=this.get_luminance_from_css_color(u),a>f?l=(a+.05)/(f+.05):l=(f+.05)/(a+.05);if(l<=4.5){var n=0,r=!1;for(n=0;n<i.length;n++){if(t.ancestors("*").indexOf(i[n])!==-1){r=!0;break}if(i[n].ancestors("*").indexOf(t)!==-1){i[n]=t,r=!0;break}}r||i.push(t)}}},this),this.add_warnings(n,M.util.get_string("needsmorecontrast","atto_accessibilitychecker"),i,!1),n.hasChildNodes()||n.append("<p>"+M.util.get_string("nowarnings","atto_accessibilitychecker")+"</p>"),n},get_report:function(t){var n='<div style="word-wrap: break-word;"></div>',r=e.Node.create(n);return r.append(this.list_warnings(t)),r}}},"@VERSION@",{requires:["node","escape","color-base"]});
@@ -36,6 +36,15 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
*/
dialogue : null,
/**
* Array of nodes that have an accessibility problem
*
* @property displayedwarnings
* @type Array
* @default null
*/
displayedwarnings: [],
/**
* Display the ui dialogue.
*
@@ -61,11 +70,26 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
dialogue.render();
} else {
dialogue = M.atto_accessibilitychecker.dialogue;
// Clear the array of previously displayed warnings.
M.atto_accessibilitychecker.displayedwarnings = [];
}
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
dialogue.centerDialogue();
// Add ability to select problem areas in the editor.
Y.all('.accessibilitywarnings li').on('click', function(e) {
e.preventDefault();
var index = e.target.getAttribute("data-index");
var node = M.atto_accessibilitychecker.displayedwarnings[index];
M.atto_accessibilitychecker.dialogue.hide();
if (node) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(node));
}
});
dialogue.show();
M.atto_accessibilitychecker.dialogue = dialogue;
},
@@ -88,16 +112,28 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
* @param Y.Node list - node to append the html to.
* @param String description - description of this failure.
* @param Y.Node[] nodes - list of failing nodes.
* @param boolean imagewarnings - true if the warnings are related to images, false if text.
*/
add_warnings : function(list, description, nodes) {
var warning, fails, i;
add_warnings : function(list, description, nodes, imagewarnings) {
var warning, fails, i, key, src, textfield;
if (nodes.length > 0) {
warning = Y.Node.create('<p>' + description + '</p>');
fails = Y.Node.create('<ol></ol>');
fails = Y.Node.create('<ol class="accessibilitywarnings"></ol>');
i = 0;
for (i = 0; i < nodes.length; i++) {
fails.append(Y.Node.create('<li>' + Y.Escape.html(nodes[i].get('outerHTML')) + '</li>'));
if (imagewarnings) {
key = 'image_'+i;
src = nodes[i].getAttribute('src');
fails.append(Y.Node.create('<li><a data-index="'+key+'" href="#"><img data-index="'+key+'" src="' + src + '" /> '+src+'</a></li>'));
} else {
key = 'text_'+i;
textfield = ('innerText' in nodes[i])? 'innerText' : 'textContent';
fails.append(Y.Node.create('<li><a href="#" data-index="'+key+'">' + nodes[i].get(textfield) + '</a></li>'));
}
M.atto_accessibilitychecker.displayedwarnings[key] = nodes[i];
}
warning.append(fails);
@@ -151,8 +187,7 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
var editable = M.editor_atto.get_editable_node(elementid);
// Missing alternatives.
var missingalt = [], dodgycontrast = [];
var problemnodes = [];
// Images with no alt text or dodgy alt text.
var alt;
@@ -160,14 +195,15 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
missingalt.push(img);
problemnodes.push(img);
}
}
}, this);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), missingalt);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), problemnodes, true);
// Contrast ratios.
problemnodes = [];
var foreground, background, lum1, lum2, ratio;
editable.all('*').each(function (node) {
// Check for non-empty text.
@@ -188,26 +224,26 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < dodgycontrast.length; i++) {
if (node.ancestors('*').indexOf(dodgycontrast[i]) !== -1) {
for (i = 0; i < problemnodes.length; i++) {
if (node.ancestors('*').indexOf(problemnodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (dodgycontrast[i].ancestors('*').indexOf(node) !== -1) {
} else if (problemnodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
dodgycontrast[i] = node;
problemnodes[i] = node;
found = true;
break;
}
}
if (!found) {
dodgycontrast.push(node);
problemnodes.push(node);
}
}
}
}, this);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), dodgycontrast);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), problemnodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', 'atto_accessibilitychecker') + '</p>');
@@ -34,6 +34,15 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
*/
dialogue : null,
/**
* Array of nodes that have an accessibility problem
*
* @property displayedwarnings
* @type Array
* @default null
*/
displayedwarnings: [],
/**
* Display the ui dialogue.
*
@@ -59,11 +68,26 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
dialogue.render();
} else {
dialogue = M.atto_accessibilitychecker.dialogue;
// Clear the array of previously displayed warnings.
M.atto_accessibilitychecker.displayedwarnings = [];
}
dialogue.set('bodyContent', M.atto_accessibilitychecker.get_report(elementid));
dialogue.centerDialogue();
// Add ability to select problem areas in the editor.
Y.all('.accessibilitywarnings li').on('click', function(e) {
e.preventDefault();
var index = e.target.getAttribute("data-index");
var node = M.atto_accessibilitychecker.displayedwarnings[index];
M.atto_accessibilitychecker.dialogue.hide();
if (node) {
M.editor_atto.set_selection(M.editor_atto.get_selection_from_node(node));
}
});
dialogue.show();
M.atto_accessibilitychecker.dialogue = dialogue;
},
@@ -86,16 +110,28 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
* @param Y.Node list - node to append the html to.
* @param String description - description of this failure.
* @param Y.Node[] nodes - list of failing nodes.
* @param boolean imagewarnings - true if the warnings are related to images, false if text.
*/
add_warnings : function(list, description, nodes) {
var warning, fails, i;
add_warnings : function(list, description, nodes, imagewarnings) {
var warning, fails, i, key, src, textfield;
if (nodes.length > 0) {
warning = Y.Node.create('<p>' + description + '</p>');
fails = Y.Node.create('<ol></ol>');
fails = Y.Node.create('<ol class="accessibilitywarnings"></ol>');
i = 0;
for (i = 0; i < nodes.length; i++) {
fails.append(Y.Node.create('<li>' + Y.Escape.html(nodes[i].get('outerHTML')) + '</li>'));
if (imagewarnings) {
key = 'image_'+i;
src = nodes[i].getAttribute('src');
fails.append(Y.Node.create('<li><a data-index="'+key+'" href="#"><img data-index="'+key+'" src="' + src + '" /> '+src+'</a></li>'));
} else {
key = 'text_'+i;
textfield = ('innerText' in nodes[i])? 'innerText' : 'textContent';
fails.append(Y.Node.create('<li><a href="#" data-index="'+key+'">' + nodes[i].get(textfield) + '</a></li>'));
}
M.atto_accessibilitychecker.displayedwarnings[key] = nodes[i];
}
warning.append(fails);
@@ -149,8 +185,7 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
var editable = M.editor_atto.get_editable_node(elementid);
// Missing alternatives.
var missingalt = [], dodgycontrast = [];
var problemnodes = [];
// Images with no alt text or dodgy alt text.
var alt;
@@ -158,14 +193,15 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
alt = img.getAttribute('alt');
if (typeof alt === 'undefined' || alt === '') {
if (img.getAttribute('role') !== 'presentation') {
missingalt.push(img);
problemnodes.push(img);
}
}
}, this);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), missingalt);
this.add_warnings(list, M.util.get_string('imagesmissingalt', 'atto_accessibilitychecker'), problemnodes, true);
// Contrast ratios.
problemnodes = [];
var foreground, background, lum1, lum2, ratio;
editable.all('*').each(function (node) {
// Check for non-empty text.
@@ -191,26 +227,26 @@ M.atto_accessibilitychecker = M.atto_accessibilitychecker || {
// We only want the highest node with dodgy contrast reported.
var i = 0, found = false;
for (i = 0; i < dodgycontrast.length; i++) {
if (node.ancestors('*').indexOf(dodgycontrast[i]) !== -1) {
for (i = 0; i < problemnodes.length; i++) {
if (node.ancestors('*').indexOf(problemnodes[i]) !== -1) {
// Do not add node - it already has a parent in the list.
found = true;
break;
} else if (dodgycontrast[i].ancestors('*').indexOf(node) !== -1) {
} else if (problemnodes[i].ancestors('*').indexOf(node) !== -1) {
// Replace the existing node with this one because it is higher up the DOM.
dodgycontrast[i] = node;
problemnodes[i] = node;
found = true;
break;
}
}
if (!found) {
dodgycontrast.push(node);
problemnodes.push(node);
}
}
}
}, this);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), dodgycontrast);
this.add_warnings(list, M.util.get_string('needsmorecontrast', 'atto_accessibilitychecker'), problemnodes, false);
if (!list.hasChildNodes()) {
list.append('<p>' + M.util.get_string('nowarnings', 'atto_accessibilitychecker') + '</p>');