Use colspan on td instead of divs for hierarchical tables (#39948)

Address Firefox table-rendering issues in docs. Refactor to use colspan to provide table cells which can vary in width and indentation; the outermost has the greatest colspan, and each nested key has a colspan of one less than the parent, with padding cells for indentation.
Apply styling to table cells to get the table height to work without hacks or browser-specific
styling.  Simplify the markup and CSS by removing extra divs. Use two passes over the options, return values, and return facts in the Jinja2 module-docs template: one to determine the maximum nesting depth to compute the maximum colspan needed, plus one to lay out the rows.
This commit is contained in:
Brian Campbell 2018-05-31 13:58:33 -04:00 committed by Alicia Cozine
commit fa5c0282a4
3 changed files with 162 additions and 215 deletions

View file

@ -421,51 +421,17 @@ table {
max-width: 100%; max-width: 100%;
} }
.documentation-table td.elbow-placeholder {
.outer-elbow-container {
display: flex;
height: 100%;
flex-direction: row;
}
.elbow-placeholder {
border-left: 1px solid #000; border-left: 1px solid #000;
height: 100%; border-top: 0px;
width: 30px; width: 30px;
min-width: 30px;
} }
.elbow-key { .documentation-table th, .documentation-table td {
height: 100%;
padding: 4px;
border-top: 1px solid #000;
flex-grow: 1;
border-left: 1px solid #000;
}
.elbow-blocker {
height: 0;
overflow: hidden;
}
.return-value-column {
height: 1px
}
.return-value-column td {
height: inherit
}
@-moz-document url-prefix() {
.return-value-column td {
height: 100%
}
}
.cell-border {
padding: 4px; padding: 4px;
border-left: 1px solid #000; border-left: 1px solid #000;
border-top: 1px solid #000; border-top: 1px solid #000;
height: 100%;
} }
.documentation-table { .documentation-table {

View file

@ -4867,50 +4867,17 @@ table {
} }
} }
.outer-elbow-container { .documentation-table td.elbow-placeholder {
display: flex;
height: 100%;
flex-direction: row;
}
.elbow-placeholder {
border-left: 1px solid #000; border-left: 1px solid #000;
height: 100%; border-top: 0px;
width: 30px; width: 30px;
min-width: 30px;
} }
.elbow-key { .documentation-table th, .documentation-table td {
height: 100%;
padding: 4px;
border-top: 1px solid #000;
flex-grow: 1;
border-left: 1px solid #000;
}
.elbow-blocker {
height: 0;
overflow: hidden;
}
.return-value-column {
height: 1px
}
.return-value-column td {
height: inherit
}
@-moz-document url-prefix() {
.return-value-column td {
height: 100%
}
}
.cell-border {
padding: 4px; padding: 4px;
border-left: 1px solid #000; border-left: 1px solid #000;
border-top: 1px solid #000; border-top: 1px solid #000;
height: 100%;
} }
.documentation-table { .documentation-table {

View file

@ -88,104 +88,107 @@ Parameters
.. raw:: html .. raw:: html
<table border=0 cellpadding=0 class="documentation-table"> <table border=0 cellpadding=0 class="documentation-table">
{# Pre-compute the nesting depth to allocate columns #}
{% set ns = namespace(maxdepth=1) %}
{% for key, value in options|dictsort recursive %}
{% set ns.maxdepth = [loop.depth, ns.maxdepth] | max %}
{% if value.suboptions %}
{% if value.suboptions.items %}
@{ loop(value.suboptions.items()) }@
{% elif value.suboptions[0].items %}
@{ loop(value.suboptions[0].items()) }@
{% endif %}
{% endif %}
{% endfor %}
{# Header of the documentation #} {# Header of the documentation #}
<tr> <tr>
<th class="head"><div class="cell-border">Parameter</div></th> <th colspan="@{ ns.maxdepth }@">Parameter</th>
<th class="head"><div class="cell-border">Choices/<font color="blue">Defaults</font></div></th> <th>Choices/<font color="blue">Defaults</font></th>
{% if plugin_type != 'module' %} {% if plugin_type != 'module' %}
<th class="head"><div class="cell-border">Configuration</div></th> <th>Configuration</th>
{% endif %} {% endif %}
<th class="head" width="100%"><div class="cell-border">Comments</div></th> <th width="100%">Comments</th>
</tr> </tr>
{% for key, value in options|dictsort recursive %} {% for key, value in options|dictsort recursive %}
<tr class="return-value-column"> <tr>
{# indentation based on nesting level #}
{% for i in range(1, loop.depth) %}
<td class="elbow-placeholder"></td>
{% endfor %}
{# parameter name with required and/or introduced label #} {# parameter name with required and/or introduced label #}
<td> <td colspan="@{ ns.maxdepth - loop.depth0 }@">
<div class="outer-elbow-container"> <b>@{ key }@</b>
{% for i in range(1, loop.depth) %} {% if value.get('required', False) %}<br/><div style="font-size: small; color: red">required</div>{% endif %}
<div class="elbow-placeholder">&nbsp;</div> {% if value.version_added %}<br/><div style="font-size: small; color: darkgreen">(added in @{value.version_added}@)</div>{% endif %}
{% endfor %}
<div class="elbow-key">
<b>@{ key }@</b>
{% if value.get('required', False) %}<br/><div style="font-size: small; color: red">required</div>{% endif %}
{% if value.version_added %}<br/><div style="font-size: small; color: darkgreen">(added in @{value.version_added}@)</div>{% endif %}
</div>
</div>
</td> </td>
{# default / choices #} {# default / choices #}
<td> <td>
<div class="cell-border"> {# Turn boolean values in 'yes' and 'no' values #}
{# Turn boolean values in 'yes' and 'no' values #} {% if value.default is sameas true %}
{% if value.default is sameas true %} {% set _x = value.update({'default': 'yes'}) %}
{% set _x = value.update({'default': 'yes'}) %} {% elif value.default is sameas false %}
{% elif value.default is sameas false %} {% set _x = value.update({'default': 'no'}) %}
{% set _x = value.update({'default': 'no'}) %} {% endif %}
{% endif %} {% if value.type == 'bool' %}
{% if value.type == 'bool' %} {% set _x = value.update({'choices': ['no', 'yes']}) %}
{% set _x = value.update({'choices': ['no', 'yes']}) %} {% endif %}
{% endif %} {# Show possible choices and highlight details #}
{# Show possible choices and highlight details #} {% if value.choices %}
{% if value.choices %} <ul><b>Choices:</b>
<ul><b>Choices:</b> {% for choice in value.choices %}
{% for choice in value.choices %} {# Turn boolean values in 'yes' and 'no' values #}
{# Turn boolean values in 'yes' and 'no' values #} {% if choice is sameas true %}
{% if choice is sameas true %} {% set choice = 'yes' %}
{% set choice = 'yes' %} {% elif choice is sameas false %}
{% elif choice is sameas false %} {% set choice = 'no' %}
{% set choice = 'no' %} {% endif %}
{% endif %} {% if (value.default is string and value.default == choice) or (value.default is iterable and value.default is not string and choice in value.default) %}
{% if (value.default is string and value.default == choice) or (value.default is iterable and value.default is not string and choice in value.default) %} <li><div style="color: blue"><b>@{ choice | escape }@</b>&nbsp;&larr;</div></li>
<li><div style="color: blue"><b>@{ choice | escape }@</b>&nbsp;&larr;</div></li> {% else %}
{% else %} <li>@{ choice | escape }@</li>
<li>@{ choice | escape }@</li> {% endif %}
{% endif %} {% endfor %}
{% endfor %} </ul>
</ul> {% endif %}
{% endif %} {# Show default value, when multiple choice or no choices #}
{# Show default value, when multiple choice or no choices #} {% if value.default is defined and value.default not in value.choices %}
{% if value.default is defined and value.default not in value.choices %} <b>Default:</b><br/><div style="color: blue">@{ value.default | escape }@</div>
<b>Default:</b><br/><div style="color: blue">@{ value.default | escape }@</div> {% endif %}
{% endif %}
</div>
</td> </td>
{# configuration #} {# configuration #}
{% if plugin_type != 'module' %} {% if plugin_type != 'module' %}
<td> <td>
<div class="cell-border"> {% if 'ini' in value %}
{% if 'ini' in value %} <div> ini entries:
<div> ini entries: {% for ini in value.ini %}
{% for ini in value.ini %} <p>[@{ ini.section }@ ]<br>@{ ini.key }@ = @{ value.default | default('VALUE') }@</p>
<p>[@{ ini.section }@ ]<br>@{ ini.key }@ = @{ value.default | default('VALUE') }@</p>
{% endfor %}
</div>
{% endif %}
{% if 'env' in value %}
{% for env in value.env %}
<div>env:@{ env.name }@</div>
{% endfor %} {% endfor %}
{% endif %} </div>
{% if 'vars' in value %} {% endif %}
{% for myvar in value.vars %} {% if 'env' in value %}
<div>var: @{ myvar.name }@</div> {% for env in value.env %}
{% endfor %} <div>env:@{ env.name }@</div>
{% endif %} {% endfor %}
</div> {% endif %}
{% if 'vars' in value %}
{% for myvar in value.vars %}
<div>var: @{ myvar.name }@</div>
{% endfor %}
{% endif %}
</td> </td>
{% endif %} {% endif %}
{# description #} {# description #}
<td> <td>
<div class="cell-border"> {% if value.description is string %}
{% if value.description is string %} <div>@{ value.description | replace('\n', '\n ') | html_ify }@</div>
<div>@{ value.description | replace('\n', '\n ') | html_ify }@</div> {% else %}
{% else %} {% for desc in value.description %}
{% for desc in value.description %} <div>@{ desc | replace('\n', '\n ') | html_ify }@</div>
<div>@{ desc | replace('\n', '\n ') | html_ify }@</div> {% endfor %}
{% endfor %} {% endif %}
{% endif %} {% if 'aliases' in value and value.aliases %}
{% if 'aliases' in value and value.aliases %} <div style="font-size: small; color: darkgreen"><br/>aliases: @{ value.aliases|join(', ') }@</div>
<div style="font-size: small; color: darkgreen"><br/>aliases: @{ value.aliases|join(', ') }@</div> {% endif %}
{% endif %}
</div>
</td> </td>
</tr> </tr>
{% if value.suboptions %} {% if value.suboptions %}
@ -242,43 +245,49 @@ Facts returned by this module are added/updated in the ``hostvars`` host facts a
.. raw:: html .. raw:: html
<table border=0 cellpadding=0 class="documentation-table"> <table border=0 cellpadding=0 class="documentation-table">
{# Pre-compute the nesting depth to allocate columns #}
{% set ns = namespace(maxdepth=1) %}
{% for key, value in returnfacts|dictsort recursive %}
{% set ns.maxdepth = [loop.depth, ns.maxdepth] | max %}
{% if value.contains %}
{% if value.contains.items %}
@{ loop(value.contains.items()) }@
{% elif value.contains[0].items %}
@{ loop(value.contains[0].items()) }@
{% endif %}
{% endif %}
{% endfor %}
<tr> <tr>
<th class="head"><div class="cell-border">Fact</div></th> <th colspan="@{ ns.maxdepth }@">Fact</th>
<th class="head"><div class="cell-border">Returned</div></th> <th>Returned</th>
<th class="head" width="100%"><div class="cell-border">Description</div></th> <th width="100%">Description</th>
</tr> </tr>
{% for key, value in returnfacts|dictsort recursive %} {% for key, value in returnfacts|dictsort recursive %}
<tr class="return-value-column"> <tr>
<td> {% for i in range(1, loop.depth) %}
<div class="outer-elbow-container"> <td class="elbow-placeholder"></td>
{% for i in range(1, loop.depth) %} {% endfor %}
<div class="elbow-placeholder">&nbsp;</div> <td colspan="@{ ns.maxdepth - loop.depth0 }@" colspan="@{ ns.maxdepth - loop.depth0 }@">
{% endfor %} <b>@{ key }@</b>
<div class="elbow-key"> <br/><div style="font-size: small; color: red">@{ value.type }@</div>
<b>@{ key }@</b>
<br/><div style="font-size: small; color: red">@{ value.type }@</div>
</div>
</div>
</td> </td>
<td><div class="cell-border">@{ value.returned | html_ify }@</div></td> <td>@{ value.returned | html_ify }@</td>
<td> <td>
<div class="cell-border"> {% if value.description is string %}
{% if value.description is string %} <div>@{ value.description | html_ify }@
<div>@{ value.description | html_ify }@ </div>
{% else %}
{% for desc in value.description %}
<div>@{ desc | html_ify }@
</div> </div>
{% else %} {% endfor %}
{% for desc in value.description %} {% endif %}
<div>@{ desc | html_ify }@ <br/>
</div> {% if value.sample is defined and value.sample %}
{% endfor %} <div style="font-size: smaller"><b>Sample:</b></div>
{% endif %} {# TODO: The sample should be escaped, using | escape or | htmlify, but both mess things up beyond repair with dicts #}
<br/> <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">@{ value.sample | replace('\n', '\n ') | html_ify }@</div>
{% if value.sample is defined and value.sample %} {% endif %}
<div style="font-size: smaller"><b>Sample:</b></div>
{# TODO: The sample should be escaped, using | escape or | htmlify, but both mess things up beyond repair with dicts #}
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">@{ value.sample | replace('\n', '\n ') | html_ify }@</div>
{% endif %}
</div>
</td> </td>
</tr> </tr>
{# --------------------------------------------------------- {# ---------------------------------------------------------
@ -308,41 +317,46 @@ Common return values are documented :ref:`here <common_return_values>`, the foll
.. raw:: html .. raw:: html
<table border=0 cellpadding=0 class="documentation-table"> <table border=0 cellpadding=0 class="documentation-table">
{% set ns = namespace(maxdepth=1) %}
{% for key, value in returndocs|dictsort recursive %}
{% set ns.maxdepth = [loop.depth, ns.maxdepth] | max %}
{% if value.contains %}
{% if value.contains.items %}
@{ loop(value.contains.items()) }@
{% elif value.contains[0].items %}
@{ loop(value.contains[0].items()) }@
{% endif %}
{% endif %}
{% endfor %}
<tr> <tr>
<th class="head"><div class="cell-border">Key</div></th> <th colspan="@{ ns.maxdepth }@">Key</th>
<th class="head"><div class="cell-border">Returned</div></th> <th>Returned</th>
<th class="head" width="100%"><div class="cell-border">Description</div></th> <th width="100%">Description</th>
</tr> </tr>
{% for key, value in returndocs|dictsort recursive %} {% for key, value in returndocs|dictsort recursive %}
<tr class="return-value-column"> <tr>
<td> {% for i in range(1, loop.depth) %}
<div class="outer-elbow-container"> <td class="elbow-placeholder">&nbsp;</td>
{% for i in range(1, loop.depth) %} {% endfor %}
<div class="elbow-placeholder">&nbsp;</div> <td colspan="@{ ns.maxdepth - loop.depth0 }@">
{% endfor %} <b>@{ key }@</b>
<div class="elbow-key"> <br/><div style="font-size: small; color: red">@{ value.type }@</div>
<b>@{ key }@</b>
<br/><div style="font-size: small; color: red">@{ value.type }@</div>
</div>
</div>
</td> </td>
<td><div class="cell-border">@{ value.returned | html_ify }@</div></td> <td>@{ value.returned | html_ify }@</td>
<td> <td>
<div class="cell-border"> {% if value.description is string %}
{% if value.description is string %} <div>@{ value.description | html_ify |indent(4)}@</div>
<div>@{ value.description | html_ify |indent(4)}@</div> {% else %}
{% else %} {% for desc in value.description %}
{% for desc in value.description %} <div>@{ desc | html_ify |indent(4)}@</div>
<div>@{ desc | html_ify |indent(4)}@</div> {% endfor %}
{% endfor %} {% endif %}
{% endif %} <br/>
<br/> {% if value.sample is defined and value.sample %}
{% if value.sample is defined and value.sample %} <div style="font-size: smaller"><b>Sample:</b></div>
<div style="font-size: smaller"><b>Sample:</b></div> {# TODO: The sample should be escaped, using |escape or |htmlify, but both mess things up beyond repair with dicts #}
{# TODO: The sample should be escaped, using |escape or |htmlify, but both mess things up beyond repair with dicts #} <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">@{ value.sample | replace('\n', '\n ') | html_ify }@</div>
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">@{ value.sample | replace('\n', '\n ') | html_ify }@</div> {% endif %}
{% endif %}
</div>
</td> </td>
</tr> </tr>
{# --------------------------------------------------------- {# ---------------------------------------------------------