Joomla 4 Tips and Tricks: Export Data
From Joomla! Documentation
Introduction[edit]
This Tip arises from a component where there is a need to export data from many different lists. To accomplish this task each export needs a Toolbar button to trigger the export and a controller to generate and export the data. A single JavaScript file is needed to facilitate the export.
A number of different approaches to data export can be adopted. In the Joomla core CMS there are examples that use a specific form for downloads separate from the standard list adminForm. Here the data list has nearly 30 filters so it is best to use the adminForm with the set filters.
HTML Export Toolbar Buttons[edit]
In the following example code there are two export buttons, one for CSV and another for HTML (ask not why!). The file is src/View/Viewname/HtmlView.php.
protected function addToolbar()
{
// Get the toolbar object instance
$toolbar = Toolbar::getInstance('toolbar');
...
$downloads = $toolbar->dropdownButton('download-group')
->text('Download') // but use Text::_('COM_MYCOMPONENT_VIEWNAME_DOWNLOAD')
->toggleSplit(false)
->icon('fa fa-file-download')
->buttonClass('btn btn-action')
->listCheck(false);
$dlchild = $downloads->getChildToolbar();
$dlchild->standardButton('download-csv')
->icon('fa fa-file-download')
->text('CSV Data') // but use Text::_('COM_MYCOMPONENT_VIEWNAME_DOWNLOAD_CSV_DATA')
->task('camps.download_csv')
->listCheck(false);
$dlchild->standardButton('download-html')
->icon('fa fa-file-download')
->text('HTML')
->task('camps.download_html')
->listCheck(false);
...
}
On reflection it may have been better to use the word Export in place of Download everywhere. Hindsight!
The JavaScript Export File[edit]
It turns out to be convenient to keep the JavaScript export code in a single file loaded only in those list views that have export functionality. The original source handles more than a dozen file exports. Just two are illustrated below. The first part intercepts the adminForm submit event and calls the export function if necessary. Otherwise it just passes the call on to the standard submit handler.
Joomla.submitbutton = task => {
if (
... more tasks
task === 'camps.download_csv' ||
task === 'camps.download_html' ||
... more tasks
) {
downloadcommon(task);
return;
}
Joomla.submitform(task);
};
async function downloadcommon(task) {
// task is a hidden field in the foot of the form
let task_id = document.getElementById("task");
task_id.value = task;
const adminform = document.getElementById('adminForm');
// Bind the FormData object and the form element
const data = new FormData( adminform );
const url = 'index.php?option=com_mycomponent';
const options = {
method: 'POST',
body: data
}
let response = await fetch(url, options);
if (!response.ok) {
throw new Error (Joomla.Text._('COM_MYCOMPONENT_JS_ERROR_STATUS') + `${response.status}`);
}
const disposition = response.headers.get('content-disposition');
let filename = '';
let ext = '';
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
ext = filename.substr(filename.lastIndexOf('.'));
}
}
let result = await response.blob();
// Create a link element, hide it, direct it towards the blob, and then 'click' it programatically
let a = document.createElement("a");
a.style = "display: none";
document.body.appendChild(a);
// Create a DOMString representing the blob and point the link element towards it
let myurl = window.URL.createObjectURL(result);
a.href = myurl;
// use the filename sent by the server to save the file
a.download = filename;
// programatically click the link to trigger the download
a.click();
// release the reference to the file by revoking the Object URL
window.URL.revokeObjectURL(myurl);
// remove the link created for download
a.remove();
// and reset the form task or else ...
task_id.value = '';
}
Controller File[edit]
The form submission is handled by a controller task. Its job is to check the form token and then select the data as a list of rows. HTTP headers are needed to tell the browser what is coming. This is also a good place to set a sensible file name. A date or date and time can be used for uniqueness. This is in src/Controller/MyviewController.php.
public function download_csv () {
Session::checkToken( 'post' ) or die( 'Invalid Token' );
$app = Factory::getApplication();
... Do whatever is necessary to get rows of data
$filename = 'Some-useful-prefix-';
$date = date('Y-m-d');
$app->setHeader('Content-Type', 'text/csv; charset=utf-8', true);
$app->setHeader('Content-disposition', 'attachment; filename="' . $filename . '-' . $date . '.csv"', true);
$app->setHeader('Cache-Control', 'no-cache', true);
$app->sendHeaders();
... send the rows
$app->close();
}