{"version":3,"file":"index.js","sources":["../../src/helpers.js","../../src/rows.js","../../src/columns.js","../../src/table.js","../../src/config.js","../../src/datatable.js"],"sourcesContent":["/**\n * Check is item is object\n * @return {Boolean}\n */\nexport const isObject = val => Object.prototype.toString.call(val) === \"[object Object]\"\n\n/**\n * Check for valid JSON string\n * @param {String} str\n * @return {Boolean|Array|Object}\n */\nexport const isJson = str => {\n let t = !1\n try {\n t = JSON.parse(str)\n } catch (e) {\n return !1\n }\n return !(null === t || (!Array.isArray(t) && !isObject(t))) && t\n}\n\n/**\n * Create DOM element node\n * @param {String} nodeName nodeName\n * @param {Object} attrs properties and attributes\n * @return {Object}\n */\nexport const createElement = (nodeName, attrs) => {\n const dom = document.createElement(nodeName)\n if (attrs && \"object\" == typeof attrs) {\n for (const attr in attrs) {\n if (\"html\" === attr) {\n dom.innerHTML = attrs[attr]\n } else {\n dom.setAttribute(attr, attrs[attr])\n }\n }\n }\n return dom\n}\n\nexport const flush = el => {\n if (el instanceof NodeList) {\n el.forEach(e => flush(e))\n } else {\n el.innerHTML = \"\"\n }\n}\n\n/**\n * Create button helper\n * @param {String} class\n * @param {Number} page\n * @param {String} text\n * @return {Object}\n */\nexport const button = (className, page, text) => createElement(\n \"li\",\n {\n class: className,\n html: `${text}`\n }\n)\n\n/**\n * Bubble sort algorithm\n */\nexport const sortItems = (a, b) => {\n let c\n let d\n if (1 === b) {\n c = 0\n d = a.length\n } else {\n if (b === -1) {\n c = a.length - 1\n d = -1\n }\n }\n for (let e = !0; e;) {\n e = !1\n for (let f = c; f != d; f += b) {\n if (a[f + b] && a[f].value > a[f + b].value) {\n const g = a[f]\n const h = a[f + b]\n const i = g\n a[f] = h\n a[f + b] = i\n e = !0\n }\n }\n }\n return a\n}\n\n/**\n * Pager truncation algorithm\n */\nexport const truncate = (a, b, c, d, ellipsis) => {\n d = d || 2\n let j\n const e = 2 * d\n let f = b - d\n let g = b + d\n const h = []\n const i = []\n if (b < 4 - d + e) {\n g = 3 + e\n } else if (b > c - (3 - d + e)) {\n f = c - (2 + e)\n }\n for (let k = 1; k <= c; k++) {\n if (1 == k || k == c || (k >= f && k <= g)) {\n const l = a[k - 1]\n l.classList.remove(\"active\")\n h.push(l)\n }\n }\n h.forEach(c => {\n const d = c.children[0].getAttribute(\"data-page\")\n if (j) {\n const e = j.children[0].getAttribute(\"data-page\")\n if (d - e == 2) i.push(a[e])\n else if (d - e != 1) {\n const f = createElement(\"li\", {\n class: \"ellipsis\",\n html: `${ellipsis}`\n })\n i.push(f)\n }\n }\n i.push(c)\n j = c\n })\n\n return i\n}\n","import {createElement} from \"./helpers\"\n/**\n * Rows API\n * @param {Object} instance DataTable instance\n * @param {Array} rows\n */\nexport class Rows {\n constructor(dt, rows) {\n this.dt = dt\n this.rows = rows\n\n return this\n }\n\n /**\n * Build a new row\n * @param {Array} row\n * @return {HTMLElement}\n */\n build(row) {\n const tr = createElement(\"tr\")\n\n let headings = this.dt.headings\n\n if (!headings.length) {\n headings = row.map(() => \"\")\n }\n\n headings.forEach((h, i) => {\n const td = createElement(\"td\")\n\n // Fixes #29\n if (!row[i] || !row[i].length) {\n row[i] = \"\"\n }\n\n td.innerHTML = row[i]\n\n td.data = row[i]\n\n tr.appendChild(td)\n })\n\n return tr\n }\n\n render(row) {\n return row\n }\n\n /**\n * Add new row\n * @param {Array} select\n */\n add(data) {\n if (Array.isArray(data)) {\n const dt = this.dt\n // Check for multiple rows\n if (Array.isArray(data[0])) {\n data.forEach(row => {\n dt.data.push(this.build(row))\n })\n } else {\n dt.data.push(this.build(data))\n }\n\n // We may have added data to an empty table\n if ( dt.data.length ) {\n dt.hasRows = true\n }\n\n\n this.update()\n\n dt.columns().rebuild()\n }\n\n }\n\n /**\n * Remove row(s)\n * @param {Array|Number} select\n * @return {Void}\n */\n remove(select) {\n const dt = this.dt\n\n if (Array.isArray(select)) {\n // Remove in reverse otherwise the indexes will be incorrect\n select.sort((a, b) => b - a)\n\n select.forEach(row => {\n dt.data.splice(row, 1)\n })\n } else if (select == \"all\") {\n dt.data = [];\n } else {\n dt.data.splice(select, 1)\n }\n\n // We may have emptied the table\n if ( !dt.data.length ) {\n dt.hasRows = false\n }\n\n this.update()\n dt.columns().rebuild()\n }\n\n /**\n * Update row indexes\n * @return {Void}\n */\n update() {\n this.dt.data.forEach((row, i) => {\n row.dataIndex = i\n })\n }\n\n /**\n * Find index of row by searching for a value in a column\n * @param {Number} columnIndex\n * @param {String} value\n * @return {Number}\n */\n findRowIndex(columnIndex, value) {\n // returns row index of first case-insensitive string match\n // inside the td innerText at specific column index\n return this.dt.data.findIndex(\n tr => tr.children[columnIndex].innerText.toLowerCase().includes(String(value).toLowerCase())\n )\n }\n\n /**\n * Find index, row, and column data by searching for a value in a column\n * @param {Number} columnIndex\n * @param {String} value\n * @return {Object}\n */\n findRow(columnIndex, value) {\n // get the row index\n const index = this.findRowIndex(columnIndex, value)\n // exit if not found\n if (index < 0) {\n return {\n index: -1,\n row: null,\n cols: []\n }\n }\n // get the row from data\n const row = this.dt.data[index]\n // return innerHTML of each td\n const cols = [...row.cells].map(r => r.innerHTML)\n // return everything\n return {\n index,\n row,\n cols\n }\n }\n\n /**\n * Update a row with new data\n * @param {Number} select\n * @param {Array} data\n * @return {Void}\n */\n updateRow(select, data) {\n const row = this.build(data)\n this.dt.data.splice(select, 1, row)\n this.update()\n this.dt.columns().rebuild()\n }\n}\n","import {sortItems} from \"./helpers\"\n\n/**\n * Columns API\n * @param {Object} instance DataTable instance\n * @param {Mixed} columns Column index or array of column indexes\n */\nexport class Columns {\n constructor(dt) {\n this.dt = dt\n return this\n }\n\n /**\n * Swap two columns\n * @return {Void}\n */\n swap(columns) {\n if (columns.length && columns.length === 2) {\n const cols = []\n\n // Get the current column indexes\n this.dt.headings.forEach((h, i) => {\n cols.push(i)\n })\n\n const x = columns[0]\n const y = columns[1]\n const b = cols[y]\n cols[y] = cols[x]\n cols[x] = b\n\n this.order(cols)\n }\n }\n\n /**\n * Reorder the columns\n * @return {Array} columns Array of ordered column indexes\n */\n order(columns) {\n let a\n let b\n let c\n let d\n let h\n let s\n let cell\n\n const temp = [\n [],\n [],\n [],\n []\n ]\n\n const dt = this.dt\n\n // Order the headings\n columns.forEach((column, x) => {\n h = dt.headings[column]\n s = h.getAttribute(\"data-sortable\") !== \"false\"\n a = h.cloneNode(true)\n a.originalCellIndex = x\n a.sortable = s\n\n temp[0].push(a)\n\n if (!dt.hiddenColumns.includes(column)) {\n b = h.cloneNode(true)\n b.originalCellIndex = x\n b.sortable = s\n\n temp[1].push(b)\n }\n })\n\n // Order the row cells\n dt.data.forEach((row, i) => {\n c = row.cloneNode(false)\n d = row.cloneNode(false)\n\n c.dataIndex = d.dataIndex = i\n\n if (row.searchIndex !== null && row.searchIndex !== undefined) {\n c.searchIndex = d.searchIndex = row.searchIndex\n }\n\n // Append the cell to the fragment in the correct order\n columns.forEach(column => {\n cell = row.cells[column].cloneNode(true)\n cell.data = row.cells[column].data\n c.appendChild(cell)\n\n if (!dt.hiddenColumns.includes(column)) {\n cell = row.cells[column].cloneNode(true)\n cell.data = row.cells[column].data\n d.appendChild(cell)\n }\n })\n\n temp[2].push(c)\n temp[3].push(d)\n })\n\n dt.headings = temp[0]\n dt.activeHeadings = temp[1]\n\n dt.data = temp[2]\n dt.activeRows = temp[3]\n\n // Update\n dt.update()\n }\n\n /**\n * Hide columns\n * @return {Void}\n */\n hide(columns) {\n if (columns.length) {\n const dt = this.dt\n\n columns.forEach(column => {\n if (!dt.hiddenColumns.includes(column)) {\n dt.hiddenColumns.push(column)\n }\n })\n\n this.rebuild()\n }\n }\n\n /**\n * Show columns\n * @return {Void}\n */\n show(columns) {\n if (columns.length) {\n let index\n const dt = this.dt\n\n columns.forEach(column => {\n index = dt.hiddenColumns.indexOf(column)\n if (index > -1) {\n dt.hiddenColumns.splice(index, 1)\n }\n })\n\n this.rebuild()\n }\n }\n\n /**\n * Check column(s) visibility\n * @return {Boolean}\n */\n visible(columns) {\n let cols\n const dt = this.dt\n\n columns = columns || dt.headings.map(th => th.originalCellIndex)\n\n if (!isNaN(columns)) {\n cols = !dt.hiddenColumns.includes(columns)\n } else if (Array.isArray(columns)) {\n cols = []\n columns.forEach(column => {\n cols.push(!dt.hiddenColumns.includes(column))\n })\n }\n\n return cols\n }\n\n /**\n * Add a new column\n * @param {Object} data\n */\n add(data) {\n let td\n const th = document.createElement(\"th\")\n\n if (!this.dt.headings.length) {\n this.dt.insert({\n headings: [data.heading],\n data: data.data.map(i => [i])\n })\n this.rebuild()\n return\n }\n\n if (!this.dt.hiddenHeader) {\n if (data.heading.nodeName) {\n th.appendChild(data.heading)\n } else {\n th.innerHTML = data.heading\n }\n } else {\n th.innerHTML = \"\"\n }\n\n this.dt.headings.push(th)\n\n this.dt.data.forEach((row, i) => {\n if (data.data[i]) {\n td = document.createElement(\"td\")\n\n if (data.data[i].nodeName) {\n td.appendChild(data.data[i])\n } else {\n td.innerHTML = data.data[i]\n }\n\n td.data = td.innerHTML\n\n if (data.render) {\n td.innerHTML = data.render.call(this, td.data, td, row)\n }\n\n row.appendChild(td)\n }\n })\n\n if (data.type) {\n th.setAttribute(\"data-type\", data.type)\n }\n if (data.format) {\n th.setAttribute(\"data-format\", data.format)\n }\n\n if (data.hasOwnProperty(\"sortable\")) {\n th.sortable = data.sortable\n th.setAttribute(\"data-sortable\", data.sortable === true ? \"true\" : \"false\")\n }\n\n this.rebuild()\n\n this.dt.renderHeader()\n }\n\n /**\n * Remove column(s)\n * @param {Array|Number} select\n * @return {Void}\n */\n remove(select) {\n if (Array.isArray(select)) {\n // Remove in reverse otherwise the indexes will be incorrect\n select.sort((a, b) => b - a)\n select.forEach(column => this.remove(column))\n } else {\n this.dt.headings.splice(select, 1)\n\n this.dt.data.forEach(row => {\n row.removeChild(row.cells[select])\n })\n }\n\n this.rebuild()\n }\n\n /**\n * Filter by column\n * @param {int} column - The column no.\n * @param {string} dir - asc or desc\n * @filter {array} filter - optional parameter with a list of strings\n * @return {void}\n */\n filter(column, dir, init, terms) {\n const dt = this.dt\n\n // Creates a internal state that manages filters if there are none\n if ( !dt.filterState ) {\n dt.filterState = {\n originalData: dt.data\n }\n }\n\n // If that column is was not filtered yet, we need to create its state\n if ( !dt.filterState[column] ) {\n\n // append a filter that selects all rows, 'resetting' the filter\n const filters = [...terms, () => true]\n\n dt.filterState[column] = (\n function() {\n let i = 0;\n return () => filters[i++ % (filters.length)]\n }()\n )\n }\n\n // Apply the filter and rebuild table\n const rowFilter = dt.filterState[column]() // fetches next filter\n const filteredRows = Array.from(dt.filterState.originalData).filter(tr => {\n const cell = tr.cells[column]\n const content = cell.hasAttribute(\"data-content\") ? cell.getAttribute(\"data-content\") : cell.innerText\n\n // If the filter is a function, call it, if it is a string, compare it\n return (typeof rowFilter) === \"function\" ? rowFilter(content) : content === rowFilter;\n })\n\n dt.data = filteredRows\n\n if (!dt.data.length) {\n dt.clear()\n dt.hasRows = false\n dt.setMessage(dt.options.labels.noRows)\n } else {\n this.rebuild()\n dt.update()\n }\n\n if (!init) {\n dt.emit(\"datatable.sort\", column, dir)\n }\n }\n\n /**\n * Sort by column\n * @param {int} column - The column no.\n * @param {string} dir - asc or desc\n * @return {void}\n */\n sort(column, dir, init) {\n const dt = this.dt\n\n // Check column is present\n if (dt.hasHeadings && (column < 0 || column > dt.headings.length)) {\n return false\n }\n\n //If there is a filter for this column, apply it instead of sorting\n const filterTerms = dt.options.filters &&\n dt.options.filters[dt.headings[column].textContent]\n if ( filterTerms && filterTerms.length !== 0 ) {\n this.filter(column, dir, init, filterTerms)\n return;\n }\n\n dt.sorting = true\n\n if (!init) {\n dt.emit(\"datatable.sorting\", column, dir)\n }\n\n let rows = dt.data\n const alpha = []\n const numeric = []\n let a = 0\n let n = 0\n const th = dt.headings[column]\n\n const waitFor = []\n\n // Check for date format\n if (th.getAttribute(\"data-type\") === \"date\") {\n let format = false\n const formatted = th.hasAttribute(\"data-format\")\n\n if (formatted) {\n format = th.getAttribute(\"data-format\")\n }\n waitFor.push(import(\"./date\").then(({parseDate}) => date => parseDate(date, format)))\n }\n\n Promise.all(waitFor).then(importedFunctions => {\n const parseFunction = importedFunctions[0] // only defined if date\n Array.from(rows).forEach(tr => {\n const cell = tr.cells[column]\n const content = cell.hasAttribute(\"data-content\") ? cell.getAttribute(\"data-content\") : cell.innerText\n let num\n if (parseFunction) {\n num = parseFunction(content)\n } else if (typeof content===\"string\") {\n num = content.replace(/(\\$|,|\\s|%)/g, \"\")\n } else {\n num = content\n }\n\n if (parseFloat(num) == num) {\n numeric[n++] = {\n value: Number(num),\n row: tr\n }\n } else {\n alpha[a++] = {\n value: typeof content===\"string\" ? content.toLowerCase() : content,\n row: tr\n }\n }\n })\n\n /* Sort according to direction (ascending or descending) */\n if (!dir) {\n if (th.classList.contains(\"asc\")) {\n dir = \"desc\"\n } else {\n dir = \"asc\"\n }\n }\n let top\n let btm\n if (dir == \"desc\") {\n top = sortItems(alpha, -1)\n btm = sortItems(numeric, -1)\n th.classList.remove(\"asc\")\n th.classList.add(\"desc\")\n } else {\n top = sortItems(numeric, 1)\n btm = sortItems(alpha, 1)\n th.classList.remove(\"desc\")\n th.classList.add(\"asc\")\n }\n\n /* Clear asc/desc class names from the last sorted column's th if it isn't the same as the one that was just clicked */\n if (dt.lastTh && th != dt.lastTh) {\n dt.lastTh.classList.remove(\"desc\")\n dt.lastTh.classList.remove(\"asc\")\n }\n\n dt.lastTh = th\n\n /* Reorder the table */\n rows = top.concat(btm)\n\n dt.data = []\n const indexes = []\n\n rows.forEach((v, i) => {\n dt.data.push(v.row)\n\n if (v.row.searchIndex !== null && v.row.searchIndex !== undefined) {\n indexes.push(i)\n }\n })\n\n dt.searchData = indexes\n\n this.rebuild()\n\n dt.update()\n\n if (!init) {\n dt.emit(\"datatable.sort\", column, dir)\n }\n })\n }\n\n /**\n * Rebuild the columns\n * @return {Void}\n */\n rebuild() {\n let a\n let b\n let c\n let d\n const dt = this.dt\n const temp = []\n\n dt.activeRows = []\n dt.activeHeadings = []\n\n dt.headings.forEach((th, i) => {\n th.originalCellIndex = i\n th.sortable = th.getAttribute(\"data-sortable\") !== \"false\"\n if (!dt.hiddenColumns.includes(i)) {\n dt.activeHeadings.push(th)\n }\n })\n\n // Loop over the rows and reorder the cells\n dt.data.forEach((row, i) => {\n a = row.cloneNode(false)\n b = row.cloneNode(false)\n\n a.dataIndex = b.dataIndex = i\n\n if (row.searchIndex !== null && row.searchIndex !== undefined) {\n a.searchIndex = b.searchIndex = row.searchIndex\n }\n\n // Append the cell to the fragment in the correct order\n Array.from(row.cells).forEach(cell => {\n c = cell.cloneNode(true)\n c.data = cell.data\n a.appendChild(c)\n\n if (!dt.hiddenColumns.includes(c.cellIndex)) {\n d = c.cloneNode(true)\n d.data = c.data\n b.appendChild(d)\n }\n })\n\n // Append the fragment with the ordered cells\n temp.push(a)\n dt.activeRows.push(b)\n })\n\n dt.data = temp\n\n dt.update()\n }\n}\n","import {createElement} from \"./helpers\"\n\n/**\n * Parse data to HTML table\n */\nexport const dataToTable = function (data) {\n let thead = false\n let tbody = false\n\n data = data || this.options.data\n\n if (data.headings) {\n thead = createElement(\"thead\")\n const tr = createElement(\"tr\")\n data.headings.forEach(col => {\n const td = createElement(\"th\", {\n html: col\n })\n tr.appendChild(td)\n })\n\n thead.appendChild(tr)\n }\n\n if (data.data && data.data.length) {\n tbody = createElement(\"tbody\")\n data.data.forEach(rows => {\n if (data.headings) {\n if (data.headings.length !== rows.length) {\n throw new Error(\n \"The number of rows do not match the number of headings.\"\n )\n }\n }\n const tr = createElement(\"tr\")\n rows.forEach(value => {\n const td = createElement(\"td\", {\n html: value\n })\n tr.appendChild(td)\n })\n tbody.appendChild(tr)\n })\n }\n\n if (thead) {\n if (this.dom.tHead !== null) {\n this.dom.removeChild(this.dom.tHead)\n }\n this.dom.appendChild(thead)\n }\n\n if (tbody) {\n if (this.dom.tBodies.length) {\n this.dom.removeChild(this.dom.tBodies[0])\n }\n this.dom.appendChild(tbody)\n }\n}\n","/**\n * Default configuration\n * @typ {Object}\n */\nexport const defaultConfig = {\n sortable: true,\n searchable: true,\n\n // Pagination\n paging: true,\n perPage: 10,\n perPageSelect: [5, 10, 15, 20, 25],\n nextPrev: true,\n firstLast: false,\n prevText: \"‹\",\n nextText: \"›\",\n firstText: \"«\",\n lastText: \"»\",\n ellipsisText: \"…\",\n ascText: \"▴\",\n descText: \"▾\",\n truncatePager: true,\n pagerDelta: 2,\n\n scrollY: \"\",\n\n fixedColumns: true,\n fixedHeight: false,\n\n header: true,\n hiddenHeader: false,\n footer: false,\n\n // Customise the display text\n labels: {\n placeholder: \"Search...\", // The search input placeholder\n perPage: \"{select} entries per page\", // per-page dropdown label\n noRows: \"No entries found\", // Message shown when there are no records to show\n noResults: \"No results match your search query\", // Message shown when there are no search results\n info: \"Showing {start} to {end} of {rows} entries\" //\n },\n\n // Customise the layout\n layout: {\n top: \"{select}{search}\",\n bottom: \"{info}{pager}\"\n }\n}\n","import {Rows} from \"./rows\"\nimport {Columns} from \"./columns\"\nimport {dataToTable} from \"./table\"\nimport {defaultConfig} from \"./config\"\nimport {\n isObject,\n isJson,\n createElement,\n flush,\n button,\n truncate\n} from \"./helpers\"\n\n\nexport class DataTable {\n constructor(table, options = {}) {\n\n const dom = typeof table === \"string\" ? document.querySelector(table) : table\n\n // user options\n this.options = {\n ...defaultConfig,\n ...options,\n layout: {\n ...defaultConfig.layout,\n ...options.layout\n },\n labels: {\n ...defaultConfig.labels,\n ...options.labels\n }\n }\n\n this.initialized = false\n\n this.initialLayout = dom.innerHTML\n this.initialSortable = this.options.sortable\n\n // Disable manual sorting if no header is present (#4)\n if (!this.options.header) {\n this.options.sortable = false\n }\n\n if (dom.tHead === null) {\n if (!this.options.data ||\n (this.options.data && !this.options.data.headings)\n ) {\n this.options.sortable = false\n }\n }\n\n if (dom.tBodies.length && !dom.tBodies[0].rows.length) {\n if (this.options.data) {\n if (!this.options.data.data) {\n throw new Error(\n \"You seem to be using the data option, but you've not defined any rows.\"\n )\n }\n }\n }\n\n this.dom = dom\n\n this.table = this.dom // For compatibility. Remove in version 4\n\n this.listeners = {\n onResize: event => this.onResize(event)\n }\n\n this.init()\n }\n\n /**\n * Add custom property or method to extend DataTable\n * @param {String} prop - Method name or property\n * @param {Mixed} val - Function or property value\n * @return {Void}\n */\n static extend(prop, val) {\n if (typeof val === \"function\") {\n DataTable.prototype[prop] = val\n } else {\n DataTable[prop] = val\n }\n }\n\n /**\n * Initialize the instance\n * @param {Object} options\n * @return {Void}\n */\n init(options) {\n if (this.initialized || this.dom.classList.contains(\"dataTable-table\")) {\n return false\n }\n\n Object.assign(this.options, options || {})\n\n this.currentPage = 1\n this.onFirstPage = true\n\n this.hiddenColumns = []\n this.columnRenderers = []\n this.selectedColumns = []\n\n this.render()\n\n setTimeout(() => {\n this.emit(\"datatable.init\")\n this.initialized = true\n\n if (this.options.plugins) {\n Object.entries(this.options.plugins).forEach(([plugin, options]) => {\n if (this[plugin] && typeof this[plugin] === \"function\") {\n this[plugin] = this[plugin](options, {createElement})\n\n // Init plugin\n if (options.enabled && this[plugin].init && typeof this[plugin].init === \"function\") {\n this[plugin].init()\n }\n }\n })\n }\n }, 10)\n }\n\n /**\n * Render the instance\n * @param {String} type\n * @return {Void}\n */\n render(type) {\n if (type) {\n switch (type) {\n case \"page\":\n this.renderPage()\n break\n case \"pager\":\n this.renderPager()\n break\n case \"header\":\n this.renderHeader()\n break\n }\n\n return false\n }\n\n const options = this.options\n let template = \"\"\n\n // Convert data to HTML\n if (options.data) {\n dataToTable.call(this)\n }\n\n // Store references\n this.body = this.dom.tBodies[0]\n this.head = this.dom.tHead\n this.foot = this.dom.tFoot\n\n if (!this.body) {\n this.body = createElement(\"tbody\")\n\n this.dom.appendChild(this.body)\n }\n\n this.hasRows = this.body.rows.length > 0\n\n // Make a tHead if there isn't one (fixes #8)\n if (!this.head) {\n const h = createElement(\"thead\")\n const t = createElement(\"tr\")\n\n if (this.hasRows) {\n Array.from(this.body.rows[0].cells).forEach(() => {\n t.appendChild(createElement(\"th\"))\n })\n\n h.appendChild(t)\n }\n\n this.head = h\n\n this.dom.insertBefore(this.head, this.body)\n\n this.hiddenHeader = options.hiddenHeader\n }\n\n this.headings = []\n this.hasHeadings = this.head.rows.length > 0\n\n if (this.hasHeadings) {\n this.header = this.head.rows[0]\n this.headings = [].slice.call(this.header.cells)\n }\n\n // Header\n if (!options.header) {\n if (this.head) {\n this.dom.removeChild(this.dom.tHead)\n }\n }\n\n // Footer\n if (options.footer) {\n if (this.head && !this.foot) {\n this.foot = createElement(\"tfoot\", {\n html: this.head.innerHTML\n })\n this.dom.appendChild(this.foot)\n }\n } else {\n if (this.foot) {\n this.dom.removeChild(this.dom.tFoot)\n }\n }\n\n // Build\n this.wrapper = createElement(\"div\", {\n class: \"dataTable-wrapper dataTable-loading\"\n })\n\n // Template for custom layouts\n template += \"