Issue #1276908 by jessebeach, nod_, moshe weitzman, lewisnyman, webchick: Fixed Administrative tables are too wide for smaller screens.

8.0.x
webchick 2012-09-26 14:26:15 -04:00
parent 36e4c77f6c
commit 6e8a223b15
14 changed files with 332 additions and 36 deletions

View File

@ -33,6 +33,22 @@ const MARK_NEW = 1;
*/
const MARK_UPDATED = 2;
/**
* A responsive table class; hide table cell on narrow devices.
*
* Indicates that a column has medium priority and thus can be hidden on narrow
* width devices and shown on medium+ width devices (i.e. tablets and desktops).
*/
const RESPONSIVE_PRIORITY_MEDIUM = 'priority-medium';
/**
* A responsive table class; only show table cell on wide devices.
*
* Indicates that a column has low priority and thus can be hidden on narrow
* and medium viewports and shown on wide devices (i.e. desktops).
*/
const RESPONSIVE_PRIORITY_LOW = 'priority-low';
/**
* @} End of "defgroup content_flags".
*/
@ -1759,6 +1775,14 @@ function theme_breadcrumb($variables) {
* - "field": The database field represented in the table column (required
* if user is to be able to sort on this column).
* - "sort": A default sort order for this column ("asc" or "desc").
* - "class": An array of values for the 'class' attribute. In particular,
* the least important columns that can be hidden on narrow and medium
* width screens should have a 'priority-low' class, referenced with the
* RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on
* medium+ wide screens should be marked up with a class of
* 'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM
* constant. Themes may hide columns with one of these two classes on
* narrow viewports to save horizontal space.
* - Any HTML attributes, such as "colspan", to apply to the column header
* cell.
* - rows: An array of table rows. Every row is an array of cells, or an
@ -1829,6 +1853,7 @@ function theme_table($variables) {
$caption = $variables['caption'];
$colgroups = $variables['colgroups'];
$sticky = $variables['sticky'];
$responsive = $variables['responsive'];
$empty = $variables['empty'];
// Add sticky headers, if applicable.
@ -1838,6 +1863,15 @@ function theme_table($variables) {
// This is needed to target tables constructed by this function.
$attributes['class'][] = 'sticky-enabled';
}
// If the table has headers and it should react responsively to columns hidden
// with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
// and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors.
if (count($header) && $responsive) {
drupal_add_library('system', 'drupal.tableresponsive');
// Add 'responsive-enabled' class to the table to identify it for JS.
// This is needed to target tables constructed by this function.
$attributes['class'][] = 'responsive-enabled';
}
$output = '<table' . new Attribute($attributes) . ">\n";
@ -1894,13 +1928,28 @@ function theme_table($variables) {
$rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message')));
}
$responsive = array();
// Format the table header:
if (count($header)) {
$ts = tablesort_init($header);
// HTML requires that the thead tag has tr tags in it followed by tbody
// tags. Using ternary operator to check and see if we have any rows.
$output .= (count($rows) ? ' <thead><tr>' : ' <tr>');
$i = 0;
foreach ($header as $cell) {
$i++;
// Track responsive classes for each column as needed. Only the header
// cells for a column are marked up with the responsive classes by a
// module developer or themer. The responsive classes on the header cells
// must be transferred to the content cells.
if (!empty($cell['class']) && is_array($cell['class'])) {
if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) {
$responsive[$i] = RESPONSIVE_PRIORITY_MEDIUM;
}
elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) {
$responsive[$i] = RESPONSIVE_PRIORITY_LOW;
}
}
$cell = tablesort_header($cell, $header, $ts);
$output .= _theme_table_cell($cell, TRUE);
}
@ -1944,7 +1993,19 @@ function theme_table($variables) {
$output .= ' <tr' . new Attribute($attributes) . '>';
$i = 0;
foreach ($cells as $cell) {
$cell = tablesort_cell($cell, $header, $ts, $i++);
$i++;
// Add active class if needed for sortable tables.
$cell = tablesort_cell($cell, $header, $ts, $i);
// Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
// class from header to cell as needed.
if (isset($responsive[$i])) {
if (is_array($cell)) {
$cell['class'][] = $responsive[$i];
}
else {
$cell = array('data' => $cell, 'class' => $responsive[$i]);
}
}
$output .= _theme_table_cell($cell);
}
$output .= " </tr>\n";
@ -2865,7 +2926,7 @@ function drupal_common_theme() {
'variables' => array(),
),
'table' => array(
'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'empty' => ''),
'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'responsive' => TRUE, 'empty' => ''),
),
'meter' => array(
'variables' => array('display_value' => NULL, 'form' => NULL, 'high' => NULL, 'low' => NULL, 'max' => NULL, 'min' => NULL, 'optimum' => NULL, 'value' => NULL, 'percentage' => NULL, 'attributes' => array()),

View File

@ -50,6 +50,7 @@ Drupal.tableDrag = function (table, tableSettings) {
var $table = $(table);
// Required object variables.
this.$table = $(table);
this.table = table;
this.tableSettings = tableSettings;
this.dragObject = null; // Used to hold information about a current drag operation.
@ -143,7 +144,8 @@ Drupal.tableDrag = function (table, tableSettings) {
* 'Drupal.tableDrag.showWeight' localStorage value.
*/
Drupal.tableDrag.prototype.initColumns = function () {
var $table = $(this.table), hidden, cell, columnIndex;
var $table = this.$table;
var hidden, cell, columnIndex;
for (var group in this.tableSettings) {
if (this.tableSettings.hasOwnProperty(group)) { // Find the first field in this group.
for (var d in this.tableSettings[group]) {

View File

@ -0,0 +1,141 @@
(function ($, Drupal, window) {
"use strict";
/**
* Attach the tableResponsive function to Drupal.behaviors.
*/
Drupal.behaviors.tableResponsive = {
attach: function (context, settings) {
var $tables = $(context).find('table.responsive-enabled').once('tableresponsive');
if ($tables.length) {
for (var i = 0, il = $tables.length; i < il; i++) {
TableResponsive.tables.push(new TableResponsive($tables[i]));
}
}
}
};
/**
* The TableResponsive object optimizes table presentation for all screen sizes.
*
* A responsive table hides columns at small screen sizes, leaving the most
* important columns visible to the end user. Users should not be prevented from
* accessing all columns, however. This class adds a toggle to a table with
* hidden columns that exposes the columns. Exposing the columns will likely
* break layouts, but it provides the user with a means to access data, which
* is a guiding principle of responsive design.
*/
function TableResponsive (table) {
this.table = table;
this.$table = $(table);
this.showText = Drupal.t('Show all columns');
this.hideText = Drupal.t('Hide unimportant columns');
// Store a reference to the header elements of the table so that the DOM is
// traversed only once to find them.
this.$headers = this.$table.find('th');
// Add a link before the table for users to show or hide weight columns.
this.$link = $('<a href="#" class="tableresponsive-toggle"></a>')
.attr({
'title': Drupal.t('Show table cells that were hidden to make the table fit within a small screen.')
})
.on('click', $.proxy(this, 'eventhandlerToggleColumns'));
this.$table.before($('<div class="tableresponsive-toggle-columns"></div>').append(this.$link));
// Attach a resize handler to the window.
$(window)
.on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility'))
.trigger('resize.tableresponsive');
}
/**
* Extend the TableResponsive function with a list of managed tables.
*/
$.extend(TableResponsive, {
tables: []
});
/**
* Associates an action link with the table that will show hidden columns.
*
* Columns are assumed to be hidden if their header has the class priority-low
* or priority-medium.
*/
$.extend(TableResponsive.prototype, {
eventhandlerEvaluateColumnVisibility: function (e) {
var pegged = parseInt(this.$link.data('pegged'), 10);
var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length;
// If the table has hidden columns, associate an action link with the table
// to show the columns.
if (hiddenLength > 0) {
this.$link.show().text(this.showText);
}
// When the toggle is pegged, its presence is maintained because the user
// has interacted with it. This is necessary to keep the link visible if the
// user adjusts screen size and changes the visibilty of columns.
if (!pegged && hiddenLength === 0) {
this.$link.hide().text(this.hideText);
}
},
// Toggle the visibility of columns classed with either 'priority-low' or
// 'priority-medium'.
eventhandlerToggleColumns: function (e) {
e.preventDefault();
var self = this;
var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden');
this.$revealedCells = this.$revealedCells || $();
// Reveal hidden columns.
if ($hiddenHeaders.length > 0) {
$hiddenHeaders.each(function (index, element) {
var $header = $(this);
var position = $header.prevAll('th').length;
self.$table.find('tbody tr').each(function () {
var $cells = $(this).find('td:eq(' + position + ')');
$cells.show();
// Keep track of the revealed cells, so they can be hidden later.
self.$revealedCells = $().add(self.$revealedCells).add($cells);
});
$header.show();
// Keep track of the revealed headers, so they can be hidden later.
self.$revealedCells = $().add(self.$revealedCells).add($header);
});
this.$link.text(this.hideText).data('pegged', 1);
}
// Hide revealed columns.
else {
this.$revealedCells.hide();
// Strip the 'display:none' declaration from the style attributes of
// the table cells that .hide() added.
this.$revealedCells.each(function (index, element) {
var $cell = $(this);
var properties = $cell.attr('style').split(';');
var newProps = [];
// The hide method adds display none to the element. The element should
// be returned to the same state it was in before the columns were
// revealed, so it is necessary to remove the display none
// value from the style attribute.
var match = /^display\s*\:\s*none$/;
for (var i = 0; i < properties.length; i++) {
var prop = properties[i];
prop.trim();
// Find the display:none property and remove it.
var isDisplayNone = match.exec(prop);
if (isDisplayNone) {
continue;
}
newProps.push(prop);
}
// Return the rest of the style attribute values to the element.
$cell.attr('style', newProps.join(';'));
});
this.$link.text(this.showText).data('pegged', 0);
// Refresh the toggle link.
$(window).trigger('resize.tableresponsive');
}
}
});
// Make the TableResponsive object available in the Drupal namespace.
Drupal.TableResponsive = TableResponsive;
})(jQuery, Drupal, window);

View File

@ -73,9 +73,9 @@ function comment_admin_overview($form, &$form_state, $arg) {
$status = ($arg == 'approval') ? COMMENT_NOT_PUBLISHED : COMMENT_PUBLISHED;
$header = array(
'subject' => array('data' => t('Subject'), 'field' => 'subject'),
'author' => array('data' => t('Author'), 'field' => 'name'),
'posted_in' => array('data' => t('Posted in'), 'field' => 'node_title'),
'changed' => array('data' => t('Updated'), 'field' => 'c.changed', 'sort' => 'desc'),
'author' => array('data' => t('Author'), 'field' => 'name', 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)),
'posted_in' => array('data' => t('Posted in'), 'field' => 'node_title', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
'changed' => array('data' => t('Updated'), 'field' => 'c.changed', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
'operations' => array('data' => t('Operations')),
);

View File

@ -33,11 +33,11 @@ function dblog_overview() {
$header = array(
'', // Icon column.
array('data' => t('Type'), 'field' => 'w.type'),
array('data' => t('Date'), 'field' => 'w.wid', 'sort' => 'desc'),
array('data' => t('Type'), 'field' => 'w.type', 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)),
array('data' => t('Date'), 'field' => 'w.wid', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
t('Message'),
array('data' => t('User'), 'field' => 'u.name'),
array('data' => t('Operations')),
array('data' => t('User'), 'field' => 'u.name', 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)),
array('data' => t('Operations'), 'class' => array(RESPONSIVE_PRIORITY_LOW)),
);
$query = db_select('watchdog', 'w')

View File

@ -17,7 +17,11 @@ function field_ui_fields_list() {
$modules = system_rebuild_module_data();
$header = array(t('Field name'), t('Field type'), t('Used in'));
$header = array(
t('Field name'),
array('data' => t('Field type'), 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)),
t('Used in'),
);
$rows = array();
foreach ($instances as $entity_type => $type_bundles) {
foreach ($type_bundles as $bundle => $bundle_instances) {

View File

@ -436,14 +436,32 @@ function node_admin_nodes() {
// Build the sortable table header.
$header = array(
'title' => array('data' => t('Title'), 'field' => 'n.title'),
'type' => array('data' => t('Content type'), 'field' => 'n.type'),
'author' => t('Author'),
'status' => array('data' => t('Status'), 'field' => 'n.status'),
'changed' => array('data' => t('Updated'), 'field' => 'n.changed', 'sort' => 'desc')
'title' => array(
'data' => t('Title'),
'field' => 'n.title',
),
'type' => array(
'data' => t('Content type'),
'field' => 'n.type',
'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
),
'author' => array(
'data' => t('Author'),
'class' => array(RESPONSIVE_PRIORITY_LOW),
),
'status' => array(
'data' => t('Status'),
'field' => 'n.status',
),
'changed' => array(
'data' => t('Updated'),
'field' => 'n.changed',
'sort' => 'desc',
'class' => array(RESPONSIVE_PRIORITY_LOW)
,)
);
if ($multilingual) {
$header['language_name'] = array('data' => t('Language'), 'field' => 'n.langcode');
$header['language_name'] = array('data' => t('Language'), 'field' => 'n.langcode', 'class' => array(RESPONSIVE_PRIORITY_LOW));
}
$header['operations'] = array('data' => t('Operations'));

View File

@ -921,8 +921,8 @@ function system_modules($form, $form_state = array()) {
'#header' => array(
array('data' => t('Enabled'), 'class' => array('checkbox')),
t('Name'),
t('Version'),
t('Description'),
array('data' => t('Version'), 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)),
array('data' => t('Description'), 'class' => array(RESPONSIVE_PRIORITY_LOW)),
array('data' => t('Operations'), 'colspan' => 3),
),
// Ensure that the "Core" package fieldset comes first.

View File

@ -1287,6 +1287,20 @@ function system_library_info() {
),
);
// Drupal's responsive table API.
$libraries['drupal.tableresponsive'] = array(
'title' => 'Drupal responsive table API',
'version' => VERSION,
'js' => array(
'core/misc/tableresponsive.js' => array('group' => JS_LIBRARY),
),
'dependencies' => array(
array('system', 'jquery'),
array('system', 'drupal'),
array('system', 'jquery.once'),
),
);
// Drupal's collapsible fieldset.
$libraries['drupal.collapse'] = array(
'title' => 'Drupal collapsible fieldset',

View File

@ -145,10 +145,10 @@ function user_admin_account() {
$header = array(
'username' => array('data' => t('Username'), 'field' => 'u.name'),
'status' => array('data' => t('Status'), 'field' => 'u.status'),
'roles' => array('data' => t('Roles')),
'member_for' => array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc'),
'access' => array('data' => t('Last access'), 'field' => 'u.access'),
'status' => array('data' => t('Status'), 'field' => 'u.status', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
'roles' => array('data' => t('Roles'), 'class' => array(RESPONSIVE_PRIORITY_LOW)),
'member_for' => array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
'access' => array('data' => t('Last access'), 'field' => 'u.access', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
'operations' => array('data' => t('Operations')),
);

View File

@ -1,4 +1,3 @@
/* ---------- Overall Specifications ---------- */
body {
@ -1708,3 +1707,21 @@ div.admin-panel .description {
padding-bottom: 2em;
}
}
/**
* Responsive tables.
*/
@media screen and (max-width:28.125em) { /* 450px */
th.helpful,
td.helpful,
th.priority-medium,
td.priority-medium {
display: none;
}
}
@media screen and (max-width:45em) { /* 720px */
th.helpful,
td.helpful {
display: none;
}
}

View File

@ -1,4 +1,3 @@
/**
* Generic elements.
*/
@ -71,11 +70,6 @@ ul.primary {
/**
* Page layout.
*/
#page {
padding: 20px 0 40px 0;
margin-left: 40px;
margin-right: 40px;
}
ul.links li,
ul.inline li {
padding-left: 1em;

View File

@ -1,4 +1,3 @@
/**
* Generic elements.
*/
@ -314,12 +313,24 @@ ul.secondary li.active a.active {
* Page layout.
*/
#page {
padding: 20px 0 40px 0; /* LTR */
margin-right: 40px; /* LTR */
margin-left: 40px; /* LTR */
background: #fff;
position: relative;
color: #333;
margin-left: 0.8125em;
margin-right: 0.8125em;
padding: 20px 0 40px 0;
position: relative;
}
@media screen and (min-width:28.125em) { /* 450px */
#page {
margin-left: 1.25em;
margin-right: 1.25em;
}
}
@media screen and (min-width:45em) { /* 720px */
#page {
margin-left: 2.5em;
margin-right: 2.5em;
}
}
ul.links li,
ul.inline li {
@ -476,6 +487,23 @@ tr td:last-child {
}
/**
* Responsive tables.
*/
@media screen and (max-width:28.125em) { /* 450px */
th.priority-low,
td.priority-low,
th.priority-medium,
td.priority-medium {
display: none;
}
}
@media screen and (max-width:45em) { /* 720px */
th.priority-low,
td.priority-low {
display: none;
}
}
/**
* Fieldsets.
*

View File

@ -1,4 +1,3 @@
/**
* @file
* Stark layout method
@ -93,3 +92,21 @@ img {
width: 20%;
}
}
/**
* Responsive tables.
*/
@media screen and (max-width:28.125em) { /* 450px */
th.priority-low,
td.priority-low,
th.priority-medium,
td.priority-medium {
display: none;
}
}
@media screen and (max-width:45em) { /* 720px */
th.priority-low,
td.priority-low {
display: none;
}
}