Opened 14 years ago

Closed 10 years ago

#640 closed enhancement (no action needed)

tables produced by xinha do not have headers and fail to meet WAI guidelines.

Reported by: anonymous Owned by: gogo
Priority: normal Milestone: Version 1.0
Component: Xinha Core Version: trunk
Severity: normal Keywords: tables accessibility
Cc:

Description

The WAI Accessibility guidelines state that tables should use the TH tag to specify a header. Would it be possible to add something to Xinha to ask the user for the table headings or simply take the top row as headings.

Change History (5)

comment:1 Changed 14 years ago by Mousy

I'm rewriting the TableOperations? plugin to handle this atm. I will donate my code once I've finished.

comment:2 Changed 12 years ago by ray

  • Milestone set to Version 1.0

comment:3 Changed 12 years ago by mharrisonline

I had done this a long time ago, so this code is not from the current Xinha. What I'm posting is just as maybe a starting point to help this become part of core Xinha and has two serious limitations (however it does / or did work): It requires seperate insert table buttons on the toolbar for tables with a header row, a table with a header column, or a table with both. Ideally, these should be radio buttons on the insert table popup. The second limitation is that it creates an incompatibility problem with the current table operations plugin, one should be able to right click and change a cell from a TD to a TH, etc.. Also, if you add a column and there is a header row, it will create a void where the new TH cell should be.

// Called when the user clicks the Insert Table button
HTMLArea.prototype._insertTable = function() {
  var sel = this._getSelection();
  var range = this._createRange(sel);
  var editor = this;	// for nested functions
  this._popupDialog(editor.config.URIs["insert_table"], function(param) {
    if (!param) {	// user must have pressed Cancel
      return false;
    }
    var doc = editor._doc;
    // create the table element
    var table = doc.createElement("table");
    // assign the given arguments

    for (var field in param) {
      var value = param[field];
      if (!value) {
        continue;
      }
      switch (field) {


		case "f_summary" : table.summary = value; break;
		  case "f_frame" : table.frame = value; break;
		  case "f_rules" : table.rules = value; break;
          case "f_width"   : table.style.width = value + param["f_unit"]; break;
          case "f_align"   : table.align	 = value; break;
          case "f_border"  : table.border	 = parseInt(value); break;
          case "f_spacing" : table.cellSpacing = parseInt(value); break;
          case "f_padding" : table.cellPadding = parseInt(value); break;
      }
    }
    var cellwidth = 0;
    if (param.f_fixed)
      cellwidth = Math.floor(100 / parseInt(param.f_cols));


    var tbody = doc.createElement("tbody");
    table.appendChild(tbody);
// if there is a header row, i=1, no header row i=0
    for (var i = 0; i < param["f_rows"]; ++i) {
      var tr = doc.createElement("tr");
      tbody.appendChild(tr);

// if there is a header column, i=1, no header column i=0
      for (var j = 0; j < param["f_cols"]; ++j) {
        var td = doc.createElement("td");
        if (cellwidth)
          td.style.width = cellwidth + "%";
        tr.appendChild(td);
        // Browsers like to see something inside the cell (&nbsp;).
       td.appendChild(doc.createTextNode('\u00a0'));
      }
    }
    if (HTMLArea.is_ie) {
      range.pasteHTML(table.outerHTML);
    } else {
      // insert the table
      editor.insertNodeAtSelection(table);
    }
    return true;
  }, null);
};

