- Add functions in utils.js
- Add tests for utils.js
This commit is contained in:
Faris Ansari 2017-09-24 23:11:55 +05:30
parent 329c27b343
commit 3356b75744
7 changed files with 11248 additions and 261 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
import ReGrid from './regrid.js'; import ReGrid from './regrid.js';
export { ReGrid }; export default ReGrid;

View File

@ -1,12 +1,18 @@
import { getHeader, getBody, getCell } from './utils.js'; import { getHeaderHTML, getBodyHTML, getColumnHTML, prepareRowHeader, prepareRows } from './utils.js';
import $ from 'jQuery'; import $ from 'jQuery';
import './style.scss';
export default class ReGrid { export default class ReGrid {
constructor({ wrapper, events }) { constructor({ wrapper, events, data }) {
this.wrapper = wrapper; this.wrapper = $(wrapper);
this.events = events || {}; this.events = events || {};
this.makeDom(); this.makeDom();
this.bindEvents(); this.bindEvents();
if (data) {
this.data = this.prepareData(data);
this.render();
}
} }
makeDom() { makeDom() {
@ -15,8 +21,6 @@ export default class ReGrid {
<table class="data-table-header table table-bordered"> <table class="data-table-header table table-bordered">
</table> </table>
<div class="body-scrollable"> <div class="body-scrollable">
<table class="data-table-body table table-bordered">
</table>
</div> </div>
<div class="data-table-footer"> <div class="data-table-footer">
</div> </div>
@ -28,25 +32,35 @@ export default class ReGrid {
this.header = this.wrapper.find('.data-table-header'); this.header = this.wrapper.find('.data-table-header');
this.bodyScrollable = this.wrapper.find('.body-scrollable'); this.bodyScrollable = this.wrapper.find('.body-scrollable');
this.body = this.wrapper.find('.data-table-body'); // this.body = this.wrapper.find('.data-table-body');
this.footer = this.wrapper.find('.data-table-footer'); this.footer = this.wrapper.find('.data-table-footer');
} }
render({ columns, rows }) { render() {
if (this.wrapper.find('.data-table').length === 0) { if (this.wrapper.find('.data-table').length === 0) {
this.makeDom(); this.makeDom();
this.bindEvents(); this.bindEvents();
} }
this.columns = this.prepareColumns(columns); this.renderHeader();
this.rows = this.prepareRows(rows); this.renderBody();
this.header.html(getHeader(this.columns));
this.body.html(getBody(this.rows));
this.setDimensions(); this.setDimensions();
} }
renderHeader() {
// fixed header
this.header.html(getHeaderHTML(this.data.columns));
}
renderBody() {
// scrollable body
this.bodyScrollable.html(`
<table class="data-table-body table table-bordered">
${getBodyHTML(this.data.rows)}
</table>
`);
}
updateCell(rowIndex, colIndex, value) { updateCell(rowIndex, colIndex, value) {
const row = this.getRow(rowIndex); const row = this.getRow(rowIndex);
const cell = row.find(cell => cell.col_index === colIndex); const cell = row.find(cell => cell.col_index === colIndex);
@ -55,22 +69,30 @@ export default class ReGrid {
this.refreshCell(cell); this.refreshCell(cell);
} }
refreshRows(rows) { refreshRows() {
if (rows) { this.renderBody();
this.rows = this.prepareRows(rows);
}
this.body.html(getBody(this.rows));
this.setDimensions(); this.setDimensions();
} }
refreshCell(cell) { refreshCell(cell) {
const selector = `.data-table-col[data-row-index="${cell.row_index}"][data-col-index="${cell.col_index}"]`; const selector = `.data-table-col[data-row-index="${cell.row_index}"][data-col-index="${cell.col_index}"]`;
const $cell = this.body.find(selector); const $cell = this.body.find(selector);
const $newCell = $(getCell(cell)); const $newCell = $(getColumnHTML(cell));
$cell.replaceWith($newCell); $cell.replaceWith($newCell);
} }
prepareData(data) {
const { columns, rows } = data;
const _columns = prepareRowHeader(columns);
const _rows = prepareRows(rows);
return {
columns: _columns,
rows: _rows
};
}
prepareColumns(columns) { prepareColumns(columns) {
return columns.map((col, i) => { return columns.map((col, i) => {
col.colIndex = i; col.colIndex = i;
@ -80,23 +102,13 @@ export default class ReGrid {
}); });
} }
prepareRows(rows) {
return rows.map((cells, i) => {
return cells.map((cell, j) => {
cell.colIndex = j;
cell.rowIndex = i;
return cell;
});
});
}
bindEvents() { bindEvents() {
const me = this; const self = this;
this.bodyScrollable.on('click', '.data-table-col', function () { this.bodyScrollable.on('click', '.data-table-col', function () {
const $col = $(this); const $col = $(this);
me.bodyScrollable.find('.data-table-col').removeClass('selected'); self.bodyScrollable.find('.data-table-col').removeClass('selected');
$col.addClass('selected'); $col.addClass('selected');
}); });
@ -106,20 +118,7 @@ export default class ReGrid {
} }
setDimensions() { setDimensions() {
const me = this; const self = this;
// set the width for each cell
this.header.find('.data-table-col').each(function () {
const col = $(this);
const height = col.find('.content').height();
const width = col.find('.content').width();
const colIndex = col.attr('data-col-index');
const selector = `.data-table-col[data-col-index="${colIndex}"] .content`;
const $cell = me.bodyScrollable.find(selector);
$cell.width(width);
$cell.height(height);
});
// setting width as 0 will ensure that the // setting width as 0 will ensure that the
// header doesn't take the available space // header doesn't take the available space
@ -128,8 +127,22 @@ export default class ReGrid {
margin: 0 margin: 0
}); });
// set the width for each cell
this.header.find('.data-table-col').each(function () {
const col = $(this);
const height = col.find('.content').height();
const width = col.find('.content').width();
const colIndex = col.attr('data-col-index');
const selector = `.data-table-col[data-col-index="${colIndex}"] .content`;
const $cell = self.bodyScrollable.find(selector);
$cell.width(width);
$cell.height(height);
});
this.setBodyWidth();
this.bodyScrollable.css({ this.bodyScrollable.css({
width: this.header.css('width'),
marginTop: this.header.height() + 1 marginTop: this.header.height() + 1
}); });
@ -138,10 +151,10 @@ export default class ReGrid {
bindCellDoubleClick() { bindCellDoubleClick() {
const { events } = this; const { events } = this;
const $editPopup = this.wrapper.find('.edit-popup'); const $editPopup = this.wrapper.find('.edit-popup');
$editPopup.hide(); $editPopup.hide();
if (!events.on_cell_doubleclick) return;
this.bodyScrollable.on('dblclick', '.data-table-col', function () { this.bodyScrollable.on('dblclick', '.data-table-col', function () {
const $cell = $(this); const $cell = $(this);
@ -172,13 +185,13 @@ export default class ReGrid {
} }
bindResizeColumn() { bindResizeColumn() {
const me = this; const self = this;
let isDragging = false; let isDragging = false;
let $currCell, startWidth, startX; let $currCell, startWidth, startX;
this.header.on('mousedown', '.data-table-col', function (e) { this.header.on('mousedown', '.data-table-col', function (e) {
$currCell = $(this); $currCell = $(this);
const col = me.getColumn($currCell.attr('data-col-index')); const col = self.getColumn($currCell.attr('data-col-index'));
if (col && col.resizable === false) { if (col && col.resizable === false) {
return; return;
@ -197,13 +210,13 @@ export default class ReGrid {
if ($currCell) { if ($currCell) {
const width = $currCell.find('.content').css('width'); const width = $currCell.find('.content').css('width');
me.setColumnWidth(colIndex, width); self.setColumnWidth(colIndex, width);
me.bodyScrollable.css('width', me.header.css('width')); self.setBodyWidth();
$currCell = null; $currCell = null;
} }
}); });
this.header.on('mousemove', '.data-table-col', function (e) { $('body').on('mousemove', function (e) {
if (!isDragging) return; if (!isDragging) return;
const fwidth = startWidth + (e.pageX - startX); const fwidth = startWidth + (e.pageX - startX);
@ -212,7 +225,7 @@ export default class ReGrid {
} }
bindSortColumn() { bindSortColumn() {
const me = this; const self = this;
this.header.on('click', '.data-table-col .content span', function () { this.header.on('click', '.data-table-col .content span', function () {
const $cell = $(this).closest('.data-table-col'); const $cell = $(this).closest('.data-table-col');
@ -237,21 +250,21 @@ export default class ReGrid {
const sortByAction = $cell.attr('data-sort-by'); const sortByAction = $cell.attr('data-sort-by');
if (me.events.on_sort) { if (self.events.on_sort) {
me.events.on_sort.apply(null, [colIndex, sortByAction]); self.events.on_sort.apply(null, [colIndex, sortByAction]);
} else { } else {
me.sortRows(colIndex, sortByAction); self.sortRows(colIndex, sortByAction);
me.refreshRows(); self.refreshRows();
} }
}); });
} }
sortRows(colIndex, sortBy = 'none') { sortRows(colIndex, sortBy = 'none') {
this.rows.sort((a, b) => { this.data.rows.sort((a, b) => {
const _aIndex = a[0].row_index; const _aIndex = a[0].rowIndex;
const _bIndex = b[0].row_index; const _bIndex = b[0].rowIndex;
const _a = a[colIndex].data; const _a = a[colIndex].content;
const _b = b[colIndex].data; const _b = b[colIndex].content;
if (sortBy === 'none') { if (sortBy === 'none') {
return _aIndex - _bIndex; return _aIndex - _bIndex;
@ -280,12 +293,19 @@ export default class ReGrid {
$el.css('width', width); $el.css('width', width);
} }
setBodyWidth() {
this.bodyScrollable.css(
'width',
parseInt(this.header.css('width'), 10) + 1
);
}
getColumn(colIndex) { getColumn(colIndex) {
return this.columns.find(col => col.col_index === colIndex); return this.data.columns.find(col => col.col_index === colIndex);
} }
getRow(rowIndex) { getRow(rowIndex) {
return this.rows.find(row => row[0].row_index === rowIndex); return this.data.rows.find(row => row[0].row_index === rowIndex);
} }
} }

View File

@ -1,63 +1,117 @@
import $ from 'jQuery'; function camelCaseToDash(str) {
return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
function getCell(cell) {
const customAttr = [
!isNaN(cell.colIndex) ? `data-col-index="${cell.colIndex}"` : '',
!isNaN(cell.rowIndex) ? `data-row-index="${cell.rowIndex}"` : '',
cell.isHeader ? 'data-sort-by="none"' : ''
].join(' ');
return `
<td class="data-table-col noselect" ${customAttr}>
<div class="content ellipsis">
${cell.format ? cell.format(cell.data) : cell.data}
</div>
</td>
`;
} }
function getRow(row) { function makeDataAttributeString(props) {
const header = row.isHeader ? 'data-header' : ''; const keys = Object.keys(props);
const cells = row.cells;
const dataRowIndex = !isNaN(cells[0].rowIndex) ?
`data-row-index="${cells[0].rowIndex}"` : '';
return ` return keys
<tr class="data-table-row" ${dataRowIndex} ${header}> .map((key) => {
${cells.map(getCell).join('')} const _key = camelCaseToDash(key);
</tr> const val = props[key];
`;
if (val === undefined) return '';
return `data-${_key}="${val}" `;
})
.join('')
.trim();
} }
function getHeader(columns) { function getColumnHTML(column) {
const $header = $(`<thead> const { rowIndex, colIndex, isHeader } = column;
${getRow({ cells: columns, isHeader: 1 })} const dataAttr = makeDataAttributeString({
</thead> rowIndex,
`); colIndex,
isHeader
columns.map(col => {
if (!col.width) return;
const $cellContent = $header.find(
`.data-table-col[data-col-index="${col.colIndex}"] .content`
);
$cellContent.width(col.width);
// $cell_content.css('max-width', col.width + 'px');
}); });
return `
<td class="data-table-col noselect" ${dataAttr}>
<div class="content ellipsis">
${column.format ? column.format(column.content) : column.content}
</div>
</td>
`;
}
function getRowHTML(columns, props) {
const dataAttr = makeDataAttributeString(props);
return `
<tr class="data-table-row" ${dataAttr}>
${columns.map(getColumnHTML).join('')}
</tr>
`;
}
function getHeaderHTML(columns) {
const $header = `
<thead>
${getRowHTML(columns, { isHeader: 1, rowIndex: -1 })}
</thead>
`;
// columns.map(col => {
// if (!col.width) return;
// const $cellContent = $header.find(
// `.data-table-col[data-col-index="${col.colIndex}"] .content`
// );
// $cellContent.width(col.width);
// });
return $header; return $header;
} }
function getBody(rows) { function getBodyHTML(rows) {
return `<tbody> return `
${rows.map(row => getRow({ cells: row })).join('')} <tbody>
${rows.map(row => getRowHTML(row, { rowIndex: row[0].rowIndex })).join('')}
</tbody> </tbody>
`; `;
}
function prepareColumn(col, i) {
if (typeof col === 'string') {
col = {
content: col
};
}
return Object.assign(col, {
colIndex: i
});
}
function prepareColumns(columns, props = {}) {
const _columns = columns.map(prepareColumn);
return _columns.map(col => Object.assign(col, props));
}
function prepareRowHeader(columns) {
return prepareColumns(columns, {
rowIndex: -1,
isHeader: 1,
format: (content) => `<span>${content}</span>`
});
}
function prepareRow(row, i) {
return prepareColumns(row, {
rowIndex: i
});
}
function prepareRows(rows) {
return rows.map(prepareRow);
} }
export default { export default {
getHeader, getHeaderHTML,
getBody, getBodyHTML,
getRow, getRowHTML,
getCell getColumnHTML,
prepareRowHeader,
prepareRows,
makeDataAttributeString
}; };

View File

@ -1,32 +0,0 @@
/* global describe, it, before */
import chai from 'chai';
import {Cat, Dog} from '../lib/regrid.js';
chai.expect();
const expect = chai.expect;
let lib;
describe('Given an instance of my Cat library', () => {
before(() => {
lib = new Cat();
});
describe('when I need the name', () => {
it('should return the name', () => {
expect(lib.name).to.be.equal('Cat');
});
});
});
describe('Given an instance of my Dog library', () => {
before(() => {
lib = new Dog();
});
describe('when I need the name', () => {
it('should return the name', () => {
expect(lib.name).to.be.equal('Dog');
});
});
});

24
test/utils.spec.js Normal file
View File

@ -0,0 +1,24 @@
/* global describe, it, before */
import chai from 'chai';
import {
makeDataAttributeString
} from '../src/utils.js';
chai.expect();
const expect = chai.expect;
describe('#utils', () => {
describe('makeDataAttributeString', () => {
it('should return the correct data-attr string', () => {
const props = {
isHeader: 1,
colIndex: 0,
rowIndex: 4
};
expect(makeDataAttributeString(props))
.to.be.equal('data-is-header="1" data-col-index="0" data-row-index="4"');
});
});
});