diff --git a/.jshintignore b/.jshintignore index edaa3402..08a5239d 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,4 +1,4 @@ -lib/vendor +lib/assets/vendor test/cli/sample-project test/cli/sample-project-link test/browser/support/vendor diff --git a/lib/vendor/base.css b/lib/assets/base.css similarity index 89% rename from lib/vendor/base.css rename to lib/assets/base.css index 75b0eb72..7fb88272 100644 --- a/lib/vendor/base.css +++ b/lib/assets/base.css @@ -138,19 +138,17 @@ div.coverage-summary a:visited { text-decoration: none; color: #333; } div.coverage-summary a:hover { text-decoration: underline; } div.coverage-summary tfoot td { border-top: 1px solid #666; } -div.coverage-summary .yui3-datatable-sort-indicator, div.coverage-summary .dummy-sort-indicator { +div.coverage-summary .sorter { height: 10px; width: 7px; display: inline-block; margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; } -div.coverage-summary .yui3-datatable-sort-indicator { - background: url("https://yui-s.yahooapis.com/3.6.0/build/datatable-sort/assets/skins/sam/sort-arrow-sprite.png") no-repeat scroll 0 0 transparent; -} -div.coverage-summary .yui3-datatable-sorted .yui3-datatable-sort-indicator { +div.coverage-summary .sorted .sorter { background-position: 0 -20px; } -div.coverage-summary .yui3-datatable-sorted-desc .yui3-datatable-sort-indicator { +div.coverage-summary .sorted-desc .sorter { background-position: 0 -10px; } diff --git a/lib/assets/sort-arrow-sprite.png b/lib/assets/sort-arrow-sprite.png new file mode 100644 index 00000000..03f704a6 Binary files /dev/null and b/lib/assets/sort-arrow-sprite.png differ diff --git a/lib/assets/sorter.js b/lib/assets/sorter.js new file mode 100644 index 00000000..6afb736c --- /dev/null +++ b/lib/assets/sorter.js @@ -0,0 +1,156 @@ +var addSorting = (function () { + "use strict"; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { return document.querySelector('.coverage-summary table'); } + // returns the thead element of the summary table + function getTableHeader() { return getTable().querySelector('thead tr'); } + // returns the tbody element of the summary table + function getTableBody() { return getTable().querySelector('tbody'); } + // returns the th element for nth column + function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function (a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function (a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function () { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i =0 ; i < cols.length; i += 1) { + if (cols[i].sortable) { + el = getNthColumn(i).querySelector('.sorter'); + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function () { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(cols); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/lib/vendor/prettify.css b/lib/assets/vendor/prettify.css similarity index 100% rename from lib/vendor/prettify.css rename to lib/assets/vendor/prettify.css diff --git a/lib/vendor/prettify.js b/lib/assets/vendor/prettify.js similarity index 100% rename from lib/vendor/prettify.js rename to lib/assets/vendor/prettify.js diff --git a/lib/report/html.js b/lib/report/html.js index 8c446be6..fd71e8af 100644 --- a/lib/report/html.js +++ b/lib/report/html.js @@ -371,9 +371,12 @@ Report.mix(HtmlReport, { templateData.reportClass = getReportClass(node.metrics.statements, opts.watermarks.statements); templateData.pathHtml = pathTemplate({ html: this.getPathHtml(node, linkMapper) }); templateData.base = { - js: linkMapper.asset(node, 'base.js'), css: linkMapper.asset(node, 'base.css') }; + templateData.sorter = { + js: linkMapper.asset(node, 'sorter.js'), + image: linkMapper.asset(node, 'sort-arrow-sprite.png') + }; templateData.prettify = { js: linkMapper.asset(node, 'prettify.js'), css: linkMapper.asset(node, 'prettify.css') @@ -521,23 +524,29 @@ Report.mix(HtmlReport, { summarizer = new TreeSummarizer(), writer = opts.writer || new FileWriter(sync), that = this, - tree; + tree, + copyAssets = function (subdir) { + var srcDir = path.resolve(__dirname, '..', 'assets', subdir); + fs.readdirSync(srcDir).forEach(function (f) { + var resolvedSource = path.resolve(srcDir, f), + resolvedDestination = path.resolve(dir, f), + stat = fs.statSync(resolvedSource); + + if (stat.isFile()) { + if (opts.verbose) { + console.log('Write asset: ' + resolvedDestination); + } + writer.copyFile(resolvedSource, resolvedDestination); + } + }); + }; collector.files().forEach(function (key) { summarizer.addFileCoverageSummary(key, utils.summarizeFileCoverage(collector.fileCoverageFor(key))); }); tree = summarizer.getTreeSummary(); - fs.readdirSync(path.resolve(__dirname, '..', 'vendor')).forEach(function (f) { - var resolvedSource = path.resolve(__dirname, '..', 'vendor', f), - resolvedDestination = path.resolve(dir, f), - stat = fs.statSync(resolvedSource); - - if (stat.isFile()) { - if (opts.verbose) { - console.log('Write asset: ' + resolvedDestination); - } - writer.copyFile(resolvedSource, resolvedDestination); - } + [ '.', 'vendor'].forEach(function (subdir) { + copyAssets(subdir); }); writer.on('done', function () { that.emit('done'); }); //console.log(JSON.stringify(tree.root, undefined, 4)); diff --git a/lib/report/templates/foot.txt b/lib/report/templates/foot.txt index 36fe4011..5fdaa5ab 100644 --- a/lib/report/templates/foot.txt +++ b/lib/report/templates/foot.txt @@ -5,8 +5,14 @@ {{#if prettify}} + {{/if}} - - + diff --git a/lib/report/templates/head.txt b/lib/report/templates/head.txt index d5360b25..cb355ff9 100644 --- a/lib/report/templates/head.txt +++ b/lib/report/templates/head.txt @@ -7,6 +7,11 @@ {{/if}} +
diff --git a/lib/vendor/base.js b/lib/vendor/base.js deleted file mode 100644 index 7c4646a1..00000000 --- a/lib/vendor/base.js +++ /dev/null @@ -1,88 +0,0 @@ -YUI().use('datatable', function (Y) { - - var formatters = { - pct: function (o) { - o.className += o.record.get('classes')[o.column.key]; - try { - return o.value.toFixed(2) + '%'; - } catch (ex) { return o.value + '%'; } - }, - html: function (o) { - o.className += o.record.get('classes')[o.column.key]; - return o.record.get(o.column.key + '_html'); - } - }, - defaultFormatter = function (o) { - o.className += o.record.get('classes')[o.column.key]; - return o.value; - }; - - function getColumns(theadNode) { - var colNodes = theadNode.all('tr th'), - cols = [], - col; - colNodes.each(function (colNode) { - col = { - key: colNode.getAttribute('data-col'), - label: colNode.get('innerHTML') || ' ', - sortable: !colNode.getAttribute('data-nosort'), - className: colNode.getAttribute('class'), - type: colNode.getAttribute('data-type'), - allowHTML: colNode.getAttribute('data-html') === 'true' || colNode.getAttribute('data-fmt') === 'html' - }; - col.formatter = formatters[colNode.getAttribute('data-fmt')] || defaultFormatter; - cols.push(col); - }); - return cols; - } - - function getRowData(trNode, cols) { - var tdNodes = trNode.all('td'), - i, - row = { classes: {} }, - node, - name; - for (i = 0; i < cols.length; i += 1) { - name = cols[i].key; - node = tdNodes.item(i); - row[name] = node.getAttribute('data-value') || node.get('innerHTML'); - row[name + '_html'] = node.get('innerHTML'); - row.classes[name] = node.getAttribute('class'); - //Y.log('Name: ' + name + '; Value: ' + row[name]); - if (cols[i].type === 'number') { row[name] = row[name] * 1; } - } - //Y.log(row); - return row; - } - - function getData(tbodyNode, cols) { - var data = []; - tbodyNode.all('tr').each(function (trNode) { - data.push(getRowData(trNode, cols)); - }); - return data; - } - - function replaceTable(node) { - if (!node) { return; } - var cols = getColumns(node.one('thead')), - data = getData(node.one('tbody'), cols), - table, - parent = node.get('parentNode'); - - table = new Y.DataTable({ - columns: cols, - data: data, - sortBy: 'file' - }); - parent.set('innerHTML', ''); - table.render(parent); - } - - Y.on('domready', function () { - replaceTable(Y.one('div.coverage-summary table')); - if (typeof prettyPrint === 'function') { - prettyPrint(); - } - }); -}); \ No newline at end of file