HTMLArea.prototype._insertTable2 = function() {
  var sel = this._getSelection();
  var range = this._createRange(sel);
  var editor = this;	// for nested functions
  this._popupDialog(editor.config.URIs["insert_table"], function(param) {
    if (!param) {	// user must have pressed Cancel
      return false;
    }
    var doc = editor._doc;
    // create the table element
    var table = doc.createElement("table");
    // assign the given arguments

    for (var field in param) {
      var value = param[field];
      if (!value) {
        continue;
      }
      switch (field) {


		  case "f_summary" : table.summary = value; break;
		  case "f_frame" : table.frame = value; break;
		  case "f_rules" : table.rules = value; break;
          case "f_width"   : table.style.width = value + param["f_unit"]; break;
          case "f_align"   : table.align	 = value; break;
          case "f_border"  : table.border	 = parseInt(value); break;
          case "f_spacing" : table.cellSpacing = parseInt(value); break;
          case "f_padding" : table.cellPadding = parseInt(value); break;
      }
    }
    var cellwidth = 0;
    if (param.f_fixed)

      cellwidth = Math.floor(100 / parseInt(param.f_cols));
// code to add header row

	var tbody = doc.createElement("thead");
    table.appendChild(tbody);
	var tr = doc.createElement("tr");
      tbody.appendChild(tr);
	 for (var j = 0; j < param["f_cols"]; ++j) {
        var td = doc.createElement("th");
        td.scope = "col";
        if (cellwidth)
          td.style.width = cellwidth + "%";
        tr.appendChild(td);
        // Browsers like to see something inside the cell (&nbsp;).
       td.appendChild(doc.createTextNode('\u00a0'));
      } 
	 
// end of code to add header row

    var tbody = doc.createElement("tbody");
    table.appendChild(tbody);
// if there is a header row, i=1, no header row i=0
    for (var i = 1; i < param["f_rows"]; ++i) {
      var tr = doc.createElement("tr");
      tbody.appendChild(tr);

// if there is a header column, i=1, no header column i=0
      for (var j = 0; j < param["f_cols"]; ++j) {
        var td = doc.createElement("td");
        if (cellwidth)
          td.style.width = cellwidth + "%";
        tr.appendChild(td);
        // Browsers like to see something inside the cell (&nbsp;).
       td.appendChild(doc.createTextNode('\u00a0'));
      }
    }
    if (HTMLArea.is_ie) {
      range.pasteHTML(table.outerHTML);
    } else {
      // insert the table
      editor.insertNodeAtSelection(table);
    }
    return true;
  }, null);
};

HTMLArea.prototype._insertTable3 = function() {
  var sel = this._getSelection();
  var range = this._createRange(sel);
  var editor = this;	// for nested functions
  this._popupDialog(editor.config.URIs["insert_table"], function(param) {
    if (!param) {	// user must have pressed Cancel
      return false;
    }
    var doc = editor._doc;
    // create the table element
    var table = doc.createElement("table");
    // assign the given arguments

    for (var field in param) {
      var value = param[field];
      if (!value) {
        continue;
      }
      switch (field) {

	
		  case "f_summary" : table.summary = value; break;
		  case "f_frame" : table.frame = value; break;
		  case "f_rules" : table.rules = value; break;
          case "f_width"   : table.style.width = value + param["f_unit"]; break;
          case "f_align"   : table.align	 = value; break;
          case "f_border"  : table.border	 = parseInt(value); break;
          case "f_spacing" : table.cellSpacing = parseInt(value); break;
          case "f_padding" : table.cellPadding = parseInt(value); break;
      }
    }
    var cellwidth = 0;
    if (param.f_fixed)
      cellwidth = Math.floor(100 / parseInt(param.f_cols));


    var tbody = doc.createElement("tbody");
    table.appendChild(tbody);
// if there is a header row, i=1, no header row i=0
    for (var i = 0; i < param["f_rows"]; ++i) {
      var tr = doc.createElement("tr");
      tbody.appendChild(tr);

// create a header column
        var th = doc.createElement("th");
        th.scope = "row";
        if (cellwidth)
          th.style.width = cellwidth + "%";
        tr.appendChild(th);
        // Browsers like to see something inside the cell (&nbsp;).
       th.appendChild(doc.createTextNode('\u00a0'));
//end of code to add a header column
// if there is a header column, i=1, no header column i=0
      for (var j = 1; j < param["f_cols"]; ++j) {
        var td = doc.createElement("td");
        if (cellwidth)
          td.style.width = cellwidth + "%";
        tr.appendChild(td);
        // Browsers like to see something inside the cell (&nbsp;).
       td.appendChild(doc.createTextNode('\u00a0'));
      }
    }
    if (HTMLArea.is_ie) {
      range.pasteHTML(table.outerHTML);
    } else {
      // insert the table
      editor.insertNodeAtSelection(table);
    }
    return true;
  }, null);
};

