【问题标题】:How to catch AJAX requests (SilverStripe 3.4)如何捕获 AJAX 请求(SilverStripe 3.4)
【发布时间】:2016-07-28 15:47:24
【问题描述】:

我有一个应用于整个网站的每个页面的表单,并且需要异步发送它,而不会将用户带到任何地方。

我无法弄清楚要捕获发送的数据...我正在使用 Page.php 因为我假设它在它所属的每个页面上;我浏览了 SilverStripe 网站上的 ajax 教程,但这并没有真正涵盖主题,仅涵盖了正在开发的示例网站。我读了,我看了视频,还是想不通。

我需要知道将数据发送到哪个 URL(我假设它是任何 URL,因为 Page 适用于所有页面,对吗?)以及如何捕获请求...
目前我已经准备好发送信息的脚本,但不明白我需要在服务器端做什么。

这是我在后端 atm 中的内容,我该怎么做才能使它工作?

Page.php

 class Page_Controller extends ContentController {

    private static $allowed_actions = array(
        'flagDated'
    );

    public function flagDated(SS_HTTPRequest $request){

        echo $request;

        if($request->isAjax()){
            //
        }
    }

    public function init() {
        parent::init();
        Requirements::javascript('themes/three-two/js/flag-dated.js');
    }
 }

更新

我想我已经让它工作了。我发现重新安排我所拥有的让我更接近我需要去的地方。现在的问题是 $request->isAjax() 失败并且它正在返回 basic return...
我仍然不确定自己做错了什么 -- 我发现在请求末尾添加 ?ajax=1 可以解决此问题

 class Page extends SiteTree {

    // ...

    public static $allowed_actions = array(
        'flagDated'
    );

 }

 class Page_Controller extends ContentController {

    public function flagDated(SS_HttpRequest $request){

        if($request->isAjax()){
            return '$request';
        }

        return 'basic return';
    }

    public function init() {
        parent::init();
        Requirements::javascript('themes/three-two/js/flag-dated.js');
    }
 }

FireFox 控制台返回 200 Response: basic return


更新二

好的,所以我已经在服务器端接收数据,但在将数据输入postVars 时遇到了一点问题。

所以当我写我的更新时,我开始收到403ForbiddenAction 'flagDated' isn't allowed on this handler. 返回...我没有更改任何与权限相关的 AFAIK,这是什么原因造成的?是$allowed_actions 吗?因为自从我上次更新以来它一直在工作...PHP Fatal error: Access level to VirtualPage_Controller::$allowed_actions must be public (as in class Page_Controller)
嗯...我发现如果我更改 $allowed_action in @ 987654337@ from private to public 它再次起作用,我得到了一个 200 回复...这是什么问题在这里?为什么该页面会影响另一个页面?

【问题讨论】:

  • 我不久前写了这个要点来帮助:gist.github.com/dhensby/5057163
  • 不确定,但也许我为此question 提供的答案有帮助?它使用基于 AJAX 的表单提交。基本上,您只需编写一个常规表单并提交处理程序,然后通过 JS/AJAX 提交表单,而不是普通的表单提交。后端没有太大变化……
  • 这看起来很像我需要的!我已经添加了我目前拥有的代码,就我所知,接下来我需要做什么?

标签: silverstripe


【解决方案1】:

您将数据提交到表单“action”(表单元素上的属性)。这是表单处理程序。

