<!DOCTYPE html>
<html>

  <head>
    <title>HTML table column resizer</title>
    <meta charset="UTF-8" />
    <script  src="https://code.jquery.com/jquery-3.3.1.min.js"  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
    <script src="colresizer.js"></script>
    <script src="sampleapp.js"></script>
    <link href="sampleapp.css" rel="stylesheet" type="text/css" />
    <link href="styles.css" rel="stylesheet" type="text/css" />
  </head>

  <body ng-app="sampleapp">
    <div ng-controller="samplectrl">
      <div ng-controller="samplectrl">
        <main>
          <div>
            <table rz-table=""  rz-mode="resizeMode" id="myTable">
              <thead>
                <tr>
                  <th id="colName">One</th>
                  <th id="colStatus">Two what sho we do here</th>
                  <th id="colNotes">Three</th>
                  <th id="colMore">Four</th>
                  <th id="colEvenMore">Five</th>
                  <th id="colEvenMore1">six</th>
                  <th id="colEvenMore2">seven</th>
                  <th id="colEvenMore3">eight</th>
                </tr>
              </thead>
              <tbody>
                <tr ng-repeat="item in items">
                  <td>{{item}}</td>
                  <td>Medium</td>
                  <td>And this one is very long</td>
                  <td>Short</td>
                  <td>A bit longer</td>
                  <td>And this one is very long</td>
                  <td>Short</td>
                  <td>A bit longer</td>
                </tr>
              </tbody>
            </table>
          </div>
        </main>
      </div>
    </div>
  </body>

</html>
# colresizer

Column resizer of the HTML table - AngularJS directive

