You can create you own Table view in Report and Dashboard by BellaDati Extensions feature. This tutorial describes how to create customized Table view with following specification:
Table of Contents:
Imagine situation that you have a list of PDF files somewhere on your filesystem and metadata about each PDF file stored in the DataSet, e.g.:
Country | City | Date | ... | File Path |
---|---|---|---|---|
France | Paris | 2016-01-01 | C:\Orders\FRA\Paris-2016-01-01.PDF | |
France | Paris | 2016-02-01 | C:\Orders\FRA\Paris-2016-02-01.PDF | |
... | ||||
Germany | Berlin | 2016-02-22 | D:\Rechnung\Berlin\Akte 22. Februar 2016.PDF | |
Germany | Düsseldorf | 2016-02-22 | D:\Bestellen\Düsseldorf\Akte 22. Februar 2016.PDF | |
... |
Users want to access (open/download) these files directly from BellaDati Report through their web browsers. User's second use case is to select multiple PDF files, merge them into one PDF file and download only this one PDF file. Of course, users want to filter PDF files displayed in the table by various attributes (e.g. country, city, date, etc.).
We will create Report with Filter view and Table view. Then we will create Extension that will act as customized table renderer. It will use Client API endpoints to load JSON data of existing Table view and render new table with customized CSS styles and additional features. Extension will have type "Create new page" and therefore we will embed this new page into Custom content in Report. The last step will be just hide original Table view.
You can find all source codes including prepared extension on our GitHub. |
Prepare DataSet with data. Prepare Report with Table view and Filter view. This step is not covered by this tutorial. Please see our documentation for more details.
You can prepare DataSet and Report very simply by importing prepared BellaApp. |
Please refresh extension page after each step to verify that your changes were applied correctly.
We will use basic Table Renderer extension provided by BellaDati on GitHub. It creates a new BellaDati page with table. This table contains data from source table view that is specified as parameter. We will customize this extension to bring new features.
At the end of this step please verify setup of extension. Go to Extension Gallery and click on name of imported extension. You should see new page with table that contains data from source table.
We will customize basic extension in next steps and therefore you need to get familiar with it at first.
Our extension should be parametrized to allow users to change extension behavior without changing implemented code, therefore we need to add these parameters:
Name | Type | Description | Value |
---|---|---|---|
enableResizingColumns | Boolean | Enable/disable resizing columns in table | false |
hideColumnWithPath | Boolean | Show/hide column where file path is located | false |
indexOfColumnWithPath | Integer | Index of column where file path is located | 3 |
linkGetFile | String | Link to our Client API endpoint that returns file from given absolute path | /bi/utils/api:file?path= |
linkMergePdfFiles | String | Link to our Client API endpoint that merges PDF files into one PDF file | /bi/utils/api:mergePdfFiles?paths= |
linkViewDetail | String | Link to our Client API endpoint that loads the view detail with data | /bi/report/api:viewDetail/ |
Set following code into Content of HTML body:
<div id="bd-custom-controls"> <div id="bd-custom-link"></div> <div id="bd-custom-label"></div> </div> <div id="bd-custom-table-container"></div> |
Add new CSS styles for new elements into text resource called table-styles.css:
#bd-custom-controls { margin-bottom: 0.5em; } #bd-custom-controls #bd-custom-link { display: inline; } #bd-custom-controls #bd-custom-link .disabled { pointer-events: none; cursor: default; opacity: 0.6; } #bd-custom-controls #bd-custom-link button { width: 160px; background-color: #4d94ff; border-color: white; color: white; } #bd-custom-controls #bd-custom-label { display: inline; margin-left: 1em; } |
Update code of table-renderer.js before both loops over header columns and body columns.
var theadCellIndex = 0; // insert first cell into each THEAD row var checkboxCell = theadRow.insertCell(theadCellIndex++); checkboxCell.style.width = '1em'; // iterate over all cells in a THEAD row var columns = data.header[i]; for (j = 0; j < columns.length; j++) { ... } ... var tbodyCellIndex = 0; // insert first cell with checkbox into each TBODY row var checkboxCell = tbodyRow.insertCell(tbodyCellIndex++); var checkbox = document.createElement('input'); checkbox.type = "checkbox"; checkboxCell.appendChild(checkbox); // iterate over all cells in a TBODY row var columns = data.body[i]; for (j = 0; j < columns.length; j++) { ... } |
Add following code into table-renderer.js at the end of success function that handles response from Client API:
// register events for rows in tbody and checkboxes $('.bd-custom-table tbody tr').click(function(event) { if (event.target.type !== 'checkbox') { $(':checkbox', this).trigger('click'); } }); $("input[type='checkbox']").change(function(e) { if ($(this).is(":checked")) { $(this).closest('tr').addClass("highlight_row"); refreshControls(); } else { $(this).closest('tr').removeClass("highlight_row"); refreshControls(); } }); refreshControls(); |
Create function in table-renderer.js:
function refreshControls() { // clear existing controls $('#bd-custom-link').empty(); $('#bd-custom-label').empty(); // compute count and paths var count = 0; var paths = ""; $('.bd-custom-table tr').filter(':has(:checkbox:checked)').find('td').filter('.bd-cell-with-link').each(function() { count++; paths += paths ? (";" + this.id) : this.id; }); // refresh link for PDF merge var button = document.createElement('button'); button.type = "button"; button.appendChild(document.createTextNode("Preview Drawing")); var link = document.createElement('a'); link.appendChild(button); link.href = "#PARAM=linkMergePdfFiles#" + paths; if (count == 0) { link.setAttribute('class', 'disabled'); } document.getElementById('bd-custom-link').appendChild(link); // refresh label with count var label = document.createTextNode("Selected Drawings: " + count); document.getElementById('bd-custom-label').appendChild(label); console.log(new Date().toLocaleString() + ': Selected files: ' + paths); } |
At first we need to know which column should be customized, and therefore we will add this helper function into table-renderer.js:
function isColumnWithPath(columnIndex) { return columnIndex == #PARAM=indexOfColumnWithPath#; } |
Based on result from this function we will customize column title in table header:
// set column title with appropriate header var textValue = column.value; if (isColumnWithPath(column.i)) { textValue += " (Link)"; } theadCell.appendChild(document.createTextNode(textValue)); |
Based on result from this function we will customize cell content in table body:
// fill cell with appropriate link or text if (isColumnWithPath(column.i)) { var aLink = document.createElement('a'); aLink.appendChild(document.createTextNode(escapedValue)); aLink.href = "#PARAM=linkGetFile#" + escapedValue; tbodyCell.appendChild(aLink); tbodyCell.setAttribute('id', escapedValue); tbodyCell.setAttribute('class', 'bd-cell-with-link'); } else { tbodyCell.appendChild(document.createTextNode(escapedValue)); } |
At first we need to know which column should be hidden and also recalculate colspans, and therefore we will add these helper functions into table-renderer.js:
function shouldBeColumnHidden(columnIndex) { return isColumnWithPath(columnIndex) && #PARAM=hideColumnWithPath#; } function computeColspanByHiddenColumn(columnIndex, colspan) { if (#PARAM=hideColumnWithPath#) { if (columnIndex <= #PARAM=indexOfColumnWithPath# && #PARAM=indexOfColumnWithPath# < (columnIndex + colspan)) { return colspan - 1; } else { return colspan; } } else { return colspan; } } |
Based on results from these functions we will customize rendering of table header cells:
// hide cell with link if (shouldBeColumnHidden(column.i)) { theadCell.style.display = 'none'; } // set colspan attributes if (typeof column.colspan != 'undefined') { theadCell.setAttribute('colspan', computeColspanByHiddenColumn(column.i, column.colspan)); } |
Based on results from these functions we will customize rendering of table body cells:
// hide cell with link if (shouldBeColumnHidden(column.i)) { tbodyCell.style.display = 'none'; } // set colspan attributes if (typeof column.colspan != 'undefined') { tbodyCell.setAttribute('colspan', computeColspanByHiddenColumn(column.i, column.colspan)); } |
There are various JavaScript and jQuery implementations that allows you to resize columns in HTML table by drag and drop. We will use jQuery plugin colResizable in this tutorial.
Download minified version colResizable from GitHub and upload it into extension as a new text resource with type JavaScript.
Register table in this jQuery plugin by following JavaScript code - add it into table-renderer.js at the end of success function:
// register resizable columns - see https://github.com/alvaro-prieto/colResizable if (#PARAM=enableResizingColumns#) { $(".bd-custom-table").colResizable({ fixed:false, liveDrag:true }); }; |
Go to Report and create new Custom Content view with following code (use copied URL from previous step instead of "XXX"):
<iframe src="XXX" width="100%" height="500" /> |
You can find all source codes and also download ready-made extension from our GitHub. |