39

I am developing a custom component for Joomla! 3.x and want to make an AJAX call inside it to retrieve some data. What is the proper way to do it?

  • Important advice is never break the flow of Joomla. E.g. few component isten ajax request onAfterRoute event and do the task and kill the request here itself. This cause errors which are tough to debug. – Shyam Apr 23 '14 at 10:45
  • Do you mean - do not close an App? Can you elaborate more? – Dmitry Rekun Apr 23 '14 at 11:34
  • Yes, if joomla closes the app, it will be best. So your extension's extensibility will be maintained. – Shyam Apr 23 '14 at 11:57
  • Still do not understand completely. What I am talking about is $app->close() in the controller. Do you mean the same? :) – Dmitry Rekun Apr 23 '14 at 12:03
  • Yes, talking the same point. Why we should close the app in controller, while same will be done by joomla itself. – Shyam Apr 23 '14 at 14:02
47

PLEASE NOTE THAT THIS ANSWER is already a few years old and was not updated. Feel free to edit / comment if you think something is no longer exact.

Abstract

There is almost no really official way of dealing with this, it much depends on the complexity and how much you want to rely on the MVC pattern to do the work.

Below are some possible solutions what should work in Joomla 2.5 and 3.x. The code is not presented for a copy - paste job but rather as a general idea.

Prior to Joomla! 3.2 the only thing you need to use the examples below is a component. After Joomla 3.2 (for lower complex tasks) you can handle request from modules and plugins.


Generic HTML response (following legacy MVC)

Your URL for the task needs to look like this:

index.php?option=com_similar&task=abc&format=raw

You than create the controller which will use the view, let's say Abc, which will contain the file view.raw.html (identical to a normal view file).

Below you have the code for generate a raw HTML response:

/controller.php

public function abc() 
{
    // Set view

    // Joomla 2.5
    JRequest::setVar('view', 'Abc'); 

    // (use JInput in 3.x)
    $this->input->set('view', 'Abc');

    parent::display();
}

/views/abc/view.raw.php

<?php
defined('_JEXEC') or die;

jimport('joomla.application.component.view');

class SimilarViewAbc extends JViewLegacy
{
    function display($tpl = null)
    {
        parent::display($tpl);
    }
}

/views/abc/tmpl/default.php

<?php

echo "Hello World from /views/abc/tmpl/default.php";

