Skip to content

Commit

Permalink
added jquery wrapper and floatthead adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
mkoryak committed Jun 3, 2020
1 parent ba65339 commit 0361efc
Show file tree
Hide file tree
Showing 12 changed files with 466 additions and 38 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
.idea/
.vcs
.vcs
.DS_Store
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ It rotates your table headings so that you can fit more stuff into the table.

WORK IN PROGRESS, based on this [code](https://codepen.io/mkoryak/pen/yLNVQVE) but better ;)

## Using with floatthead

The library comes with a jquery wrapper and an adapter that lets you run it alongside floatthead.

Since this lib is not on a CDN, I am using relative import paths in this example that may not match yours.

```html
<script src="https://mkoryak.github.io/floatThead/dist/jquery.floatThead.js"></script>
<script src="/js/rotate-table-headings/jquery.rotate-table-headings.js"></script>

<!-- This import is the adapter and should be added after floatthead and rotate-table-headings. -->
<script src="/js/rotate-table-headings/jquery.rotate-table-headings.floatthead-adapter.js"></script>
```

To use with floatthead, make sure you call rotate-table-headings **first**, then floatthead:

```js
$("table.demo").rotateTableHeadings({
maxCellHeight: 200,
angle: 45,
}).floatThead({
scrollContainer: function($table) {
return $table.closest(".table-container");
},
position: "absolute"
});
```

## Sponsored by [Ctrl O](https://ctrlo.com)

Expand Down
44 changes: 21 additions & 23 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

<link type="text/css" rel="stylesheet" href="./styles.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<script src="./../dist/rotate-table-headings-global.js"></script>

<script src="https://mkoryak.github.io/floatThead/dist/jquery.floatThead.js"></script>
<script src="./../dist/jquery.rotate-table-headings.js"></script>
<script src="./../dist/jquery.rotate-table-headings.floatthead-adapter.js"></script>
<style>
/* Make our table look decent */
th {
Expand Down Expand Up @@ -438,27 +438,25 @@
</tbody>
</table>
</div>

<script>

const jq = 0;
const $table = $("#table_demo");
if(jq) {

const lastCellWidth = rotateTableCells(
$table.find("thead th"),
/* maxCellHeight= */ 250,
/* angle= */ 35,
/* For angles under 40deg you will need to increase from 10: */
/* textTruncateOffset= */ 20
);
} else {
rotateTableHeadings($table[0].querySelectorAll('thead th'),
/* maxCellHeight= */ 250,
/* angle= */ 35,
/* For angles under 40deg you will need to increase from 10: */
/* textTruncateOffset= */ 20);
<style>
thead {
background-color: white;
}
</style>
<script>
$(() => {
$("#table_demo").rotateTableHeadings({
maxCellHeight: 200,
setLastCellWidth: false,
angle: 45,
textTruncateOffset: 10,
}).floatThead({
scrollContainer: function($table) {
return $table.closest(".table-container");
},
position: "absolute"
});
});
</script>

</body>
Expand Down
21 changes: 21 additions & 0 deletions dist/jquery.rotate-table-headings.floatthead-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
(function () {
'use strict';

/**
* jQuery.floatThead adapter for rotate-table-headings.
*
* Import this script after jQuery.floatThead and jQuery.rotate-table-headings.
* Run rotate-table-headings on a table first, followed by floatThead.
*
* Its kinda hacky since it requires you to never set the default options it overrides.
*/
Object.assign(jQuery.rotateTableHeadings.defaults, {callback: function ($table, lastCellWidth) {
// Ensure there is space for the last cell to extend into.
// Do it as a fake cell to placate floatThead's needs.
$table.find('tr').append($("<th aria-hidden='true' style='min-width: " + (lastCellWidth.toFixed(2)) + "px; padding: 0; margin: 0;'></th>"));
}});
Object.assign(jQuery.floatThead.defaults, {
floatContainerClass: 'rotate-table-headings-container'
});

}());
255 changes: 255 additions & 0 deletions dist/jquery.rotate-table-headings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
(function () {
'use strict';

/**
* Inserts styles that are required by this library into the DOM.
*/
function insertStyles () {
var STYLE_ID = 'rotate-table-headings-style';

if (document.getElementById(STYLE_ID)) {
return;
}

var styleElem = document.createElement('style');
styleElem.type = 'text/css';
styleElem.id = STYLE_ID;
document.head.appendChild(styleElem);
var sheet = styleElem.sheet;

var stringify = function (selector, rules) {
return (selector + " {\n" + (Object.keys(rules)
.map(function (key) { return ((key.split(/(?=[A-Z])/).join('-').toLowerCase()) + ": " + (rules[key]) + ";"); }).join('\n')) + "\n}");
};

var add = function (selector, rules) {
sheet.insertRule(stringify(selector, rules),
sheet.cssRules.length);
};

add(".rotate-table-headings-container", {
overflowY: 'hidden',
});
add("table.rotate-table-headings", {
borderCollapse: 'collapse',
borderSpacing: '0',
});
add("table.rotate-table-headings .cell-rotate", {
verticalAlign: 'bottom',
padding: '0 !important',
textAlign: 'left',
});
add("table.rotate-table-headings .cell-rotate .cell-positioner", {
position: 'relative',
});
add("table.rotate-table-headings .cell-rotate .cell-label", {
position: 'absolute',
bottom: '0',
textAlign: 'left',
left: '100%',
transformOrigin: 'bottom left',
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
});
}

/**
* Rotates the contents of table cells by `angle` while also
* truncating their contents if it does not fit inside the max
* cell height.
*
* @param {!NodeList<Element>} tableCells NodeList of table cells: TDs
* or THs to operate on.
* @param {number} maxCellHeight Maximum height of the cell in pixels.
* Used for truncating the cell contents to fit. Use a very large
* number if you do not want to truncate cell text.
* @param {boolean=} setLastCellWidth Whether to set the last cell's
* width so that the header extends outside the table.
* @param {number=} angle Angle in degrees.
* @param {number=} textTruncateOffset Subtract this from the max width
* used to truncate cell text. It is needed because our forumula does
* not take font-size into account.
*
* @return {number} The horizontal width of the last cell's label. Since
* this cell will extend outside of the table, you will need this value
* to add padding to the table if the label goes off-screen.
*/
function rotateTableHeadings(
tableCells,
maxCellHeight,
setLastCellWidth,
angle,
textTruncateOffset
){
if ( setLastCellWidth === void 0 ) setLastCellWidth = true;
if ( angle === void 0 ) angle = 45;
if ( textTruncateOffset === void 0 ) textTruncateOffset = 10;

var solveRightTriangle = function (ref) {
var hypotenuse = ref.hypotenuse;
var height = ref.height;

var rads = (angle * Math.PI) / 180;
if (hypotenuse) {
// Solve for height.
return hypotenuse * Math.sin(rads);
} else {
var width = height / Math.tan(rads);
// Solve for hypotenuse.
return Math.sqrt(Math.pow(height, 2) + Math.pow(width, 2));
}
};

var crel = function (clazz, name) {
if ( name === void 0 ) name = 'div';

var el = document.createElement(name);
el.classList.add(clazz);
return el;
};
var empty = function (el) {
while(el.firstChild) {
el.removeChild(el.firstChild);
}
return el;
};
var findParentTable = function (el) {
while (el) {
if (el.nodeName === 'TABLE') {
return el;
} else {
el = el.parentElement;
}
}
};

// Push the cell down to line the border up with the columns.
// Equal to border-bottom-width.
var compensateForCellBorder = function (cellLabel) {
cellLabel.style.marginBottom =
"-" + (getComputedStyle(cellLabel).borderBottomWidth);
};

if(tableCells.length === 0){
return 0;
}

// This method ensures that styles are inserted only once.
insertStyles();

var table = findParentTable(tableCells[0]);
table.classList.add('rotate-table-headings');

var maxHeadingWidth =
solveRightTriangle({ height: maxCellHeight }) - textTruncateOffset;

var maxWidth = -1;
var lastCellWidth = 0;
var cellLabels = [];

for(var i = 0, list = tableCells; i < list.length; i += 1){
var cell = list[i];

cell.style.whiteSpace = 'nowrap';
var hypotenuse = cell.offsetWidth;
var text = cell.innerText;
var height = solveRightTriangle({hypotenuse: hypotenuse});
var idealHeight = Math.min(height, maxCellHeight);
maxWidth = Math.max(hypotenuse, maxWidth);

cell.style.height = idealHeight + 'px';
cell.setAttribute('aria-label', text);
var cellLabel = crel('cell-label');
cellLabel.innerText = text;
cellLabel.setAttribute('label', text);
cellLabel.style.transform = "rotate(" + (360 - angle) + "deg)";
cellLabel.style.maxWidth = maxHeadingWidth + 'px';
var positioner = crel('cell-positioner');
positioner.appendChild(cellLabel);
empty(cell).appendChild(positioner);
cell.classList.add('cell-rotate');
cellLabels.push(cellLabel);

if(cell === tableCells[tableCells.length - 1]) {
lastCellWidth = height / Math.tan(angle * Math.PI / 180);
}
}
if(!setLastCellWidth) {
var lastLabel = cellLabels.pop();
compensateForCellBorder(lastLabel);
}
for(var i$1 = 0, list$1 = cellLabels; i$1 < list$1.length; i$1 += 1){
var cellLabel$1 = list$1[i$1];

cellLabel$1.style.width = maxWidth + 'px';
compensateForCellBorder(cellLabel$1);
}
return lastCellWidth - textTruncateOffset;
}

/**
* JQuery wrapper for rotateTableHeadings.
* You may run this plugin on a table wrapped set or a header cell
* wrapped set. ex:
* $('table.rotated').rotateTableHeadings();
* $('table tr.rotated > th').rotateTableHeadings();
*
* The options object has:
* @param {number} maxCellHeight Maximum height of the cell in pixels.
* Used for truncating the cell contents to fit. Use a very large
* number if you do not want to truncate cell text.
* @param {boolean=} setLastCellWidth Whether to set the last cell's
* width so that the header extends outside the table.
* @param {number=} angle Angle in degrees.
* @param {number=} textTruncateOffset Subtract this from the max width
* used to truncate cell text. It is needed because our forumula does
* not take font-size into account.
*
* @param {string=} cellSelector If you run this plugin on a table, this
* selector will be used to find the table cells to rotate.
* @param {function($table: jQuery, lastCellWidth: number): jQuery} callback
* A function executed after the plugin runs giving you access to the
* table and the lastCellWidth.
*/
jQuery.fn.rotateTableHeadings = function(options) {
if ( options === void 0 ) options = {};

var opts = Object.assign(jQuery.rotateTableHeadings.defaults, options);
var $this = this;

var wrapper = function ($cells) {
$cells = $cells.filter('th,td');
var lastCellWidth = rotateTableHeadings(
$cells.toArray(),
opts.maxCellHeight,
opts.setLastCellWidth,
opts.angle,
opts.textTruncateOffset
);
opts.callback($cells.closest('table'), lastCellWidth);
};

if($this.length && $this[0].nodeName === 'TABLE') {
// Assume that this is a list of tables.
$this.each(function(){
wrapper($this.find(opts.cellSelector));
});
} else {
wrapper($this);
}
// This is required for making jQuery plugin calls chainable.
return $this;
};
jQuery.rotateTableHeadings = {
defaults: {
maxCellHeight: 5000,
setLastCellWidth: true,
angle: 45,
textTruncateOffset: 10,
cellSelector: '> thead th',
callback: function ($table, lastCellWidth) {},
}
};

}());
Loading

0 comments on commit 0361efc

Please sign in to comment.