Data Sources > Template Content > JavaScript Extension
When you build a Data Source record with the Select Command builder, the Template Content attribute is filled in for you. This article explains what that default markup is, and what you can change.
The markup arrives in four parts, in this order: the script and style registrations, the table itself, the taxonomy search panel, and the JavaScript that starts the grid plugin. Most of what you will want to change is in that last part.
Script and style registrations
These sit above the table markup and load everything the grid needs.
<link href="/Modules/DataSource/DataTablesJS/Styles.css" rel="stylesheet">
[cp:scripting key='RegisterUnmanagedScriptInclude' url='/Integrations/JQuery/Plugins/dataTables/cp_DataSources_Extender.js' /]
[cp:scripting key='RegisterUnmanagedScriptInclude' url='/Scripting/ModuleResults/Export/Scripts.js' /]
[cp:scripting key='ExtTable' targetId='dataSource_TUGI' bPaginate='true' sPaginationType='two_button' bFilter='true' bSort='true' bInfo='true' bLengthChange='true' iDisplayLength='100' bJQueryUI='false' bProcessing='true' aLengthMenu='10, 25, 50, 100, 250, -1' aaSorting='0,cpsys_Aposdesccpsys_Apos' asStripeClasses='' bAutoWidth='false' sScrollY='' hasSearchableColumns='1' skippedSearchableColumns='0' /]
[cp:scripting key='DataSourceExportButton' id='ExcelXml' type='ExcelXml' imagePath='/Integrations/Centralpoint/Resources/Icons/Microsoft_Excel.png' linkText='Export to Excel Xml Format' /]
- The <link> tag registers the default styles for the UI built from the Data Source.
- The RegisterUnmanagedScriptInclude scripts register the system JavaScript plugin files. These are what extend the DataTables.js grid with the filtering, download and export behavior described below.
- The ExtTable script registers the DataTables.js source plugin itself.
- The DataSourceExportButton script outputs a button that downloads the results as an XML file which opens in Microsoft Excel.
- There is no equivalent script for the PDF export. That button is created by the extender plugin and is configured entirely through the pdf block of the JavaScript at the bottom of the Template Content.
The table
<table id="dataSource_XBMN" align="left" style="max-width: none;">
<thead>
<tr>
<th><input type="checkbox" class="select-all"></th>
<th>StartDate</th>
<th>Title</th>
<th>DataId</th>
<th>AutoNumber</th>
</tr>
</thead>
<tbody>
[cp:placeholders key='ItemContent' /]
</tbody>
</table>
- The columns shown here are reduced for the example. Yours will reflect the relational columns of the table you selected.
- The first column always holds the checkbox. That is what lets a visitor select rows to download the files referenced in those records, or to use the selected values for something else.
ItemContent is where the Item Content attribute is inserted, once per record.
The taxonomy search panel
This sits below the table and gives visitors a way to filter it by taxonomy.
<div class="cp-dtjs overlay">
<div data-filter="taxonomy">
<h2>File Types</h2>
[cp:scripting key='FormRadioButtonList' id='TaxonomyList1' selectionmode='Multiple' repeatlayout='Flow' repeatColumns='4' tablename='cpsys_Taxonomy' filter='cpsys_Taxonomy.ParentTaxonomyId = cpsys_Apos8982210e-0447-47d6-9c09-4ad5014e30c6cpsys_Apos' valuefield='SystemName' textfield='Name' /]
<hr>
<h2>Document Types</h2>
[cp:scripting key='FormRadioButtonList' id='TaxonomyList2' selectionmode='Multiple' repeatlayout='Flow' repeatColumns='4' tablename='cpsys_Taxonomy' filter='cpsys_Taxonomy.ParentTaxonomyId = cpsys_Apos10ef4b4d-f913-4447-94fd-11dc71a4b157cpsys_Apos' valuefield='SystemName' textfield='Name' /]
<hr>
<h2>Terms</h2>
[cp:scripting key='FormRadioButtonList' id='TaxonomyList3' selectionmode='Multiple' repeatlayout='Flow' repeatColumns='4' tablename='cpsys_Taxonomy' filter='cpsys_Taxonomy.ParentTaxonomyId = cpsys_Apos39421aa9-99ad-4dfe-8931-3f95ab57b74ccpsys_Apos' valuefield='SystemName' textfield='Name' /]
</div>
<button type="button" class="srch-tbl">Search Table</button>
</div>
- To finish the setup, update the FormRadioButtonList scripts with taxonomy from the site whose data you are querying.
- This requires a QueryStringTaxonomyFilter script in the SQL, through the Select Command attribute. The Select Command builder provides it by default.
- The default gives visitors three tiers of taxonomy, filtering results by File Types, Document Types and Terms. Records matching the selected items are returned.
- When it is set up correctly a clickable Other Filters element appears, which opens the search controls.
The plugin configuration
This sits at the very bottom of the Template Content and is where nearly all of your customization happens.
The pdf block below is shown with every property at its default value. The Select Command builder writes only hasExport; the rest are defaults the plugin supplies, listed here so the available settings and their values are visible in one place. The other blocks are written out in full by the builder.
<script type="text/javascript">
$('#dataSource_XBMN').cp_DataSourcesExtTable({
onInitComplete: function () { },
searchTermsLabel: "Search for:",
taxonomy: {
hasSearch: true,
queryStringVariableName: "tax",
modalTriggerHtml: '<strong>Other Filters:</strong> <img src="/Integrations/Centralpoint/Resources/Icons/Hierarchy.png" alt="filters">',
resetButton: "Remove Filters",
noneSelectedMessage: "Please select at least one taxonomy item."
},
dates: {
hasSearch: true,
from: "From:",
to: "To: ",
placeholder: "mm/dd/yyyy",
searchButton: "Search",
resetButton: "Reset"
},
pdf: {
hasExport: "[cp:scripting key='HTMLOutput' html='true' roles='private' /]" === "true",
buttonHtml: '<img src="/Integrations/Centralpoint/Resources/file_pdf.png" alt="Export to PDF" title="Export to PDF">',
orientation: "portrait",
cssClass: "",
title: "",
fileName: "",
scope: "applied",
generatedLabel: "Generated",
rowsLabel: "Rows",
ofTotalLabel: "of",
filtersLabel: "Filters",
dateRangeJoinLabel: "to"
},
download: {
columnIndex: 0,
buttonHtml: 'Download Selected',
attributes: ["FileUpload1", "FileUpload2", "FileUpload3", "FileUpload4", "FileUpload5", "Image", "Document"],
fileName: "Attachments",
selectedRowsControl: {
hasControl: true,
unfilteredButtonHtml: "View Selected Rows",
filteredButtonHtml: "Clear Selected Filter"
},
onChange: function (selectedIds) { },
onLoadSelectedIds: function() { return []; }
}
});
</script>
Top level properties
| Property |
What it does |
onInitComplete |
A callback that runs after the extender plugin has found the table and finished starting up. |
searchTermsLabel |
When the taxonomy search is used, the names of the searched items are listed above the table. This changes the bolded wording in Search for: TaxonomyName1, TaxonomyName2. |
Taxonomy properties
| Property |
What it does |
hasSearch |
Set to false and the taxonomy search does not render. If it is true and the trigger still does not appear, there are most likely no checkboxes in the taxonomy panel. The plugin will not give visitors an overlay with nothing in it. |
queryStringVariableName |
Must match the names used in the QueryStringTaxonomyFilter script in your SQL. A base name of tax expects those names to be tax1, tax2, tax3 and so on, starting at 1 and incrementing by 1. |
modalTriggerHtml |
The clickable HTML that opens the search overlay. |
resetButton |
The text inside the reset button, which appears once a visitor has filtered by taxonomy. |
noneSelectedMessage |
The message shown when a visitor searches in the overlay without selecting anything. |
Date properties
| Property |
What it does |
hasSearch |
Set to false and the date search does not render. |
from and to |
The label text for the two date inputs. |
placeholder |
The placeholder shown inside each empty date input. |
searchButton |
The text for the search button. |
resetButton |
The text for the reset button, which appears once a visitor has run a date search. |
This feature needs QueryString scripts in your SQL with name='' values of fromDate and toDate. The Select Command builder writes them for you.
PDF export properties
The PDF export button renders in the table toolbar beside the column selector and the Excel export. It produces a PDF of the rows the visitor is currently looking at: every row matching the active search, taxonomy and date filters, in the current sort order, without the columns they have hidden and without being limited to the page on screen. Checkboxes and other controls are stripped from the cells, and a column left with an empty header once its control is removed is dropped, so a checkbox column does not print as a blank one.
Above the table the export prints a band naming the report, the generation time in the visitor's own time zone, the row count against the unfiltered total, and the filters in force. It is left out where there is nothing to say, so an unfiltered export carries no filter list.
This feature is for signed-in visitors only. An anonymous visitor is refused, so a report on a public page gains nothing from turning it on. The builder handles this by hiding the button from anonymous visitors rather than offering one that fails, which is what the hasExport value is doing.
Everything on this page concerns the button the plugin provides. The handler behind it can also be posted to directly, from a Form, a custom page, or anything else that can build a table, which is covered in PDF Export Handler.
| Property |
What it does |
hasExport |
Whether the button renders, for signed-in visitors only. Defaults to false, so reports built before this feature do not gain a button when the site is upgraded. An existing report on an authenticated page opts in by adding pdf: { hasExport: true }. It has to be a real boolean; the string "true" will not turn it on. |
buttonHtml |
The inner HTML of the button. The default is the standard PDF icon, in the same shape the column selector uses, so it renders as a bare icon in the toolbar. |
orientation |
Either portrait, the default, or landscape. Anything else is treated as portrait. |
scope |
Either applied, the default, which exports the rows matching the active search, or all, which exports every row and ignores the search box while still following the current sort order. Any other value falls back to applied and logs a warning to the browser console. |
title |
The heading printed at the top of the PDF. When empty the page title is used, so set this on any report whose page title is not a good document heading. |
fileName |
The downloaded file name, without the extension. When empty the title is used. Spaces and ordinary punctuation are kept. |
cssClass |
An extra class on the export wrapper, for styling one report differently from the rest. See below. |
generatedLabel
rowsLabel
ofTotalLabel
filtersLabel
dateRangeJoinLabel |
The wording of the band above the table, defaulting to Generated, Rows, of, Filters and to. These exist for sites that need different language and most reports never set them. |
Leave the comparison around the role-gated script. The script renders true for a signed-in visitor and nothing at all for an anonymous one, so comparing its output to "true" is what turns "nothing" into a usable false. Drop the comparison and an anonymous visitor is left with hasExport: } in the configuration, which is a JavaScript syntax error that stops the whole plugin on that page. Replacing the whole expression with a literal true is the other mistake: it shows the button to anonymous visitors, who are refused when they click it.
Styling the PDF
The PDF is rendered against the site's own stylesheet, so the table arrives looking like the table on screen. The document is wrapped in a class that exists only in the export, so a Design styles record can restyle the printed version without those rules reaching the report on screen. Edits take effect on the next export.
.cp-ds-pdf-export table { font-size: 9px; }
.cp-ds-pdf-export th { background: #0a2240; color: #fff; }
.cp-ds-pdf-export .cp-ds-pdf-title { font-size: 24px; }
/* just one report, configured with cssClass: 'quarterly' */
.cp-ds-pdf-export.quarterly table { font-size: 8px; }
.cp-ds-pdf-export wraps the whole document and is the class to target for a site-wide export style.
.cp-ds-pdf-title is the heading, .cp-ds-pdf-stamp the band beneath it, and .cp-ds-pdf-meta-label and .cp-ds-pdf-meta-value the small labels and their values inside the band.
- A value in
cssClass is added to the wrapper rather than replacing it, so a site-wide rule keeps applying to a report that brings its own class. Write the narrower rule as .cp-ds-pdf-export.quarterly so it outranks the site-wide one.
Wide tables
A report with many columns does not produce a good PDF, and no setting changes that. Columns are squeezed to fit the page rather than cut off, so no data is ever lost. Past roughly a dozen columns of real content the result is cramped, and past twenty it is very hard to read. Landscape and hiding columns help, but neither moves that boundary far.
- Where a visitor needs every column of a wide report, the Excel export is the right tool. It already returns every row and every column, and a wide grid is what a spreadsheet is for.
- The PDF export is for a report somebody prints, files, or sends to a person who will read it as a document.
- The most effective thing a visitor can do is hide the columns they do not need with the column selector before exporting, since hidden columns are left out.
Download properties
| Property |
What it does |
columnIndex |
The zero-based index of the column holding the row checkbox. Set it to -1 to switch the feature off on a table with no checkbox column. The default of 0 uses the first column; move the checkbox elsewhere and this has to move with it. |
buttonHtml |
The text inside the download button. |
attributes |
The attribute system names within each selected record that hold file paths. These are the files included in the ZIP. |
fileName |
The name of the downloaded ZIP. |
selectedRowsControl.hasControl |
Whether the selected-rows filter button renders. Defaults to false. |
selectedRowsControl.unfilteredButtonHtml |
The inner HTML of that button while the table is not filtered by selected rows. |
selectedRowsControl.filteredButtonHtml |
The inner HTML of that button while the table is filtered by selected rows. |
onChange |
A callback that runs each time a checkbox changes. Commonly used to pass the DataIds to a hidden input. See below. |
onLoadSelectedIds |
A callback that runs once when the plugin starts, to set the selected DataId values. This is how selections survive a postback when the plugin is used inside a form and a visitor is held up by a validator. See below. |
The checkbox value has to be the DataId. The markup in that column should look like this:
<input type="checkbox" value="e2ec3c7f-fc71-f011-807a-0050b6c0caf2" class="cp-file-download">
Callback examples
Storing the selection into a hidden form field as it changes:
onChange: function (selectedIds) {
$('#cpsys_FormItem_cpsys_DefaultGroup_SelectedDataIds').val(selectedIds.join(', '));
}
And reading it back when the plugin starts, so the selection survives a postback:
onLoadSelectedIds: function() {
return $('#cpsys_FormItem_cpsys_DefaultGroup_SelectedDataIds')?.val()?.split(/[,\s]+/);
}
Template Content and Item Content
Understanding the difference between the two is essential to building a Data Source that works.
| Attribute |
What goes in it |
How often it renders |
| Template Content |
Wrapper markup, table headers, scripts and styles |
Once |
| Item Content |
The repeating row or card template |
Once per record |
Your Template Content must contain the ItemContent placeholder, or no rows render at all.
ItemContent
That placeholder is where the Item Content template is inserted for every record returned. Without it the Data Source appears empty even when the SQL is returning data perfectly well.
Displaying data with Column scripts
The Column script is the main way to display data inside your Item Content.
<!-- a standard field -->
[cp:scripting key='Column' name='Title' /]
<!-- a custom attribute -->
[cp:scripting key='Column' name='cpsys_Attributes:AuthorName' /]
<!-- a formatted date -->
[cp:scripting key='Column' name='CreateDate' format='MMMM d, yyyy' /]
<!-- with prefix text -->
[cp:scripting key='Column' name='cpsys_Attributes:Author' format='Written by {0}' /]
<!-- a truncated description -->
[cp:scripting key='Column' name='Description' truncate='150' truncatedtextsuffix='...' striphtml='true' /]
<!-- a status, with the stored value swapped for a readable one -->
[cp:scripting key='Column' name='Status' replacetext='1,2,3' replacewith='Active,Pending,Archived' /]
| Parameter |
What it does |
Example |
name |
The column or attribute name. Required. |
name='Title' |
format |
A .NET format string, with {0} standing for the value. |
format='By {0}' |
datatype |
Forces a data type, such as datetime or number. |
datatype='datetime' |
default |
What to show when the column is empty. |
default='N/A' |
truncate |
Maximum character length. |
truncate='100' |
striphtml |
Removes HTML tags from the value. |
striphtml='true' |
replacetext |
Comma separated values to find. |
replacetext='1,2,3' |
replacewith |
Comma separated replacements, in the same order. |
replacewith='Active,Pending,Archived' |
Custom attributes need a prefix. A field that is not in your SQL SELECT but is stored in the record's Attributes XML is reached with cpsys_Attributes: in front of its name, as in name='cpsys_Attributes:CustomField'.