Custom plugin method won't be triggered when submitting ajax request

138 Views Asked by At

I have created a custom plugin in Craft CMS 4 that has to decode data and store it in it's corresponding entries. However, while developing I fired the method in my init function so on every pageload, it would be triggered which is not necessary. So I want to fire the method when a button in my twig template is clicked. I managed to puzzle my way through the fundamentals but for some reason won't it fire the actual function. Does anybody knows what I'doing wrong..? My code:

Edited Plugin.php

public function init(): void
{
    parent::init();

    // Render the template for my plugin.
    Event::on(
        View::class,
        View::EVENT_REGISTER_SITE_TEMPLATE_ROOTS,
        function(RegisterTemplateRootsEvent $event) {
            $event->roots['_jsonify'] = __DIR__ . '/src/templates';
        }
    );

    // Register the event that should be triggered on this url.
    Event::on(
        UrlManager::class,
        UrlManager::EVENT_REGISTER_CP_URL_RULES,
        function(RegisterUrlRulesEvent $event) {
            $event->rules['_jsonify/import/test'] = '_jsonify/import/test';
        }
    );

    Craft::$app->onInit(function() {
        $this->getJsonFile();
        $this->decodeJsonFile();
    });
}

PluginController.php

<?php
namespace plugins\jsonify\controllers;

use Craft;
use craft\web\Controller;
use craft\elements\Entry;
use yii\web\Response;


class JsonifySettingsController extends Controller
{

// Test function for test purposes
public function test(): Response
{
    dd('test');
    return $this->asJson(['message' => true]);
}

private function actionHandleJsonRead(): Response
{

    $jsonFile = $this->decodeJsonFile();

    $section = Craft::$app->sections->getSectionByHandle('test');

        foreach ($jsonFile as $key => $data) {
            $existingEntry = Entry::find()
            ->sectionId($section->id)
            ->andWhere(['title' => $data['Trial_name']])
            ->one();

            if ($existingEntry) {
                Craft::$app->getSession()->setNotice('Entry already exists with the same unique identifier. Skipping.');
                continue;
            }

            $entry = new Entry();
            $entry->sectionId = $section->id;
            $entry->title = $data['Trial_name'];
            $entry->testId = $data['testID'];
            $entry->entryName = $data['Name'];
            $entry->contractorSampleAnalysis = $data['Contractor_sample_analysis'];
            $entry->country = $data['Country'];
            $entry->crops = $data['Crops'];
            $entry->deltaYield = $data['Delta_yield'];
            $entry->lat = $data['lat'];
            $entry->location = $data['Location'];
            $entry->locationXy = $data['location_XY'];
            $entry->lon = $data['lon'];
            $entry->mapExport = $data['Map_export'];
            $entry->primaryCompany = $data['Primary_company'];
            $entry->primaryContact = $data['Primary_contact'];
            $entry->sizeHa = $data['Size_ha'];
            $entry->specsTrial = $data['Specs_trial'];
            $entry->entryStatus = $data['Status'];
            $entry->summaryResults = $data['Summary_results'];
            $entry->testType = $data['Test_type'];
            $entry->variety = $data['Variety'];
            $entry->year = $data['Year'];
            $entry->entryId = $data['ID'];
            $entry->internalSpecsTrial = $data['Internal_specs_trial'];
            $entry->prio = $data['Prio'];
            $entry->trialName = $data['Trial_name'];
            $entry->xy = $data['XY'];
            $entry->internalStatusTodo = $data['Internal_status_todo'];
            $entry->samplingAnalysis = $data['Sampling_Analysis'];
            $entry->statusObservations = $data['Status_Observations'];
            $entry->subsidyProject = $data['Subsidy_project'];
            $entry->xyRandomiser = $data['XY_randomiser'];
    
            if (Craft::$app->elements->saveElement($entry)) {
                Craft::$app->getSession()->setNotice('Entry saved.');
            } else {
                Craft::$app->getSession()->setError('Couldn’t save the entry: ' . implode(', ', $entry->getErrorSummary(true)));
                continue;
            } 

    }
    $data = ['message' => 'JSON data read successfully'];
    return $this->asJson($data);
}

}

