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 date 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!

Data Export Buttons

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 HTML 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 cane 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();
	}

Result[edit]

Export Data Example

Parent Links[edit]

Back to J4.x:Tips and Tricks for Joomla 4 Developers