Source code is [here](https://github.com/kekeh/colresizer)
angular.module("rzTable", []);

angular.module("rzTable").directive('rzTable', ['resizeStorage', '$injector', '$parse', function(resizeStorage, $injector, $parse) {

    var mode;
    var saveTableSizes;
    var profile;

    var columns = null;
    var ctrlColumns = null;
    var handleColumns = null;
    var listener = null;
    var handles = []
    var table = null;
    var container = null;
    var resizer = null;
    var isFirstDrag = true;

    var cache = null;

    RzController.$inject = ['$scope', '$attrs', '$element'];

    function RzController($scope) {

    }

    function link(scope, element, attr) {
        // Set global reference to table
        table = element;

        // Set global reference to container
        container = scope.container ? angular.element(scope.container) : angular.element(table).parent();

        // Set options to an empty object if undefined
        scope.options = attr.rzOptions ? scope.options || {} : {}

        // Add css styling/properties to table
        angular.element(table).addClass(scope.options.tableClass || 'rz-table');

        // Initialise handlers, bindings and modes
        initialiseAll(table, attr, scope);

        // Bind utility functions to scope object
        bindUtilityFunctions(table, attr, scope)

        // Watch for changes in columns
        watchTableChanges(table, attr, scope)

        // Watch for scope bindings
        setUpWatchers(table, attr, scope)
    }

    function renderWatch(table, attr, scope) {
      return function(oldVal, newVal) {
        if (newVal !== oldVal) {
          cleanUpAll(table);
          initialiseAll(table, attr, scope);
        }
      }
    }

    function setUpWatchers(table, attr, scope) {
        scope.$watch('profile', renderWatch(table, attr, scope))
        scope.$watch('mode', renderWatch(table, attr, scope))
    }

    function watchTableChanges(table, attr, scope) {
        scope.$watch(function () {
          return angular.element(table).find('th').length;
        }, renderWatch(table, attr, scope));
    }

    function bindUtilityFunctions(table, attr, scope) {
        if (!attr.rzModel) return;
        var model = $parse(attr.rzModel)
        model.assign(scope.$parent, {
            update: function() {
                cleanUpAll(table)
                initialiseAll(table, attr, scope)
            },
            reset: function() {
                resetTable(table)
                this.clearStorageActive()
                this.update()
            },
            clearStorage: function() {
                resizeStorage.clearAll()
            },
            clearStorageActive: function() {
                resizeStorage.clearCurrent(table, mode, profile)
            }
        })
    }

    function cleanUpAll(table) {
        isFirstDrag = true;
        deleteHandles(table);
    }

    function resetTable(table) {
        angular.element(table).outerWidth('100%');
        angular.element(table).find('th').width('auto');
    }

    function deleteHandles(table) {
        handles.map(function(h) { h.remove() })
        handles = []
    }

    function initialiseAll(table, attr, scope) {
        // Get all column headers
        columns = angular.element(table).find('th');

        mode = scope.mode;
        saveTableSizes = angular.isDefined(scope.saveTableSizes) ? scope.saveTableSizes : true;
        profile = scope.profile

        // Get the resizer object for the current mode
        var ResizeModel = getResizer(scope, attr);
        if (!ResizeModel) return;
        resizer = new ResizeModel(table, columns, container);

        if (saveTableSizes) {
            // Load column sizes from saved storage
            cache = resizeStorage.loadTableSizes(table, scope.mode, scope.profile)
        }

        // Decide which columns should have a handler attached
        handleColumns = resizer.handles(columns);

        // Decide which columns are controlled and resized
        ctrlColumns = resizer.ctrlColumns;

        // Execute setup function for the given resizer mode
        resizer.setup();

        // Set column sizes from cache
        setColumnSizes(cache);

        // Initialise all handlers for every column
        angular.forEach(handleColumns,function(column) {
            initHandle(scope, table, column);
        })

    }

    function initHandle(scope, table, column) {
        // Prepend a new handle div to the column

        var handle = angular.element('<div>');
        
        handle.addClass(scope.options.handleClass || 'rz-handle');
        
        angular.element(column).prepend(handle);

        // Add handles to handles for later removal
        handles.push(handle)

        // Use the middleware to decide which columns this handle controls
        var controlledColumn = resizer.handleMiddleware(handle, column)

        // Bind mousedown, mousemove & mouseup events
        bindEventToHandle(scope, table, handle, controlledColumn);
    }

    function bindEventToHandle(scope, table, handle, column) {

        // This event starts the dragging
        angular.element(handle).bind('mousedown',  function(event) {
            if (isFirstDrag) {
                resizer.onFirstDrag(column, handle);
                resizer.onTableReady();
                isFirstDrag = false;
            }

            scope.options.onResizeStarted && scope.options.onResizeStarted(column)

            var optional = {}
            if (resizer.intervene) {
                optional = resizer.intervene.selector(column);
                optional.column = optional;
                optional.orgWidth = angular.element(optional).width();
            }

            // Prevent text-selection, object dragging ect.
            event.preventDefault();

            // Change css styles for the handle
            angular.element(handle).addClass(scope.options.handleClassActive || 'rz-handle-active');

            // Get mouse and column origin measurements
            var orgX = event.clientX;
            var orgWidth = angular.element(column).width();

            // On every mouse move, calculate the new width
            listener = calculateWidthEvent(scope, column, orgX, orgWidth, optional)
            angular.element(window).mousemove(listener)

            // Stop dragging as soon as the mouse is released
            angular.element(window).one('mouseup', unbindEvent(scope, column, handle))
        })
    }

    function calculateWidthEvent(scope, column, orgX, orgWidth, optional) {
        return function(event) {
            // Get current mouse position
            var newX = event.clientX;

            // Use calculator function to calculate new width
            var diffX = newX - orgX;
            var newWidth = resizer.calculate(orgWidth, diffX);

            if (newWidth < getMinWidth(column)) return;
            if (resizer.restrict(newWidth, diffX)) return;

            // Extra optional column
            if (resizer.intervene){
                var optWidth = resizer.intervene.calculator(optional.orgWidth, diffX);
                if (optWidth < getMinWidth(optional.column)) return;
                if (resizer.intervene.restrict(optWidth, diffX)) return;
                angular.element(optional.column).width(optWidth)
            }

            scope.options.onResizeInProgress && scope.options.onResizeInProgress(column, newWidth, diffX)

            // Set size
            angular.element(column).width(newWidth);
        }
    }

    function getMinWidth(column) {
        // "25px" -> 25
        return parseInt(angular.element(column).css('min-width')) || 0;
    }

    function getResizer(scope, attr) {
        try {
            var mode = attr.rzMode ? scope.mode : 'BasicResizer';
            var Resizer = $injector.get(mode)
            return Resizer;
        } catch (e) {
            console.error("The resizer "+ scope.mode +" was not found");
            return null;
        }
    }


    function unbindEvent(scope, column, handle) {
        // Event called at end of drag
        return function( /*event*/ ) {
            angular.element(handle).removeClass(scope.options.handleClassActive || 'rz-handle-active');

            if (listener) {
                angular.element(window).unbind('mousemove', listener);
            }

            scope.options.onResizeEnded && scope.options.onResizeEnded(column)

            resizer.onEndDrag();

            saveColumnSizes();
        }
    }

    function saveColumnSizes() {
        if (!saveTableSizes) return;

        if (!cache) cache = {};
        angular.forEach(columns, function(column) {
            var colScope = angular.element(column).scope()
            var id = colScope.rzCol || angular.element(column).attr('id')
            if (!id) return;
            cache[id] = resizer.saveAttr(column);
        })

        resizeStorage.saveTableSizes(table, mode, profile, cache);
    }

    function setColumnSizes(cache) {
        if (!cache) {
            return;
        }

        angular.element(table).width('auto');
        
        angular.forEach(ctrlColumns,function( column){
            var colScope = angular.element(column).scope()
            var id = colScope.rzCol || angular.element(column).attr('id')
            var cacheWidth = cache[id];
            angular.element(column).css({ width: cacheWidth });
        })

        resizer.onTableReady();
    }

    // Return this directive as a object literal
    return {
        restrict: 'A',
        link: link,
        controller: RzController,
        scope: {
            mode: '=rzMode',
            profile: '=?rzProfile',
            // whether to save table sizes; default true
            saveTableSizes: '=?rzSave',
            options: '=?rzOptions',
            model: '=rzModel',
            container: '@rzContainer'
        }
    };

}]);

angular.module("rzTable").directive('rzCol', [function() {
  // Return this directive as a object literal
  return {
    restrict: 'A',
    priority: 650, /* before ng-if */
    link: link,
    require: '^^rzTable',
    scope: true
  };

  function link(scope, element, attr) {
    scope.rzCol = scope.$eval(attr.rzCol)
  }
}])
angular.module("rzTable").service('resizeStorage', ['$window', function($window) {

    var prefix = "ngColumnResize";

    this.loadTableSizes = function(table, mode, profile) {
        var key = getStorageKey(table, mode, profile);
        var object = $window.localStorage.getItem(key);
        return JSON.parse(object);
    }

    this.saveTableSizes = function(table, mode, profile, sizes) {
        var key = getStorageKey(table, mode, profile);
        if (!key) return;
        var string = JSON.stringify(sizes);
        $window.localStorage.setItem(key, string)
    }

    this.clearAll = function() {
        var keys = []
        for (var i = 0; i < $window.localStorage.length; ++i) {
            var key = localStorage.key(i)
            if (key && key.startsWith(prefix)) {
                keys.push(key)
            }
        }
        keys.map(function(k) { $window.localStorage.removeItem(k) })
    }

    this.clearCurrent = function(table, mode, profile) {
        var key = getStorageKey(table, mode, profile);
        if (key) {
            $window.localStorage.removeItem(key)
        }
    }

    function getStorageKey(table, mode, profile) {
        var id = table.attr('id');
        if (!id) {
            console.error("Table has no id", table);
            return undefined;
        }
        return prefix + '.' + table.attr('id') + '.' + mode + (profile ? '.' + profile : '');
    }

}]);

angular.module("rzTable").factory("ResizerModel", [function() {

    function ResizerModel(table, columns, container){
        this.table = table;
        this.columns = columns;
        this.container = container;

        this.handleColumns = this.handles();
        this.ctrlColumns = this.ctrlColumns();
    }

    ResizerModel.prototype.setup = function() {
        // Hide overflow by default
        angular.element(this.container).css({
            overflowX: 'hidden'
        })
    }

    ResizerModel.prototype.onTableReady = function () {
        // Table is by default 100% width
        angular.element(this.table).outerWidth('100%');
    };

    ResizerModel.prototype.getMinWidth = function(column) {
        // "25px" -> 25
        return parseInt(angular.element(column).css('min-width')) || 0;
    }

    ResizerModel.prototype.handles = function () {
        // By default all columns should be assigned a handle
        return this.columns;
    };

    ResizerModel.prototype.ctrlColumns = function () {
        // By default all columns assigned a handle are resized
        return this.handleColumns;
    };

    ResizerModel.prototype.onFirstDrag = function () {
        // By default, set all columns to absolute widths
        angular.forEach(this.ctrlColumns,function(column) {
          angular.element(column).css('width', angular.element(column)[0].getBoundingClientRect().width + 'px');
            //$(column).width($(column).width());
        })
    };

    ResizerModel.prototype.handleMiddleware = function (handle, column) {
        // By default, every handle controls the column it is placed in
        return column;
    };

    ResizerModel.prototype.restrict = function (newWidth) {
        return false;
    };

    ResizerModel.prototype.calculate = function (orgWidth, diffX) {
        // By default, simply add the width difference to the original
        return orgWidth + diffX;
    };

    ResizerModel.prototype.onEndDrag = function () {
        // By default, do nothing when dragging a column ends
        return;
    };

    ResizerModel.prototype.saveAttr = function (column) {
        return angular.element(column).outerWidth();
    };

    return ResizerModel;
}]);

/*angular.module("rzTable").factory("BasicResizer", ["ResizerModel", function(ResizerModel) {

    function BasicResizer(table, columns, container) {
        // Call super constructor
        ResizerModel.call(this, table, columns, container)

        // All columns are controlled in basic mode
        this.ctrlColumns = this.columns;

        this.intervene = {
            selector: interveneSelector,
            calculator: interveneCalculator,
            restrict: interveneRestrict
        }
    }

    // Inherit by prototypal inheritance
    BasicResizer.prototype = Object.create(ResizerModel.prototype);

    function interveneSelector(column) {
        return angular.element(column).next()
    }

    function interveneCalculator(orgWidth, diffX) {
        return orgWidth - diffX;
    }

    function interveneRestrict(newWidth){
        return newWidth < 25;
    }

    BasicResizer.prototype.setup = function() {
        // Hide overflow in mode fixed
        angular.element(this.container).css({
            overflowX: 'hidden'
        })

        angular.element(this.table).css({
            width: '100%'
        })
    };

    BasicResizer.prototype.handles = function() {
        // Mode fixed does not require handler on last column
        return angular.element(this.columns).not(':last')
    };

    BasicResizer.prototype.onFirstDrag = function() {
        // Replace all column's width with absolute measurements
        this.onEndDrag()
    };

    BasicResizer.prototype.onEndDrag = function () {
        // Calculates the percent width of each column
        var totWidth = angular.element(this.table).outerWidth();

        var callbacks = []

        // Calculate the width of every column
        angular.element(this.columns).each(function(index, column) {
            var colWidth = angular.element(column).outerWidth();
            var percentWidth = colWidth / totWidth * 100 + '%';
            callbacks.push(function() {
              angular.element(column).css({ width: percentWidth });
            })
        })

        // Apply the calculated width of every column
        callbacks.map(function(cb) { cb() })
    };

    BasicResizer.prototype.saveAttr = function (column) {
        return angular.element(column)[0].style.width;
    };

    // Return constructor
    return BasicResizer;

}]);

angular.module("rzTable").factory("FixedResizer", ["ResizerModel", function(ResizerModel) {

    function FixedResizer(table, columns, container) {
        // Call super constructor
        ResizerModel.call(this, table, columns, container)

        this.fixedColumn = angular.element(table).find('th').first();
        this.bound = false;
    }

    // Inherit by prototypal inheritance
    FixedResizer.prototype = Object.create(ResizerModel.prototype);

    FixedResizer.prototype.setup = function() {
        // Hide overflow in mode fixed
        angular.element(this.container).css({
            overflowX: 'hidden'
        })

        angular.element(this.table).css({
            width: '100%'
        })

        // First column is auto to compensate for 100% table width
        angular.element(this.columns).first().css({
            width: 'auto'
        });
    };

    FixedResizer.prototype.handles = function() {
        // Mode fixed does not require handler on last column
        return angular.element(this.columns).not(':last')
    };

    FixedResizer.prototype.ctrlColumns = function() {
        // In mode fixed, all but the first column should be resized
        return angular.element(this.columns).not(':first');
    };

    FixedResizer.prototype.onFirstDrag = function() {
        // Replace each column's width with absolute measurements
        angular.element(this.ctrlColumns).each(function(index, column) {
            angular.element(column).width(angular.element(column).width());
        })
    };

    FixedResizer.prototype.handleMiddleware = function (handle, column) {
        // Fixed mode handles always controll next neightbour column
        return angular.element(column).next();
    };

    FixedResizer.prototype.restrict = function (newWidth, diffX) {
        if (this.bound && this.bound < diffX) {
          this.bound = false
          return false
        } if (this.bound && this.bound > diffX) {
          return true
        } else if (this.fixedColumn.width() <= this.getMinWidth(this.fixedColumn)) {
            this.bound = diffX
            angular.element(this.fixedColumn).width(this.minWidth);
            return true;
        }
    };

    FixedResizer.prototype.onEndDrag = function () {
        this.bound = false
    };

    FixedResizer.prototype.calculate = function (orgWidth, diffX) {
        // Subtract difference - neightbour grows
        return orgWidth - diffX;
    };

    // Return constructor
    return FixedResizer;

}]);*/

angular.module("rzTable").factory("OverflowResizer", ["ResizerModel", function(ResizerModel) {

    function OverflowResizer(table, columns, container) {
        // Call super constructor
        ResizerModel.call(this, table, columns, container)
    }

    // Inherit by prototypal inheritance
    OverflowResizer.prototype = Object.create(ResizerModel.prototype);


    OverflowResizer.prototype.setup = function() {
        // Allow overflow in this mode
        angular.element(this.container).css({
            overflow: 'auto'
        });
    };

    OverflowResizer.prototype.onTableReady = function() {
        // For mode overflow, make table as small as possible
        angular.element(this.table).width(1);
    };

    // Return constructor
    return OverflowResizer;

}]);
/*
 * angular-table-resize
 * https://github.com/tympanix/angular-table-resize
 * - minimal stylesheet
 */

 table.rz-table {
   table-layout: fixed;
   border-collapse: collapse;
 }

table.rz-table th {
    position: relative;
    min-width: 25px;
}

table.rz-table th .rz-handle {
    width: 10px;
    height: 100%;
    position: absolute;
    top: 0;
    right: 0;
    cursor: ew-resize !important;
}

table.rz-table th .rz-handle.rz-handle-active {
    border-right: 1px dotted #000;
}
var sampleapp = angular.module('sampleapp', ['rzTable']);
sampleapp.controller('samplectrl', function ($scope) {
    
   $scope.resizeMode = "OverflowResizer"

    $scope.table = undefined

    $scope.items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']
});



.body {
  overflow-x: hidden;
}

* {
  box-sizing: border-box;
}

body {
  font-family: 'Nunito', sans-serif;
  overflow-y: scroll;
  padding: 0;
  margin: 0;
}

header {
  font-size: 2em;
  font-family: 'Pacifico', cursive;
  text-align: center;
  padding: .5em;
}

h1, h2, h3, h4, h5, h6 {
  margin: .2em;
}

main {
  width: 65vw;
  margin: auto;
}

.subtitle {
  text-decoration: underline;
  margin-top: -1em;
}

ul.menu {
  padding: 0;
  margin: 1em -.5em;
  display: flex;
  list-style: none;
}

ul.menu li {
  flex: 1 1 100%;
  margin: .5em;
  display: inline-block;
}

button {
  font-family: 'Nunito', sans-serif;
  outline: 0;
  cursor: pointer;
  border: none;
  padding: 1em;
  border-radius: .2em;
  width: 100%;
}

button:active {
  transform: scale(1.01);
  box-shadow: 0 .2em .2em .1em rgba(0,0,0,.05);
}

table {
  border-collapse: collapse;
}

table th {
  min-width: 10px;
  background-color: #f8f8f8;
}

table th, table td {
  text-align: left;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  padding: .75em;
  border-right: 1px solid rgba(0,0,0,0.05);
}

.rz-handle {
  background: repeating-linear-gradient(
    45deg,
    transparent,
    transparent 2px,
    rgba(0, 0, 0, 0.05) 2px,
    rgba(0, 0, 0, 0.05) 4px
  );
}