Edited Twig template

{% extends "_layouts/cp.twig" %}
{% set title = "jsonify"|t('_jsonify') %}

{% set fullPageForm = true %}

{% block content %}

{% set folderId = 1 %}
    {% set assets = craft.assets.folderId(folderId).kind('json').all() %}
    
    {% if assets|length %}
        <ul class="testing">
            {% for asset in assets %}
                <li style="width: auto; display: flex; justify-content: space-between; align-items: center">
                    {% if asset.extension == 'json' %}
                        {{ asset.filename }}
                        <form method="post">
                            {{ csrfInput() }}  
                            {{ actionInput('jsonify/import/test') }}
                                <button
                                    style="margin-left: 30px; background: #d3d3d3; padding: 3px 12px; border-radius: 4px"
                                    class="json-button"
                                    width="120px"
                                    >
                                    Read JSON
                                </button>
                        </form>
                    {% endif %}
                </li>
            {% endfor %}
        </ul>
    {% else %}
        <p>No assets found.</p>
    {% endif %}
   {% endblock %}

Inside the button is the action to trigger the method.

At this moment the template is loaded inside the settings. I rather have my plugin in a screen which is accessible from crafts dashboard sidebar, but I have no clue how to make that happen..

1

There are 1 best solutions below

6
August Miller On

Before we dive in… you might try the first-party Feed Me plugin, which can import and synchronize data from JSON files, without any coding required.


I think you've got all the pieces you need—and then some!

  1. You don't need to configure a route to your controller—plugins automatically have their controllers exposed via action routes.

    Solution: Remove UrlManager::EVENT_REGISTER_SITE_URL_RULES event listener.

  2. As you noted, Craft::$app->onInit() is triggered on every request.

    Solution: Remove this block, and allow the plugin methods to be called from your dedicated controller!

  3. Your controller is not autoloadable. PHP classes must be named the same as their file.

    Solution: Rename the class from JsonifySettingsController to ImportController, and rename the file to ImportController.php. This file must be in the your-plugin-directory/controllers/ directory—or, in a controllers/ directory next to your primary plugin class.

  4. Only public controller methods can be routed automatically. Currently, your action method is private!

    Solution: Use public function actionHandleJsonRead() for the method signature.

  5. Your settings template includes markup that conflicts with the built-in form element. Craft wraps the output from getSettingsHtml() in a <form> that automatically POSTs values to its plugins/save-plugin-settings action.

    Solution: Use a different template. Add templates/import.twig to your plugin’s folder, and it will be automatically accessible at /admin/jsonify/import!

    Don't worry about Javascript and Ajax right now. Use the actionInput() function inside a basic HTML form, like this:

    <form method="post">
        {{ csrfInput() }}
        {{ actionInput('jsonify/import/handle-json-read') }}
    
        <button>Import JSON Data</button>
    </form>
    

    This will submit a normal HTTP request to Craft, which will route it to your controller.

    Note: You are already loading the asset in the back-end during import (with Asset::find()). There is no need to send its URL as part of the request, nor download the asset file from its URL. Instead, the browser will make a request to the back-end, which reads the file and performs the import.

  6. Setting a flash message has no effect for JSON requests—and repeatedly setting the same flash level multiple times ("notice," in this case) will only display the last message (the others are overwritten).

    Solution: Track failures by setting a flag, and use the asSuccess() method to send a response. The body of your action method might look like this:

    $failures = 0;
    
    foreach ($jsonFile as $key => $data) {
       // ... look up existing entry
    
       if ($existingEntry) {
           $failures++;
    
           continue;
       }
    
       // ... do import
    }
    
    // ...
    
    return $this->asSuccess(Craft::t('jsonify', 'Import complete, skipping {num} duplicate record(s).', ['num' => $failures]));
    

    Note the different return statement. This method automatically sets flash messages, or formats them appropriately for Ajax requests (if you want to switch back). Our message includes the number of entries that didn't import due to duplication.

  7. ImportController::actionIndex() is not currently used.

    Solution: Remove it!


This is not guaranteed to be a complete solution, but hopefully it helps clean up what you have so it's easier to tell what's going on!