diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 7766952c99e..cb7a84db2b4 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -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 = '
\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) ? ' ' : '
');
+ $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 .= '
';
$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 .= "
\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()),
diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js
index 07c50d512ce..230f5889dfa 100644
--- a/core/misc/tabledrag.js
+++ b/core/misc/tabledrag.js
@@ -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]) {
diff --git a/core/misc/tableresponsive.js b/core/misc/tableresponsive.js
new file mode 100644
index 00000000000..4a03be366e7
--- /dev/null
+++ b/core/misc/tableresponsive.js
@@ -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 = $('')
+ .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($('').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);
diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc
index 6210cc5aad7..4a3220f591e 100644
--- a/core/modules/comment/comment.admin.inc
+++ b/core/modules/comment/comment.admin.inc
@@ -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')),
);
diff --git a/core/modules/dblog/dblog.admin.inc b/core/modules/dblog/dblog.admin.inc
index 15f23547a40..012d01e47fa 100644
--- a/core/modules/dblog/dblog.admin.inc
+++ b/core/modules/dblog/dblog.admin.inc
@@ -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')
diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc
index 3034156bca5..659334b08cb 100644
--- a/core/modules/field_ui/field_ui.admin.inc
+++ b/core/modules/field_ui/field_ui.admin.inc
@@ -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) {
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index db113f59886..b28a1903b02 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -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'));
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 921c8877f71..109c54b2cd4 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -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.
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index be79ff40eb2..b41bf0862f2 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -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',
diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc
index f9b1c23efc0..180b9f1d077 100644
--- a/core/modules/user/user.admin.inc
+++ b/core/modules/user/user.admin.inc
@@ -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')),
);
diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css
index 7532a4a4b53..9446e25a136 100644
--- a/core/themes/bartik/css/style.css
+++ b/core/themes/bartik/css/style.css
@@ -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;
+ }
+}
diff --git a/core/themes/seven/style-rtl.css b/core/themes/seven/style-rtl.css
index 7070720f4a7..788aa36bc14 100644
--- a/core/themes/seven/style-rtl.css
+++ b/core/themes/seven/style-rtl.css
@@ -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;
diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css
index ebaa86b85f6..24b0035d349 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -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.
*
diff --git a/core/themes/stark/css/layout.css b/core/themes/stark/css/layout.css
index a31a7740ff9..39f533bbf79 100644
--- a/core/themes/stark/css/layout.css
+++ b/core/themes/stark/css/layout.css
@@ -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;
+ }
+}