1

I'm trying to download a CSV file from a custom created element action

Here's the performAction function

    public function performAction(ElementCriteriaModel $criteria) {
        $contents = 'row1column1,row2column2'.PHP_EOL.'row2column1,row2column2'.PHP_EOL;

        craft()->request->sendFile('export.csv', $contents, array('forceDownload' => true), false);

        return true;  
    }

No error is thrown, the code passes right through the function. When I check the craft logs, there's no sign of any error

And yet, there's no download occuring. When I try this same code in a controller or a service, it works just fine. But in this element action class, nothing happens

2

It's because those element actions happen over an AJAX and you have to jump through a few hoops to send a file to the browser over an AJAX request.

| improve this answer | |
2

Element actions take place over AJAX requests, so the performAction method will not work as you are expecting.

Here's how I solved it in Craft 3.

In the element action class, create a redirect to a controller action in the activate setting of the ElementActionTrigger:

public function getTriggerHtml()
{
    $type = Json::encode(static::class);

    $js = <<<EOD
(function()
{
var trigger = new Craft.ElementActionTrigger({
    type: {$type},
    activate: function() {
        Craft.redirectTo(Craft.getActionUrl('my-plugin/my-controller/say-hello'));
    }
});
})();
EOD;

    Craft::$app->getView()->registerJs($js);

    return null;
}

Then perform the download in the controller:

public function actionSayHello(): Response
{
    return Craft::$app->getResponse()->sendContentAsFile('Hello!', 'hello.pdf', [
        'mimeType' => 'application/pdf'
    ]);
}

You'll usually want to do something with the selected elements, which you can do by joining the selected element IDs:

public function getTriggerHtml()
{
    $type = Json::encode(static::class);

    $js = <<<EOD
(function()
{
var trigger = new Craft.ElementActionTrigger({
    type: {$type},
    activate: function(\$selectedItems) {
        Craft.redirectTo(Craft.getActionUrl('my-plugin/my-controller/say-hello', 'elementIds=' + Craft.elementIndex.getSelectedElementIds().join('|')));
    }
});
})();
EOD;

    Craft::$app->getView()->registerJs($js);

    return null;
}

You can then explode the elementIds in the controller:

public function actionSayHello(): Response
{
    $elementIds = Craft::$app->getRequest()->getRequiredParam('elementIds');
    $elementIds = explode('|', $elementIds);

    // Do something with elementIds

    return Craft::$app->getResponse()->sendContentAsFile('Hello!', 'hello.pdf', [
        'mimeType' => 'application/pdf'
    ]);
}
| improve this answer | |
  • 1
    Brilliant solution - I just implemented this in a module for my use-case and it worked a treat! – Josh Angell Apr 8 at 15:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.