在组件中进行AJAX调用的正确方法是什么?


40

我正在为Joomla开发自定义组件!3.x并希望在其中进行AJAX调用以检索一些数据。正确的做法是什么?


重要的建议是永不中断Joomla的流程。例如,一些组件是ajax请求的onAfterRoute事件,然后执行任务并在此处杀死请求。这会导致难以调试的错误。
Shyam 2014年

您的意思是-不关闭应用程序吗?您能详细说明吗?
德米特里·雷昆2014年

是的,如果joomla关闭应用程序,那将是最好的选择。因此,您的扩展程序的可扩展性将得以维持。
Shyam 2014年

还是不完全理解。我在说的是控制器中的$ app-> close()。你的意思是一样的吗?:)
德米特里·雷昆2014年

是的,我说的是同一点。为什么我们应该在控制器中关闭该应用程序,而joomla本身也会这样做。
Shyam

Answers:


47

请注意,此答案已经存在数年了,尚未更新。如果您认为某些内容不再正确,请随时进行编辑/评论。

抽象

几乎没有真正官方的方式来处理此问题,它很大程度上取决于复杂性以及您想要依靠MVC模式进行工作的程度。

以下是一些可能的解决方案,它们应在Joomla 2.5和3.x中起作用。该代码不是为复制粘贴作业提供的,而是作为一般思路提供的。

在Joomla之前!3.2您需要使用以下示例的唯一内容是component。在Joomla 3.2(适用于复杂度较低的任务)之后,您可以处理来自模块和插件的请求。


通用HTML响应(遵循旧版MVC)

您的任务网址应如下所示:

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

然后,您要创建将使用该视图的控制器(假设)Abc,其中将包含文件view.raw.html(与普通视图文件相同)。

下面是生成原始HTML响应的代码:

/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";

注意:这是我必须返回HTML时将使用的解决方案(它更干净并且遵循Joomla逻辑)。要返回简单的JSON数据,请参阅下文如何将所有内容放入控制器。

子控制器

如果你让你的Ajax请求的子控制器,如:

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

(对于原始视图)的子控制器名称必须为abc.raw.php

这也意味着您将/可能有2个名为Abc的子控制器。

如果返回JSON,则可以使用format=jsonabc.json.php。在Joomla 2.5中。我在使该选项正常工作时遇到了一些问题(以某种方式破坏了输出),所以我使用了raw。


有效的JSON响应(遵循新/旧MVC)

如果您需要生成有效的JSON响应,请查看docs页面生成JSON输出

// 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);

通常,您会将这段代码放在控制器中(您将调用一个模型,该模型将返回您编码的数据-一种非常常见的情况)。如果需要进一步介绍,则还可以创建一个JSON视图(view.json.php),与原始示例类似。


安全

现在,Ajax请求正在运行,请不要关闭页面。参见下文。

不要忘记检查伪造的请求。JSession::checkToken()在这里派上用场。阅读有关如何向表单添加CSRF反欺骗的文档


多语种网站

如果您没有在请求中发送语言名称,Joomla可能不会翻译您想要的语言字符串。

考虑以某种方式将lang参数添加到您的请求中(如&lang=de)。


Joomla!Ajax介面

Joomla 3.2的新功能!-使您能够在不构建组件的情况下发出处理请求

Joomla!Ajax接口 -Joomla现在提供了一种轻巧的方式来处理插件或模块中的Ajax请求。您可能要使用Joomla!Ajax接口,如果您还没有组件,或者需要从已有的模块发出请求。


9
到目前为止,我在joomla.stackexchange.com上看到的最佳质量答案-做得很好并且提高了标准。很棒的工作!
NivF007 2014年

同意,但是呢JRequest?不推荐使用$this->input我的v3.x版本吗?
德米特里·雷坤2014年

1
我解决了您的担忧JRequest。谢谢
Valentin Despa

3
不错的答案,只是想提一下,自3.1起就有一个Joomla类可处理JSON输出:API用法
fruppel 2014年

@ fl0r是的,Valentin在Valid JSON Response节中提到了它。
德米特里·雷昆2014年

20

对于这个非常好的问题,这是一个较晚的答案,但是我想为那些只需要简单方法通过AJAX调用获取其组件数据的用户添加此切入点解决方案。

在数天的谷歌搜索中发现了所有Joomla版本,第三者的可能性以及黑客攻击之后,这是我能想到的最简单的方法-反馈得到了大家的赞赏。

  1. execute向我现有的主控制器添加了功能
  2. 为要使用AJAX调用的任务创建了具有公共功能的子控制器
  3. 使用内置的Joomla JResponseJson类来处理输出(这非常好!

调用/执行任务的URL:

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

修改后的主控制器\ 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);
        }
    }
}

新的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();
    }
}

呈现的JSON输出

{
    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的回答很好,但是如果您需要做的就是将1或2个ajax调用添加到已构建的组件中,则它会过于复杂。完全不分离controller.raw.php或分离view.raw.php文件是很可能的。

拨打这个ajax电话

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

在子job控制器中

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

瓦伦丁的答案很好。

我更喜欢一个用于处理编码和错误处理的json控制器,我创建了一个json基类:

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();
  }

}

该控制器由负责该工作的控制器类扩展,如下所示:

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();
  }
}

然后您这样调用请求:

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

通过JSession :: getFormToken()生成令牌哈希。因此,完整的完整调用可能如下所示:

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

第二个参数设置为“ false”,因此我们可以在javascript调用中使用此参数而无需重新编写xml。


1
很好,但是为什么不使用JResponseJson类来处理呢?
德米特里·雷昆2014年

JResponseJson在的Joomla 3推出
阿尼巴尔

我无法向Joomla SE提问;)
Harald Leithner 2014年

4

如果您100%确定没有第三方插件添加任何Javascript输出,则纯json_encode可以正常工作。

但是...例如,JomSocial在整个网站上添加了“”。

所以...一个方便的技巧,将json_encode与代码包装在一起,并在Javascript端对其进行处理。

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

3

您可以通过在任务中使用控制器名称直接访问控制器:

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

将会调用:controller.raw.php(返回原始值)

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

将调用:controller.php(如果不使用,则返回html die;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.