Skip to content

Feature/code factoring #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,807 changes: 1,354 additions & 2,453 deletions package-lock.json

Large diffs are not rendered by default.

124 changes: 78 additions & 46 deletions src/components/Pagination.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,84 @@
import React, { Fragment } from 'react';
import React, { Fragment, useContext } from "react";
import { ConfigContext } from "..";

export default function InitialPagination(props) {
return (
<Fragment>
{(props.config.show_first) ? (
<li className={(props.isFirst ? "disabled " : "") + "page-item"}>
<a href='#' className="page-link" tabIndex="-1"
onClick={props.firstPage}>
{props.config.language.pagination.first}
</a>
</li>
) : null}
<li className={(props.isFirst ? "disabled " : "") + "page-item"}>
<a href='#' className="page-link" tabIndex="-1"
onClick={props.previousPage}>
{props.config.language.pagination.previous}
const InitialPagination = ({
isFirst,
firstPage,
previousPage,
is_temp_page,
temp_page_number,
page_number,
onPageBlur,
onPageChange,
nextPage,
isLast,
}) => {

const config = useContext(ConfigContext);

return (
<Fragment>
{config.show_first ? (
<li className={(isFirst ? "disabled " : "") + "page-item"}>
<a
href="#"
className="page-link"
tabIndex="-1"
onClick={firstPage}
>
{config.language.pagination.first}
</a>
</li>
<li className="page-item">
<a className="page-link">
<input style={{
border: 'none',
padding: '0',
maxWidth: '30px',
textAlign: 'center',
display: 'inline-block'
) : null}
<li className={(isFirst ? "disabled " : "") + "page-item"}>
<a
href="#"
className="page-link"
tabIndex="-1"
onClick={previousPage}
>
{config.language.pagination.previous}
</a>
</li>
<li className="page-item">
<a className="page-link">
<input
style={{
border: "none",
padding: "0",
maxWidth: "30px",
textAlign: "center",
display: "inline-block",
}}
type="text"
value={(props.is_temp_page) ? props.temp_page_number : props.page_number}
onChange={(e) => props.onPageChange(e, true)}
onBlur={props.onPageBlur}
onKeyDown={props.onPageChange}/>
type="text"
value={
is_temp_page ? temp_page_number : page_number
}
onChange={(e) => onPageChange(e, true)}
onBlur={onPageBlur}
onKeyDown={onPageChange}
/>
</a>
</li>
<li className={(isLast ? "disabled " : "") + "page-item"}>
<a href="#" className="page-link" onClick={nextPage}>
{config.language.pagination.next}
</a>
</li>
{config.show_last ? (
<li className={(isLast ? "disabled " : "") + "page-item"}>
<a
href="#"
className="page-link"
tabIndex="-1"
onClick={lastPage}
>
{config.language.pagination.last}
</a>
</li>
<li className={(props.isLast ? "disabled " : "") + "page-item"}>
<a href='#' className="page-link"
onClick={props.nextPage}>
{props.config.language.pagination.next}
</a>
</li>
{(props.config.show_last) ? (
<li className={(props.isLast ? "disabled " : "") + "page-item"}>
<a href='#' className="page-link" tabIndex="-1"
onClick={props.lastPage}>
{props.config.language.pagination.last}
</a>
</li>
) : null}
</Fragment>
)
}
) : null}
</Fragment>
);
};

export default InitialPagination
125 changes: 73 additions & 52 deletions src/components/TableFooter.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,74 @@
import React from 'react';
import Pagination from './Pagination';
import ADPagination from './ADPagination';
import React, { useContext } from "react";
import Pagination from "./Pagination";
import ADPagination from "./ADPagination";

export default function TableFooter(props){
if(props.config.show_info==true || props.config.show_pagination==true){
return (
<div className="row table-foot asrt-table-foot" id={(props.id) ? props.id + "-table-foot" : ""}>
<div className="col-md-6">
{(props.config.show_info) ? props.paginationInfo : null}
</div>
<div className="col-md-6 pull-right text-right">
{(props.config.show_pagination) ? (
<nav aria-label="Page navigation" className="pull-right">
<ul className="pagination justify-content-end asrt-pagination">
{props.config.pagination == "basic" ? (
<Pagination
config={props.config}
isFirst={props.isFirst}
isLast={props.isLast}
pages={props.pages}
page_number={props.page_number}
is_temp_page={props.is_temp_page}
temp_page_number={props.temp_page_number}
previousPage={props.previousPage}
firstPage={props.firstPage}
nextPage={props.nextPage}
lastPage={props.lastPage}
goToPage={props.goToPage}
onPageChange={props.onPageChange}
onPageBlur={props.onPageBlur} />
) : (
<ADPagination
language={props.config.language}
isFirst={props.isFirst}
isLast={props.isLast}
pages={props.pages}
page_number={props.page_number}
previousPage={props.previousPage}
nextPage={props.nextPage}
goToPage={props.goToPage}/>
)
}
</ul>
</nav>
) : null}
</div>
</div>
);
} else {
return null;
}
}
const TableFooter = ({
id,
isFirst,
isLast,
paginationInfo,
pages,
page_number,
is_temp_page,
temp_page_number,
previousPage,
firstPage,
nextPage,
lastPage,
goToPage,
onPageChange,
onPageBlur,
}) => {

const config = useContext(ConfigContext);

config.show_info && config.show_pagination ? (
<div
className="row table-foot asrt-table-foot"
id={id ? id + "-table-foot" : ""}
>
<div className="col-md-6">
{config.show_info ? paginationInfo : null}
</div>
<div className="col-md-6 pull-right text-right">
{config.show_pagination ? (
<nav aria-label="Page navigation" className="pull-right">
<ul className="pagination justify-content-end asrt-pagination">
{config.pagination == "basic" ? (
<Pagination
config={config}
isFirst={isFirst}
isLast={isLast}
pages={pages}
page_number={page_number}
is_temp_page={is_temp_page}
temp_page_number={temp_page_number}
previousPage={previousPage}
firstPage={firstPage}
nextPage={nextPage}
lastPage={lastPage}
goToPage={goToPage}
onPageChange={onPageChange}
onPageBlur={onPageBlur}
/>
) : (
<ADPagination
language={config.language}
isFirst={isFirst}
isLast={isLast}
pages={pages}
page_number={page_number}
previousPage={previousPage}
nextPage={nextPage}
goToPage={goToPage}
/>
)}
</ul>
</nav>
) : null}
</div>
</div>
) : null;
};

export default TableFooter;
245 changes: 139 additions & 106 deletions src/components/TableHeader.js
Original file line number Diff line number Diff line change
@@ -1,111 +1,144 @@
import React from 'react';
import includes from 'lodash/includes';
import style from '../style';
import React, { useContext } from "react";
import includes from "lodash/includes";
import style from "../style";

export default function TableHeader(props){
if(props.config.show_length_menu == true
|| props.config.show_filter == true
|| props.config.button.excel == true
|| props.config.button.csv == true
|| props.config.button.print == true){
return (
<div className="row table-head asrt-table-head" id={(props.id) ? props.id + "-table-head" : ""}>
<div className="col-md-6">
{(props.config.show_length_menu) ? (
<div className="input-group asrt-page-length">
<div className="input-group-addon input-group-prepend">
<span className="input-group-text" style={style.table_size}>
{(props.lengthMenuText[0]) ? props.lengthMenuText[0] : ''}
</span>
</div>
{(includes(props.config.language.length_menu, '_MENU_')) ? (
<select type="text" className="form-control" style={style.table_size_dropdown}
onChange={props.changePageSize}>
{props.config.length_menu.map((value, key) => {
return (<option key={value}>{value}</option>);
})}
<option value={props.recordLength}>All</option>
</select>
) : null}
<div className="input-group-addon input-group-prepend">
<span className="input-group-text" style={style.table_size}>
{(props.lengthMenuText[1]) ? props.lengthMenuText[1] : ''}
</span>
</div>
</div>
) : null}
</div>
<div className="col-md-6 float-right text-right">
{(props.config.show_filter) ? (
<div className="table_filter" style={style.table_filter}>
<input
type="search"
className="form-control"
placeholder={props.config.language.filter}
onChange={props.filterRecords} />
</div>
const TableHeader = ({
id,
filterRecords,
exportToExcel,
extraButtons,
changePageSize,
recordLength,
lengthMenuText,
exportToCSV,
exportToPDF
}) => {
const config = useContext(ConfigContext);

config.show_length_menu ||
config.show_filter ||
config.button.excel ||
config.button.csv ||
config.button.print ? (
<div
className="row table-head asrt-table-head"
id={id ? id + "-table-head" : ""}
>
<div className="col-md-6">
{config.show_length_menu ? (
<div className="input-group asrt-page-length">
<div className="input-group-addon input-group-prepend">
<span className="input-group-text" style={style.table_size}>
{lengthMenuText[0] ?? ""}
</span>
</div>
{includes(config.language.length_menu, "_MENU_") ? (
<select
type="text"
className="form-control"
style={style.table_size_dropdown}
onChange={changePageSize}
>
{config.length_menu.map((value) => {
return <option key={value}>{value}</option>;
})}
<option value={recordLength}>All</option>
</select>
) : null}
<div className="table_tools" style={style.table_tool}>
{(props.config.button.excel) ? (
<button className="btn btn-primary buttons-excel"
tabIndex="0"
aria-controls="configuration_tbl"
title="Export to Excel"
style={style.table_tool_btn}
onClick={props.exportToExcel}>
<span>
<i className="fa fa-file-excel-o" aria-hidden="true"></i>
</span>
</button>
) : null}
{(props.config.button.csv) ? (
<button className="btn btn-primary buttons-csv"
tabIndex="0"
aria-controls="configuration_tbl"
title="Export to CSV"
style={style.table_tool_btn}
onClick={props.exportToCSV}>
<span>
<i className="fa fa-file-text-o" aria-hidden="true"></i>
</span>
</button>
) : null}
{(props.config.button.print) ? (
<button className="btn btn-primary buttons-pdf"
tabIndex="0"
aria-controls="configuration_tbl"
title="Export to PDF"
style={style.table_tool_btn}
onClick={props.exportToPDF}>
<span>
<i className="glyphicon glyphicon-print fa fa-print" aria-hidden="true"></i>
</span>
</button>
) : null}
{(props.config.button.extra==true) ? (
props.extraButtons.map((elem,index)=>{
elem.clickCount=0;
elem.singleClickTimer='';
return (
<button className={elem.className ? elem.className : "btn btn-primary buttons-pdf"}
tabIndex="0"
aria-controls="configuration_tbl"
title={elem.title?elem.title:"Export to PDF"}
style={style.table_tool_btn}
onClick={(event)=>{
elem.onClick(event);
}}
key={index}>
{elem.children}
</button>
)
})
) : null}
<div className="input-group-addon input-group-prepend">
<span className="input-group-text" style={style.table_size}>
{lengthMenuText[1] ?? ""}
</span>
</div>
</div>
) : null}
</div>
<div className="col-md-6 float-right text-right">
{config.show_filter ? (
<div className="table_filter" style={style.table_filter}>
<input
type="search"
className="form-control"
placeholder={config.language.filter}
onChange={filterRecords}
/>
</div>
) : null}
<div className="table_tools" style={style.table_tool}>
{config.button.excel ? (
<button
className="btn btn-primary buttons-excel"
tabIndex="0"
aria-controls="configuration_tbl"
title="Export to Excel"
style={style.table_tool_btn}
onClick={exportToExcel}
>
<span>
<i className="fa fa-file-excel-o" aria-hidden="true"></i>
</span>
</button>
) : null}
{config.button.csv ? (
<button
className="btn btn-primary buttons-csv"
tabIndex="0"
aria-controls="configuration_tbl"
title="Export to CSV"
style={style.table_tool_btn}
onClick={exportToCSV}
>
<span>
<i className="fa fa-file-text-o" aria-hidden="true"></i>
</span>
</button>
) : null}
{config.button.print ? (
<button
className="btn btn-primary buttons-pdf"
tabIndex="0"
aria-controls="configuration_tbl"
title="Export to PDF"
style={style.table_tool_btn}
onClick={exportToPDF}
>
<span>
<i
className="glyphicon glyphicon-print fa fa-print"
aria-hidden="true"
></i>
</span>
</button>
) : null}
{config.button.extra
? extraButtons.map((elem, index) => {
elem.clickCount = 0;
elem.singleClickTimer = "";
return (
<button
className={
elem.className
? elem.className
: "btn btn-primary buttons-pdf"
}
tabIndex="0"
aria-controls="configuration_tbl"
title={elem.title ? elem.title : "Export to PDF"}
style={style.table_tool_btn}
onClick={(event) => {
elem.onClick(event);
}}
key={index}
>
{elem.children}
</button>
);
})
: null}
</div>
);
} else {
return null;
}
}
</div>
</div>
) : null;
};

export default TableHeader;
31 changes: 31 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const DEFAULT_CONFIG = {
button: {
excel: false,
print: false,
csv: false,
extra : false,
},
filename: "table",
key_column: "id",
language: {
length_menu: "Show _MENU_ records per page",
filter: "Search in records...",
info: "Showing _START_ to _END_ of _TOTAL_ entries",
pagination: {
first: "First",
previous:"Previous",
next: "Next",
last: "Last"
},
no_data_text: 'No rows found',
loading_text: "Loading..."
},
length_menu: [10, 25, 50, 75, 100],
show_length_menu: true,
show_filter: true,
show_pagination: true,
show_info: true,
show_first: true,
show_last: true,
pagination: 'basic'
};
254 changes: 254 additions & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
const strip = (html) => {
const doc = new DOMParser().parseFromString(html, 'text/html');
return doc.body.textContent || "";
}

const getExportHtml = (records,columns,filter_value,sortRecords,filterData) => {
let tableHtml = "<table>";
tableHtml += "<thead>";
tableHtml += "<tr>";
for (let column of columns) {
tableHtml += "<th>" + column.text + "</th>";
}
tableHtml += "</tr>";
tableHtml += "</thead>";
tableHtml += "<tbody>";

// Filter records before export
let filterRecords = records;
if(!dynamic){
let records = sortRecords(),
filterValue = filter_value;
filterRecords = records;

if (filterValue) {
filterRecords = filterData(records);
}
}

for (let i in filterRecords) {
let record = filterRecords[i];
tableHtml += "<tr>";
for (let column of columns) {
if (column.cell && typeof column.cell === "function") {
let cellData = ReactDOMServer.renderToStaticMarkup(column.cell(record, i));
cellData = strip(cellData);
tableHtml += "<td>" + cellData + "</td>";
}else if (record[column.key]) {
tableHtml += "<td>" + record[column.key] + "</td>";
} else {
tableHtml += "<td></td>";
}
}
tableHtml += "</tr>";
}
tableHtml += "</tbody>";
tableHtml += "</table>";

return tableHtml;
}

const exportToExcel = (records,columns,config,filter_value,sortRecords,filterData) => {
let downloadLink, dataType = 'application/vnd.ms-excel';

let tableHtml = getExportHtml(records,columns,filter_value,sortRecords,filterData);

// Specify file name
let filename = config.filename ? config.filename + '.xls':'table.xls';
// Create download link element
downloadLink = document.createElement("a");
if(navigator.msSaveOrOpenBlob){
let blob = new Blob(['\ufeff', tableHtml], {
type: dataType
});
navigator.msSaveOrOpenBlob(blob, filename);
}else{
// Create a link to the file
downloadLink.href = 'data:' + dataType + ', ' + tableHtml;
// Setting the file name
downloadLink.download = filename;
//triggering the function
downloadLink.click();
}
}

const exportToPDF = (records, columns,config) => {
let tableHtml = getExportHtml(records, columns);

let style = "<style>";
style = style + "table {width: 100%;font: 17px Calibri;}";
style = style + "table, th, td {border: solid 1px #DDD; border-collapse: collapse;";
style = style + "padding: 2px 3px;text-align:left;}";
style = style + "</style>";

let win = window.open('', '_blank');
win.document.write('<html><head>');
win.document.write('<title>' + config.filename + '</title>');
win.document.write(style);
win.document.write('</head>');
win.document.write('<body>');
win.document.write('<h1>' + config.filename + '</h1>');
win.document.write(tableHtml);
win.document.write('</body></html>');
win.print();
win.close();
}

const convertToCSV = (objArray) => {
let array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
let str = '';
for (let i = 0; i < array.length; i++) {
let line = '';
for (let index in array[i]) {
if (line != '') line += ','
line += array[i][index];
}
str += line + '\r\n';
}
return str;
}

const exportToCSV = (records,columns,dynamic,sort,filter_value,callBack) => {
let headers = {};
// add columns in sheet array
for (let column of columns) {
headers[column.key] = '"' + column.text + '"';
}

// Filter records before export
let filterRecords = records;
if(!dynamic){
let records = sort(),
filterValue = filter_value;
filterRecords = records;

if (filterValue) {
filterRecords = callBack(records);
}
}

let records = [];
// add data rows in sheet array
for (let i in filterRecords) {
let record = filterRecords[i],
newRecord = {};
for (let column of columns) {
if (column.cell && typeof column.cell === "function") {
let cellData = ReactDOMServer.renderToStaticMarkup(column.cell(record, i));
cellData = strip(cellData);
newRecord[column.key] = cellData;
} else if (record[column.key]) {
let colValue = record[column.key];
colValue = (typeof colValue === "string") ? colValue.replace(/"/g, '""') : colValue;
newRecord[column.key] = '"' + colValue + '"';
} else {
newRecord[column.key] = "";
}
}
records.push(newRecord);
}
if (headers) {
records.unshift(headers);
}
// Convert Object to JSON
let jsonObject = JSON.stringify(records);
let csv = convertToCSV(jsonObject);
let exportedFilename = config.filename + '.csv' || 'export.csv';
let blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, exportedFilename);
} else {
let link = document.createElement("a");
if (link.download !== undefined) { // feature detection
// Browsers that support HTML5 download attribute
let url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", exportedFilename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
link.remove();
}
}
}


const isLast = (pages,page_number) => {
// because for empty records page_number will still be 1
if(pages === 0){
return true;
}
if (page_number == pages) {
return true
} else {
return false;
}
}

const isFirst = (page_number) => {
if (page_number === 1) {
return true;
}
return false
}

const goToPage = (e, pageNumber,page_number,callBack,onPageChange) => {
e.preventDefault();
if(page_number === pageNumber){
return;
}
let pageState = {
previous_page: page_number,
current_page: pageNumber
};
callBack({
is_temp_page: false,
page_number: pageNumber
});
onPageChange(pageState);
}

const firstPage=(e) => {
e.preventDefault();
if(isFirst()) return;
goToPage(e, 1);
}

const lastPage = (e,pages) => {
e.preventDefault();
if(isLast()) return;
goToPage(e, pages);
}

const previousPage = (e,page_number) => {
e.preventDefault();
if(isFirst()) return false;
goToPage(e, page_number - 1);
}

const nextPage = (e, page_number) => {
e.preventDefault();
if(isLast()) return;
goToPage(e, parseInt(page_number) + 1);
}

const onPageChange = (e, isInputChange = false, callBack) => {
if(isInputChange){
stateUpdate({
is_temp_page : true,
temp_page_number: e.target.value
});
} else {
if (e.key === 'Enter') {
let pageNumber = e.target.value;
goToPage(e, pageNumber);
}
}
}

const onPageBlur = (e) => {
let pageNumber = e.target.value;
goToPage(event, pageNumber);
}


745 changes: 201 additions & 544 deletions src/index.js

Large diffs are not rendered by default.