很久以前我写了一个要点来处理 ajax 表单提交 (https://gist.github.com/dhensby/5057163)。

以下是一个完整示例,说明如何设置一个基本表单,该表单接受 AJAX 和传统默认浏览器行为的提交(这是一种很好的做法):

将表单添加到我们的控制器

首先我们需要定义我们的表单;你的Page_Controller 应该是这样的:

class Page_Controller extends ContentController {

    /**
     * A list of "actions" (functions) that are allowed to be called from a URL
     *
     * @var array
     * @config
     */
    private static $allowed_actions = array(
        'Form',
        'complete',
    );

    /**
     * A method to return a Form object to display in a template and to accept form submissions
     *
     * @return Form
     */
    public function Form() {
        // include our javascript in the page to enable our AJAX behaviour
        Requirements::javascript('framework/thirdparty/jquery/jquery.js');
        Requirements::javascript('mysite/javascript/ajaxforms.js');
        //create the fields we want
        $fields = FieldList::create(
            TextField::create('Name'),
            EmailField::create('Email'),
            TextareaField::create('Message')
        );
        //create the button(s) we want
        $buttons = FieldList::create(
            FormAction::create('doForm', 'Send')
        );
        //add a validator to make sure the fields are submitted with values
        $validator = RequiredFields::create(array(
            'Name',
            'Email',
            'Message',
        ));
        //construct the Form
        $form = Form::create(
            $this,
            __FUNCTION__,
            $fields,
            $buttons,
            $validator
        );

        return $form;
    }

    /**
     * The form handler, this runs after a form submission has been successfully validated
     *
     * @param $data array RAW form submission data - don't use
     * @param $form Form The form object, populated with data
     * @param $request SS_HTTPRequest The current request object
     */
    public function doForm($data, $form, $request) {
        // discard the default $data because it is raw submitted data
        $data = $form->getData();

        // Do something with the data (eg: email it, save it to the DB, etc

        // send the user back to the "complete" action
        return $this->redirect($this->Link('complete'));
    }

    /**
     * The "complete" action to send users to upon successful submission of the Form.
     *
     * @param $request SS_HTTPRequest The current request object
     * @return string The rendered response
     */
    public function complete($request) {
        //if the request is an ajax request, then only render the include
        if ($request->isAjax()) {
            return $this->renderWith('Form_complete');
        }
        //otherwise, render the full HTML response
        return $this->renderWith(array(
            'Page_complete',
            'Page',
        ));
    }

}

将这些函数添加到Page_Controller 将使它们在所有 页面类型上可用 - 这可能是不希望的,您应该考虑是否更适合创建新的页面类型(例如ContactPage) 以在此表单上显示

在这里,我们定义了以下方法:

  • 创建Form
  • 表单处理程序(保存或发送提交的内容,在 Form 成功验证其数据后运行)
  • complete 操作,用户在成功完成表单提交后将被发送到该操作。

自定义模板以轻松替换内容

接下来我们需要设置我们的模板 - 修改您的 Layout/Page.ss 文件:

<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
    <article>
        <h1>$Title</h1>
        <div class="content">$Content</div>
    </article>
    <div class="form-holder">
        $Form
    </div>
        $CommentsForm
</div>

这是从默认的简单主题中获取的,并稍加补充,表单现在被包裹在 &lt;div class="form-holder"&gt; 中,以便我们可以轻松地将表单替换为成功消息。

我们还需要创建一个Layout/Page_complete.ss 模板——除了form-holder div 将是:

<div class="form-holder">
    <% include Form_complete %>
</div>

接下来创建 Includes/Form_complete 包含 - 使用包含非常重要,这样我们就可以呈现页面的此部分以响应 AJAX 请求:

<h2>Thanks, we've received your form submission!</h2>
<p>We'll be in touch as soon as we can.</p>

创建 javascript 表单监听器

最后,我们需要编写 javascript 以通过 AJAX 发送表单,而不是默认浏览器行为(将其放在 mysite/javascript/ajaxform.js 中):

(function($) {
    $(window).on('submit', '.js-ajax-form', function(e) {
        var $form = $(this);
        var formData = $form.serialize();
        var formAction = $form.prop('action');
        var formMethod = $form.prop('method');
        var encType = $form.prop('enctype');

        $.ajax({
            beforeSend: function(jqXHR,settings) {
                if ($form.prop('isSending')) {
                    return false;
                }
                $form.prop('isSending',true);
            },
            complete: function(jqXHR,textStatus) {
                $form.prop('isSending',false);
            },
            contentType: encType,
            data: formData,
            error: function(jqXHR, textStatus, errorThrown) {
                window.location = window.location;
            },
            success: function(data, textStatus, jqXHR) {
                var $holder = $form.parent();
                $holder.fadeOut('normal',function() {
                    $holder.html(data).fadeIn();
                });
            },
            type: formMethod,
            url: formAction
        });
        e.preventDefault();
    });
})(jQuery);

此 javascript 将使用 AJAX 提交表单,完成后它将淡出表单并将其替换为响应并淡入。

对于高级用户:

在此示例中,您网站上的所有表单都将被“ajaxified”,这可能是可以接受的,但有时您需要对此进行一些控制(例如,搜索表单不能像这样很好地工作)。相反,您可以稍微修改代码以仅查找具有特定类的表单。

修改Page_Controller 上的Form 方法,如下所示:

public function Form() {
    ...
    $form->addExtraClass('js-ajax-form');
    return $form;
}

像这样修改javascript:

$(window).on('submit', '.js-ajax-form', function(e) {
    ...
})(jQuery);

现在只有类 js-ajax-form 的表单才会以这种方式运行。

【讨论】:

  • 只是为了澄清问题,我遇到问题的不是 ajax 方面,而是如何在框架中接收数据。 旁注:无论如何感谢该链接,它有文档还是仅需要 1 条评论即可使用它?
  • 没有其他文档。能否链接到您一直关注的视频和文档?
  • 当然 - Linky。这是主要的,然后是您在各种搜索引擎上找到的几乎所有搜索结果(到目前为止,我一直在寻找大约 7 个小时,真的不知道它需要什么)
  • 该视频不涉及表格。使用 Forms,您可以在控制器上设置常用的表单方法和表单处理程序(或操作)。见the form docs。表单通常可以很好地处理 AJAX 请求。如果你需要一个特殊的响应,你的表单处理程序可以有一个if Director::is_ajax() 检查来为 ajax 提交返回一个不同的响应
  • 你是对的,它没有,但这是我发现的唯一官方提到在该框架中使用 ajax。如果他们通过相关示例对整体使用情况进行某种概述,那就太好了,然后开发人员可以将其调整为他们需要做的事情......无论如何,这就是我记录功能的想法
【解决方案2】:

我已经设法让它工作了。
这是我的解决方案,供其他人使用 SilverStripe 进行 ajax,不管用例是什么。


HTML
非常基本,只是向用户展示了一些可以输入内容的字段。

 <div class="flag-dated-container hidden">

     <div class="modal-window">

         <div class="header">
             <h1>Flagging $Title</h1>
             <div class="close" data-action="close" title="Close">X</div>
         </div><!-- . header -->

         <form>
             <input type="text" placeholder="This content is..." />
             <textarea placeholder="Explain the issue in full detail..."></textarea>
         </form>

         <div class="buttons">
             <div data-action="close">Cancel</div>
             <div data-action="send">Send</div>
         </div><!-- . buttons -->

     </div><!-- . modal-window -->

 </div><!-- . flag-dated -->

JavaScript / ajax
只需注意两件事,在url 末尾使用您正在使用的函数的名称,您也在发送数据。我的是同一个页面,所以我使用当前 URL 并将 flagDated?ajax=1 附加到其中,所以当请求发送时 isAjax() 传递

 var container = document.getElementsByClassName('flag-dated-container')[0];

 // Interaction functionality
 container.addEventListener('click', function(e){

     var target = e.target,
         userInput = container.querySelectorAll('input, textarea');

     if(target.dataset.action === 'send'){

         /*
             My function in the controller is called 'flagDated'
             so that's what I append to the end of the URL
             and the ?ajax=1 is to ensure it passes the isAjax() check
             in the controller
         */
         var r = new XMLHttpRequest(),
             url = window.location + 'flagDated?ajax=1',
             data = '';

         // Create the param to send
         data = 'title=' + encodeURIComponent(userInput[0].value);
         data += '&body=' + encodeURIComponent(userInput[1].value);

         r.onreadystatechange = function(){

             if(r.readyState === 4 && r.status === 200){
                 // Success, do something
             }
             else if(r.readyState !== 4 && (r.status === 400 || r.status === 500)){
                 // failed
             }
         }
         r.open('POST', url); // We use the current URL because it doesn't matter what the target is
         r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
         r.send(data);
     }
 });

PHP
这是我最麻烦的部分。 $allowed_actions 需要在Controller 中并设置为private。我在 JS 脚本中设置的 titlebody 是使用 $request-&gt;postVars('myVar'); 中的相同变量访问

 class Page_Controller extends ContentController {

     private static $allowed_actions = array(
         'flagDated'
     );

     public function flagDated(SS_HttpRequest $request){

         if($request->isAjax() && $request->isPost()){

             $title = $request->postVar('title');
             $body = $request->postVar('body');

             $email = new Email(
                 'from@email.com',
                 'to@email.com',
                 'Flagged as Dated: ' . $title,
                 $body
             );

             $email->send();
         }
         else
             // Not ajax or post, do something else.
     }

     public function init() {
         parent::init();
         Requirements::javascript('themes/three-two/js/flag-dated.js');
     }
 }

【讨论】:

  • 请不要将其视为个人,但这不应被指定为正确答案。此示例绕过了 SilverStripe 的几乎所有安全功能,从 xsrf 保护到 html 注入保护。它也只是 ajax 表单的一个糟糕且无法访问的实现。这只是一个精简的例子,那些不知道的人不会意识到,并且可能会以此作为如何使用 ajax 和使用 SS 发送电子邮件的示例。这可能会解决您的特定问题,但不会帮助其他人,并且会传播不良做法。
  • 这是我找到的唯一方法,如果您知道更好的方法,请随时添加或更正答案。此外,我需要 Ajax 的不仅仅是表单数据,这只是我想要的示例,也是我发现收集和处理请求的唯一方法
  • 我在 SO docs 网站上提供了一个详细的示例,并为您提供了链接。如果这是您实现表单的方式,那么您的网站就会面临严重的安全漏洞。
猜你喜欢
  • 1970-01-01
  • 2013-09-11
  • 1970-01-01
  • 2016-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多