Note: This is the solution I would use if I had to return HTML (it's cleaner and follows Joomla logic). For returning simple JSON data, see below how to put everything in the controller.

Subcontrollers

If you make your Ajax request to a subcontroller, like:

index.php?option=com_similar&controller=abc&format=raw

Than your subcontroller name (for the raw view) needs to be abc.raw.php.

This means also that you will / may have 2 subcontrollers named Abc.

If you return JSON, it may make sense to use format=json and abc.json.php. In Joomla 2.5. I had some issues getting this option to work (somehow the output was corrupted), so I used raw.


Valid JSON Response (following new / legacy MVC)

If you need to generate a valid JSON response, check out the docs page Generating JSON output

// We assume that the whatver you do was a success.
$response = array("success" => true);
// You can also return something like:
$response = array("success" => false, "error"=> "Could not find ...");

// Get the document object.
$document = JFactory::getDocument();

// Set the MIME type for JSON output.
$document->setMimeEncoding('application/json');

// Change the suggested filename.
JResponse::setHeader('Content-Disposition','attachment;filename="result.json"');

echo json_encode($response);

You would generally put this code in the controller (you will call a model which will return the data you encode - a very common scenario). If you need to take it further, you can also create a JSON view (view.json.php), similar with the raw example.


Security

Now that the Ajax request is working, don't close the page yet. Read below.

Don't forget to check for request forgeries. JSession::checkToken() come in handy here. Read the documentation on How to add CSRF anti-spoofing to forms


Multilingual sites

It may happen that if you don't send the language name in the request, Joomla won't translate the language strings you want.

Consider appending somehow the lang param to your request (like &lang=de).


Joomla! Ajax Interface

New in Joomla 3.2! - enabled you to make handle requests without building a component

Joomla! Ajax Interface - Joomla now provides a lightweight way to handle Ajax request in a plugin or module. You may want to use the Joomla! Ajax Interface if you don't have already a component or if you need to make requests from a module your already have.

  • 9
    Best quality answer I've seen on joomla.stackexchange.com so far - nicely done and way to raise the bar. Excellent job! – NivF007 Apr 23 '14 at 8:28
  • Agree, but what about JRequest? It is deprecated should it be simply $this->input since I use v3.x? – Dmitry Rekun Apr 23 '14 at 8:32
  • 1
    I addressed your concern regarding JRequest. Thanks – Valentin Despa Apr 23 '14 at 8:36
  • 3
    Nice answer, just wanted to mention that there is a Joomla class since 3.1 that handles JSON output: API, Usage – fruppel Apr 23 '14 at 8:51
  • @fl0r yeap, Valentin mentioned it in Valid JSON Response section. – Dmitry Rekun Apr 23 '14 at 8:55
20

This is a late answer for this very well answered question, but I wanted to add this cut-to-the-chase solution for those that just need a simple way to get to their components's data with an AJAX call.

With all the Joomla versions, 3rd party possibilities, and hacks that I found over several days of googling, this was the simplest approach I could come up with - and feedback is DEFINATELY appreciated.

  1. Added function execute to my existing main controller
  2. Created a subcontroller with a public function for the task(s) I wanted to call with AJAX
  3. Used the built in Joomla JResponseJson class to handle the output (it's really nice!)

URL to call/execute the task:

www.mysite.com/index.php?option=com_example&task=ForAjax.mytaskname

Modified Main Controller \com_example\controller.php

class ExampleController extends JControllerLegacy {
    public function display($cachable = false, $urlparams = false) {
        $app = JFactory::getApplication();
        $view = $app->input->getCmd('view', 'default');
        $app->input->set('view', $view);
        parent::display($cachable, $urlparams);
        return $this;
    }

    public function execute()
    {
        // Not technically needed, but a DAMN good idea.  See http://docs.joomla.org/How_to_add_CSRF_anti-spoofing_to_forms
        // JSession::checkToken();
        $task = JFactory::getApplication()->input->get('task');
        try
        {
            parent::execute($task);
        }
        catch(Exception $e)
        {
            echo new JResponseJson($e);
        }
    }
}

New Subcontroller \com_example\controllers\forajax.php

require_once JPATH_COMPONENT.'/controller.php';
class ExampleControllerForAjax extends ExampleController
{
    public function MyTaskName()
    {
        $app = JFactory::getApplication();

        $data['myRequest'] =$_REQUEST;
        $data['myFile'] =__FILE__;
        $data['myLine'] ='Line '.__LINE__;

        $app->enqueueMessage('This part was reached at line ' . __LINE__);
        $app->enqueueMessage('Then this part was reached at line ' . __LINE__);
        $app->enqueueMessage('Here was a small warning at line ' . __LINE__, 'warning');
        $app->enqueueMessage('Here was a big warning at line ' . __LINE__, 'error');

        $task_failed = false;
        echo new JResponseJson($data, 'My main response message',$task_failed);

        $app->close();
    }
}

Rendered JSON Output

{
    success: true,
    message: "My main response message",
    messages: {
        message: [
            "This part was reached at line 26",
            "Then this part was reached at line 27"
        ],
        warning: [
            "Here was a small warning at line 28"
        ],
        error: [
            "Here was a big warning at line 29"
        ]
    },
    data: {
        myRequest: {
            option: "com_example",
            task: "mytaskname",
            Itemid: null
        },
        myFile: "C:\mysite\components\com_example\controllers\forajax.php",
        myLine: "Line 24"
    }
}
11

Valentin answer is good but is a bit overly complex if all you need to do is add 1 or 2 ajax calls to a component that is already built. It's perfectly possible to get away with not making separate controller.raw.php or view.raw.php files.

To make this ajax call

index.php?format=raw&option=com_example&controller=job&task=keep_alive&tokenhash=1

In the job subcontroller

public function keep_alive() {
    $this->ajax_check();

    //Do your processing and echo out whatever you want to return to the AJAX call
    header('HTTP/1.1 202 Accepted', true, 202);
    echo 'OK';

    JFactory::getApplication()->close();
}

// Verifies jtoken and does a basic check that this is actually an AJAX call
private function ajax_check() {
    if(!JSession::checkToken('GET') || !isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') {
        header('HTTP/1.1 403 Forbidden', true, 403);
        JFactory::getApplication()->close();
    }
}
7

Valentin's answer is good.

I prefer a json controller that handles the encoding and error handling for this I created a json base class:

class itrControllerJson extends JControllerLegacy {

  /** @var array the response to the client */
  protected $response = array();

  public function addResponse($type, $message, $status=200) {

    array_push($this->response, array(
      'status' => $status,
      'type' => $type,
      'data' => $message
    ));

  }

  /**
   * Outputs the response
   * @return JControllerLegacy|void
   */
  public function display() {

    $response = array(
      'status' => 200,
      'type' => 'multiple',
      'count' => count($this->response),
      'messages' => $this->response
    );

    echo json_encode($response);
    jexit();
  }

}

This controller get extended by the controller class that do the work, something like this:

require_once __DIR__.'json.php';

class componentControllerAddress extends itrControllerJson {
  public function get() {

    try {
      if (!JSession::checkToken()) {
        throw new Exception(JText::_('JINVALID_TOKEN'), 500);
      }
      $app = JFactory::getApplication();

      $id = $app->input->get('id', null, 'uint');
      if (is_null($id)) {
        throw new Exception('Invalid Parameter', 500);
      }

      $db = JFactory::getDbo();
      $query = $db->getQuery(true);
      $query->select('*');
      $query->from('#__table');
      $query->where('id = '.$db->quote($id));
      $db->setQuery($query);
      $response = $db->loadObject();

      $this->addResponse('message', $response, 200);

    } catch (Exception $e) {
      $this->addResponse('error', $e->getMessage(), 500);
    }

    $this->display();
  }
}

and you call the request like this:

index.php?option=com_component&task=address.get&format=json&id=1234&tokenhash=1

The token hash get generated by JSession::getFormToken(). So the complete complete call could be look like this:

$link = JRoute::_('index.php?option=com_component&task=address.get&format=json&id=1234&'.JSession::getFormToken().'=1', false);

The second parameter is set to "false" so we can use this in javascript calls without xml rewrite.

  • 1
    Nice, but why not to use JResponseJson class to handle it? – Dmitry Rekun Apr 23 '14 at 8:56
  • JResponseJson was introduced in Joomla 3 – Anibal Apr 23 '14 at 21:03
  • There was no Joomla SE where I could ask ;) – Harald Leithner Apr 23 '14 at 21:30
4

If you are 100% sure there's no thrid-party plugin adding any Javascript output, a pure json_encode works Ok.

But ... for example JomSocial adds "" to the whole site.

So... a handy trick, wrap json_encode with tags, and process it on Javascript side.

echo '@START@' . json_encode(...) . '@END@';
3

You can access a controller directly by using the controller name in the task:

index.php?option=com_similar&task=controller.abc&format=raw

will call: controller.raw.php (return is raw)

index.php?option=com_similar&task=controller.abc

will call: controller.php ( return is html if you don't use die; )

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.