Custom button execute model function? how?

Hi,
I’m playing with the new custom (panel view) buttons (since v5.0).

In my case I do have a backup button which should create a backup of something. The backup function resides in my custom plugins model.

I can’t figure out how to trigger this backup function by clicking the custom button.

Thanks in advance

My button yml:

buttons:
  status: true
  mybbakupbtn:
    icon: download
    text: Backup Roles
    request: page.backupRoles

my model (with debug die statement):

// site/models/role-manager.php

use Kirby\Cms\Page;
use Kirby\Http\Response;

class RoleManagerPage extends Page
{
    /**
     * Gathers all permissions from all urole pages and returns them
     * as a downloadable JSON file.
     */
    public function backupRoles()
    {
        // --- DEBUGGING TEST ---
        // If this method is being called, the script will stop and show this message.
        die('SUCCESS: The backupRoles() method was called.');

        $backupData = [];

        // Find all children of this page that use the 'urole' template.
        $rolePages = $this->children()->filterBy('intendedTemplate', 'urole');

        // Loop through each role page to gather its rules.
        foreach ($rolePages as $rolePage) {
            $roleId = $rolePage->slug();
            $rules = $rolePage->rules()->toStructure();

            // Prepare the data for this role, converting structure items to arrays.
            $backupData[$roleId] = $rules->map(function ($rule) {
                return [
                    'permission_name'   => $rule->permission_name()->value(),
                    'scope_type'        => $rule->scope_type()->value(),
                    'scope_target_text' => $rule->scope_target_text()->value(),
                    'scope_target_page' => $rule->scope_target_page()->yaml(), // Store as YAML for easy import
                    'allowed'           => $rule->allowed()->value(),
                ];
            })->data();
        }

        // Generate a filename with the current date.
        $filename = 'padeltime-permissions-backup-' . date('Y-m-d') . '.json';

        // Use Kirby's Response class to force a file download.
        return Response::download(json_encode($backupData, JSON_PRETTY_PRINT), $filename);
    }
}

There is no request option for view buttons. Where did you pick that up?

You could maybe use a custom dialog that auto-closes/never actually renders anything. And then make your method call in the dialog’s load callback.

Thanks Nico for the quick response.

My “AI-friend” proposed this and also a API call that didn’t work.

I will look into your proposal.

I’ll post an eventual solution.

Regards

my janitor plugin can do stuff like that. Janitor | Kirby CMS Plugins

dostuff:
  type: janitor
  label: call on model
  icon: bell
  command: "janitor:call --method nameOfMethod --data 'some data or ommit'"

Does this also work when setting up a janitor button as view button? Then that would probably be the ideal solution?

not yet as I have not figured out a way to make the current vue viewbutton code of janitor aware of the model (aka do queries). the field can get parameters a bit easier. but i am planing to add that eventually.

But it will also work if you use a route, which then calls your model method

Thanks Sonja,

Managed to get it working.

First setup:

buttons:
  status: true
  mybbakupbtn:
    icon: download
    text: Backup Roles
property.
    link: /api/backup

This partially worked but it returned a black browser screen with the json succes message while my models function was executed and the backups were present…

To clarify, my custom backup button makes a backup of some files.

Second setup:
Use js to call the route, this works but i’d rather not use this and call my models function from within my .yml file and display an informational after execution of the function. If you’ve got a better idea feel free…

code below:

in .yml

buttons:
  status: true
  mybbakupbtn:
    icon: download
    text: Backup Roles
    # THE FIX: We give the button an ID for our JavaScript to find.
    # The link is a dummy link to make it render as a button.
    id: backup-permissions-button
    link: "#"

my plugin index.php with the api registration:

    'api' => [
        
        'routes' => [
            // Route for the backup functionality
            [
                'pattern' => 'backup',
                'auth'    => false,
                'action'  => function () {
                    // Check if a user is logged into the panel before proceeding.
                    if (!kirby()->user()) {
                        return ['status' => 'error', 'message' => 'Authentication required.'];
                    }

                    $roleManagerPage = page('role-manager');
                    if ($roleManagerPage) {
                        return $roleManagerPage->backupRoles();
                    }
                    return null;
                }
            ]
        ]
    ],

in my model:

<?php
use Kirby\Cms\Page;
use Kirby\Toolkit\F; 

class RoleManagerPage extends Page
{
    public function backupRoles()
    {
        $backupData = [];

        $backupDir = kirby()->root('site') . '/backups/permissions';
        $filename = 'permissions-backup-' . date('Y-m-d-His') . '.json';
        $filePath = $backupDir . '/' . $filename;
        $json = json_encode($backupData, JSON_PRETTY_PRINT);

        try {
            Dir::make($backupDir);
            
            if (F::write($filePath, $json) === true) {
                return [
                    'status'  => 'ok',
                    'message' => 'Backup created successfully at: ' . $filePath
                ];
            } else {
                throw new Exception('The backup file could not be written.');
            }
        } catch (Exception $e) {
            return [
                'status'  => 'error',
                'message' => $e->getMessage()
            ];
        }
    }
}

my js:


async function handleBackup(event) {

  event.preventDefault();


  if (confirm("Do you really want to create a new permission backup on the server?")) {
    try {

      const response = await fetch('/api/backup');
      const result = await response.json();

      if (response.ok && result.status === 'ok') {

        panel.notification.success(result.message);

        panel.view.reload();

      } else {

        panel.notification.error(result.message || "An unknown error occurred.");
      }
    } catch (error) {
      panel.notification.error("Failed to connect to the backup API.");
    }
  }
}

document.body.addEventListener('click', function(event) {
  if (event.target.closest('#backup-permissions-button')) {
    handleBackup(event);
  }
});

the most recent version of janitor now solves this and view buttons created with janitor are aware of the current page again.