HTMLArea.prototype._insertTable4 = function() {
  var sel = this._getSelection();
  var range = this._createRange(sel);
  var editor = this;	// for nested functions
  this._popupDialog(editor.config.URIs["insert_table"], function(param) {
    if (!param) {	// user must have pressed Cancel
      return false;
    }
    var doc = editor._doc;
    // create the table element
    var table = doc.createElement("table");
    // assign the given arguments

    for (var field in param) {
      var value = param[field];
      if (!value) {
        continue;
      }
      switch (field) {


		  case "f_summary" : table.summary = value; break;
		  case "f_frame" : table.frame = value; break;
		  case "f_rules" : table.rules = value; break;
          case "f_width"   : table.style.width = value + param["f_unit"]; break;
          case "f_align"   : table.align	 = value; break;
          case "f_border"  : table.border	 = parseInt(value); break;
          case "f_spacing" : table.cellSpacing = parseInt(value); break;
          case "f_padding" : table.cellPadding = parseInt(value); break;
      }
    }
    var cellwidth = 0;
    if (param.f_fixed)
      cellwidth = Math.floor(100 / parseInt(param.f_cols));
// code to add header row
//	 if ((param.f_headers=="top_th")||(param.f_headers=="top_left_th")){

	var tbody = doc.createElement("thead");
    table.appendChild(tbody);
	var tr = doc.createElement("tr");
      tbody.appendChild(tr);
	 for (var j = 0; j < param["f_cols"]; ++j) {
        var th = doc.createElement("th");
        th.scope = "col";
        if (cellwidth)
          th.style.width = cellwidth + "%";
        tr.appendChild(th);
        // Browsers like to see something inside the cell (&nbsp;).
       th.appendChild(doc.createTextNode('\u00a0'));
      } 

// end of code to add header row

    var tbody = doc.createElement("tbody");
    table.appendChild(tbody);
// if there is a header row, i=1, no header row i=0
    for (var i = 1; i < param["f_rows"]; ++i) {
      var tr = doc.createElement("tr");
      tbody.appendChild(tr);

// create a header column
        var th = doc.createElement("th");
        th.scope = "row";
        if (cellwidth)
          th.style.width = cellwidth + "%";
        tr.appendChild(th);
        // Browsers like to see something inside the cell (&nbsp;).
       th.appendChild(doc.createTextNode('\u00a0'));
//end of code to add a header column
// if there is a header column, i=1, no header column i=0
      for (var j = 1; j < param["f_cols"]; ++j) {
        var td = doc.createElement("td");
        if (cellwidth)
          td.style.width = cellwidth + "%";
        tr.appendChild(td);
        // Browsers like to see something inside the cell (&nbsp;).
       td.appendChild(doc.createTextNode('\u00a0'));
      }
    }
    if (HTMLArea.is_ie) {
      range.pasteHTML(table.outerHTML);
    } else {
      // insert the table
      editor.insertNodeAtSelection(table);
    }
    return true;
  }, null);
};

comment:4 Changed 12 years ago by mharrisonline

...also, in a perfect world, this would simply be a modification of the one insertTable function that would recieve the parameter from the radio buttons and then selectively implement TH rows or columns, or not.

This was the HTML for the mostly stock popup:

<html>

<head>
  <title>Insert Table</title>

<script type="text/javascript" src="popup.js"></script>
<link rel="stylesheet" type="text/css" href="popup.css" />

<script type="text/javascript">

window.resizeTo(500, 500);

HTMLArea = window.opener.HTMLArea;
function i18n(str) {
  return (HTMLArea._lc(str, 'HTMLArea'));
};

function Init() {
  HTMLArea = window.opener.HTMLArea; // load the HTMLArea plugin and lang file
  __dlg_translate('HTMLArea');
  __dlg_init();
  // Make sure the translated string appears in the drop down. (for gecko)
  document.getElementById("f_unit").selectedIndex = 1;
  document.getElementById("f_unit").selectedIndex = 0;
  document.getElementById("f_align").selectedIndex = 1;
  document.getElementById("f_align").selectedIndex = 0;
  document.getElementById("f_rows").focus();
};

function onOK() {
  var required = {
    "f_rows": i18n("You must enter a number of rows"),
    "f_cols": i18n("You must enter a number of columns")
  };
  for (var i in required) {
    var el = document.getElementById(i);
    if (!el.value) {
      alert(required[i]);
      el.focus();
      return false;
    }
  }
  var fields = ["f_summary", "f_frame", "f_rules", "f_rows", "f_cols", "f_width", "f_unit", "f_fixed", "f_align", "f_border", "f_spacing", "f_padding"];
  var param = new Object();
  for (var i in fields) {
    var id = fields[i];
    var el = document.getElementById(id);
    param[id] = (el.type == "checkbox") ? el.checked : el.value;
  }
  __dlg_close(param);
  return false;
};

function onCancel() {
  __dlg_close(null);
  return false;
};

</script>

</head>

<body class="dialog" onLoad="Init()">

<div class="title">Insert Table</div>

<form action="" method="get">
<fieldset style="margin-left: 5px;">
<legend>Description</legend>
<div class="space"></div>
<p><span class="label"><img src="/images/editors/icons/icon_508.gif" width="30" height="30" align="left"></span>Summary (Description for screen readers)</p>
<p>
  <textarea name="summary" cols="25" rows="2" id="f_summary" title="Description for screen readers"></textarea>
</p>

<div class="space"></div>

</fieldset>
<div class="space"></div>
<table border="0" style="padding: 0px; margin: 0px">
  <tbody>

  <tr>
    <td style="width: 4em; text-align: right">Rows:</td>
    <td><input type="text" name="rows" id="f_rows" size="5" title="Number of rows" value="2" /></td>
    <td style="width: 4em; text-align: right">Width:</td>
    <td><input type="text" name="width" id="f_width" size="5" title="Width of the table" value="100" /></td>
    <td><select size="1" name="unit" id="f_unit" title="Width unit">
      <option value="%" selected="1"  >Percent</option>
      <option value="px"              >Pixels</option>
      <option value="em"              >Em</option>
    </select></td>
  </tr>
  <tr>
    <td style="width: 4em; text-align: right">Cols:</td>
    <td><input type="text" name="cols" id="f_cols" size="5" title="Number of columns" value="4" /></td>
    <td style="text-align: right"><input type="checkbox" checked="checked" name="fixed" id="f_fixed" /></td>
    <td colspan="2"><label for="f_fixed"
    >Fixed width columns</label></td>
  </tr>
  </tbody>
</table>

<p />

<fieldset style="margin-left: 5px;">
<legend>Layout</legend>

<div class="space"></div>

<div class="fl">Alignment:</div>
<select size="1" name="align" id="f_align"
  title="Positioning of this table">
  <option value="" selected="1"                >Not set</option>
  <option value="left"                         >Left</option>
  <option value="right"                        >Right</option>
  <option value="texttop"                      >Texttop</option>
  <option value="absmiddle"                    >Absmiddle</option>
  <option value="baseline"                     >Baseline</option>
  <option value="absbottom"                    >Absbottom</option>
  <option value="bottom"                       >Bottom</option>
  <option value="middle"                       >Middle</option>
  <option value="top"                          >Top</option>
</select>

<p />

<div class="fl">Border thickness:</div>
<input type="text" name="border" id="f_border" size="5" value="1"
title="Leave empty for no border" />

<p />
<div class="space"></div>
<div class="fl">Collapse borders:</div>
<input name="collapse" type="checkbox" id="f_collapse" checked />

<div class="space"></div>

</fieldset>

<fieldset style="margin-right: 5px;">
<legend>Spacing</legend>

<div class="space"></div>

<div class="fr">Cell spacing:</div>
<input type="text" name="spacing" id="f_spacing" size="5" value="0"
title="Space between adjacent cells" />

<p />

<div class="fr">Cell padding:</div>
<input type="text" name="padding" id="f_padding" size="5" value="5"
title="Space between content and border in cell" />

<div class="space"></div>

</fieldset>
<fieldset style="float: left; margin-left: 5px;">
<legend>Grid Style (Optional)</legend>

<div class="space"></div>

<div class="fl">Outer Borders:<br /></div>
<select size="1" name="frame" id="f_frame"
  title="Outer borders of this table">
  <option value="" selected="1"                >Not Set</option>
  <option value="box"                       >All four sides</option>
  <option value="void"                         >No outside borders</option>
  <option value="above"                        >The top side only</option>
  <option value="below"                      >The bottom side only</option>
  <option value="hsides"                    >The top and bottom sides only</option>
  <option value="vsides"                     >The right and left sides only</option>
  <option value="lhs"                    >The left-hand side only</option>
  <option value="rhs"                       >The right-hand side only</option>


</select>

<p />

<div class="space"></div>


<div class="fl">Inner Borders:<br /></div>
<select size="1" name="rules" id="f_rules"
  title="Positioning of this table">
  <option value="" selected="1"                >Not Set</option>
  <option value="all"                         >Borders will appear between all rows and columns</option>
  <option value="rows"                        >Borders will appear between rows only</option>
  <option value="cols"                      >Borders will appear between columns only</option>

</select>

 <p />



</fieldset>

<div style="margin-top: 100px; border-top: 1px solid #999; padding: 2px; text-align: right;">
<button type="button" name="ok" onClick="return onOK();">OK</button>
<button type="button" name="cancel" onClick="return onCancel();">Cancel</button>
</div>

</form>

</body>
</html>

comment:5 Changed 10 years ago by gogo

  • Resolution set to no action needed
  • Status changed from new to closed

Too old, nobody complains, inactive. Patches welcome.

Note: See TracTickets for help on using tickets.