Ajax call on controller

Hi all,

I have a page /home with a simple form where to select local folders on my webserver. Once submitted I redirect the user to the next page called /copy. Here the submitted data is being used to start a local copy task from dir A to dir B based on the form selection. To display the user the percentage done on the copy task I have a controller to echo json_encode() the percentage value.

My question is how can I fetch with ajax only the controller and not the entire page copy? In a non Kirby environment I would simply put the percentage function code into a separate file and then tell JavaScript to only use that file.

I’ve read this recipe here Generating JSON with Kirby which is exactly what I would need but the problem I have is, how can I get the data being submitted from my form also be accessible by the copy.json.php file in this case?



<form method="POST" action="<?= page('copy')->url() ?>">
    <input type="checkbox" id="one" name="item[]" value="/path/to/folder/one">
    <input type="checkbox" id="two" name="item[]" value="/path/to/folder/two">
  <input type="submit" name="process" value="Submit" />



return function ($kirby, $site, $page) {

    if ($kirby->request()->is('POST') && get('process')) {

        $data = [
            'item'    => get('item'),
        $rules = [
            'item'    => ['required'],
        $messages = [
            'item'    => 'Select minimum 1 Item.',

        if ($invalid = invalid($data, $rules, $messages)) {
            $alert = $invalid;
        } else {

            try {
                // Target folder
                $targetDir = '/path/to/target/folder';

                // Returns size of the Target folder
                function targetDirSize($key) {
                    $iterator = new RecursiveIteratorIterator(
                        new RecursiveDirectoryIterator($key)
                    foreach ($iterator as $file) {
                        $totalSize += $file->getSize();
                    return $totalSize;

                // Returns size of selected folders
                function sourceDirSize($key) {
                    // Loop through src folders
                    foreach ($key as $i) {
                        $iterator = new RecursiveIteratorIterator(
                            new RecursiveDirectoryIterator($i)
                        foreach ($iterator as $dir) {
                            $totalSize += $dir->getSize();
                        $sum = $totalSize;
                    return $sum;

                // Return percentage
                function getPercentage($srcDir, $dstDir) {
                    $percentage = round(($srcDir / $dstDir) * 100);
                    return $percentage;

                // Get size of selected folders
                $srcDir = sourceDirSize($data);

                $json = array();
                $json['percent'] = getPercentage(targetDirSize($targetDir), $srcDir);
                // encode data into a json array
                echo json_encode($json);

            } catch (Exception $e) {
                $alert = ['Something went wrong here.' . $e->getMessage()];

    return [
        'alert' => $alert ?? null,
        'data'  => $data ?? false,


var percentCheck = setInterval(function() { 
        url: '/copy',
        type: 'POST',
        contentType: "application/json",
        dataType: 'json',  
        success: function(data) {
            var percent = data['percent'];
            // do more stuff here
            if (percent == 100) { // if 100% stop the interval
        error: function (request, status, error) {
    }, 1000);

You could do this in a route.

Could you point me into the right direction which way the route would go?

The way I thought to set it up:
home -> form action -> go to page copy -> ajax calls page copy.json -> page copy.json needs form data for processing

Ideal I’d prefer to have everything on one page so I don’t need to route the data through but the problem I have with the json_encode() in the controller is that it outputs also the entire html of the template.

I worked my way around it and created children pages and $kirby->session() to store my data :slight_smile: