Responsive 2D table

For WeatherSupermarket I need to layout a grid forecasts for five days for up to six providers, Met Office, AccuWeather etc. I have the providers vertically, each with the location, and a dropdown box to change the location. The days are laid out horizontally. This has always worked fine for Desktop browsers with plenty of pixel real estate, but not for mobiles where there is limited width.

Mon 17th Tue 18th > more days…
Met Office Forecast Forecast Forecast
Weather Channel Forecast Forecast Forecast
AccuWeather Forecast Forecast Forecast
BBC Forecast Forecast Forecast
MetCheck Forecast Forecast Forecast
The Weather Channel Forecast Forecast Forecast

Possible solutions

I looked at making the table header fixed on the left and scrolling horizontally, however this is difficult if you have both vertical and horizontal headers which need to remain on display at all times.

I also tried using floating divs, however this did not work as the forecast cells are all different heights which isn’t known in advance. e.g. Met Office forecasts have a lot of detail, but BBC are one liners.


I kept the markup how it was, but introduced some JavaScript to use the window width to approximate the number of days that can be comfortably shown at one time. To allow the user to see other days, there are two large buttons to move left and right – these are enabled only when there are more columns available in that direction.

For dynamic updates – e.g. resizing the window, or flipping a mobile/tablet between portrait and landscape I put a change listener of the window object.

Example. Portrait on a mobile


Example. Landscape on a mobile


Example. Small Desktop (netbook)



define([ 'jquery', 'underscore' ], function() {

  "use strict";
  var $ = require('jquery');
  var _ = require('underscore');

  var TableResize = function(options) {
    this.table = $(options.table);
    this.nextButton = $(options.nextButton);
    this.previousButton = $(options.previousButton);
    $(this.nextButton).click(_.bind(, this));
    $(this.previousButton).click(_.bind(this.previous, this));

    this.columnWidth = 200;
    this.headerWidth = 100;
    $(window).resize(_.bind(this.resize, this));
  }; = function() {
    if (this.isNext()) {
  TableResize.prototype.previous = function() {
    if (this.isPrevious()) {
  TableResize.prototype.setPointer = function(pointer) {
    this.visible = pointer;
  TableResize.prototype.isNext = function() {
    return this.visible + this.visibleCols < this.columnCount - 1;
  TableResize.prototype.isPrevious = function() {
    return this.visible > 0;
  TableResize.prototype.buttonsUpdate = function() {
     this.nextButton[0].disabled = !this.isNext();
     this.previousButton[0].disabled = !this.isPrevious();

  TableResize.prototype.update = function() {
  TableResize.prototype.resize = function() {
    this.columnCount = this.table.find('tr').eq(0).find('td,th').length;
    this.visibleCols = this.getVisibleColumns();
    this.visible = 0;
  TableResize.prototype.quickUpdate = function() {
    var i;
    for (i = 0; i < this.columnCount; i++) {
      this.setColumnVisible(i, i >= this.visible && i < this.visible + this.visibleCols);

  TableResize.prototype.getVisibleColumns = function() {
    var visibleCols = ($(window).width() - this.headerWidth) / this.columnWidth;
    visibleCols = Math.floor(visibleCols);
    if (visibleCols < 1) {
      visibleCols = 1;
    return visibleCols;

  TableResize.prototype.getColumn = function(colIndex) {
    var cells = $([]);
    this.table.find("tr").each(function(rowIndex, row) {
      var cell = $(row).find('td,th').eq(colIndex + 1);
      cells = cells.add(cell);
    return cells;
  TableResize.prototype.setColumnVisible = function(colIndex, visible) {

  return TableResize;

Leave a comment

Your email address will not be published.