Phalcon PHP post link 带有 JavaScript 确认对话框

Phalcon PHP post link with JavaScript confirmation dialog

我正在开发 CRUD system in Phalcon PHP(版本 1.3.4)。

我的目标是创建一个 link(删除行),在点击时要求确认(JavaScript 确认框),然后转到(请求类型 POST) link.

假设用户点击了 "delete row" 按钮。

  1. JavaScript确认"Are you sure you want to delete this row?"
  2. 用户点击"yes"
  3. 网页执行 POST 到“/users/delete/1”

我知道 CakePHP has a function (FormHelper::postLink()) 正是这样做的。

我想知道 Phalcon PHP 是否也有这样的功能。

我看到三种可能性来实现你想要的。一种是创建一个 macro in Volt template, second is to add a function to your View. Third and closest to - what I understand is your wish - is to extend Phalcons tag helper,这是我将在此处描述的部分。

Phalcon 有自己的标签助手,可以让您轻松创建一些元素。 postLink 不是在那里实现的部分,但是你可以很容易地实现它。在我的例子中,我有 Application 的命名空间,Tag 的 class 从 \Phalcon\Tag 延伸。这是本教程的基础。

// Tag.php

namespace Application;

class Tag extends \Phalcon\Tag
{

    static public function postLink() {
        return '<strong>TEST TAG</strong>';
    }
}

要强制 Phalcon DI 使用此 class,有必要通过手动将其声明为新的 DI 服务来覆盖它在引擎中的标准声明:

// services.php
$di['tag'] = function() {
    return new \Application\Tag();
};

您可以通过在 Volt 模板中键入 {{ tag.postLink() }} 或如果使用 phtml 模板则使用 $this->tag->postLink() 来测试它是否正常工作。

现在您可以使用 HTML 和您希望它产生的参数填充您的 Tag::postLink() 方法:

namespace Application;

class Tag extends \Phalcon\Tag
{

    static $forms = [];

    static public function postLink($title, $url, $options = array()) {

        // random & unique form ID
        while ($randId = 'f_' . mt_rand(9000, 999999)) {
            if (!isset(self::$forms[$randId])) {
                self::$forms[$randId] = true;
                break;
            }
        }

        // dialog message
        $dialogMessage = isset($options['message']) && $options['message'] ?  $options['message'] : 'Are you sure you want to go on?';


        $html = <<<HTML

            <form action="{$url}" method="post" id="{$randId}">
                <!-- maybe not necessary part -->
                <input type="hidden" name="confirmed" value="1" />
            </form>
            <a href="#" onclick="javascript: confirm('{$dialogMessage}') ? document.forms['{$randId}'].submit() : false;">{$title}</a>

HTML;

        return $html;
    }
}

现在您可以 运行 像这样:

{{ tag.postLink('delete', '/users/delete/1') }}

{% set formOptions = ['message' : 'Are you sure you want to delete user Kialia Kuliambro?'] %}
{{ tag.postLink('delete', '/users/delete/1', formOptions) }}

{{ tag.postLink('delete', '/users/delete/1', ['message' : 'Are you sure you want to delete user Kialia Kuliambro?']) }}

享受扩展的乐趣:)[​​=22=]

有几种方法可以在 phalcon 中实现这种行为。首先,我们需要了解视图和视图助手在 phalcon 中是如何工作的。如果你仔细观察,你会注意到,.volt and .phtml have direct access to the DI

以伏特为例,您可以访问flash服务,并通过调用输出其消息:
{{ flash.output() }} 它被转换为 phtml:<?php echo $this->flash->output(); ?>

因此,我的解决方案侧重于在 DI 中定义一个新服务,volt 可以访问该服务。在 CakePHP 中,postLink(), looks something like: echo $this->Form->postLink() while the function is actually defined in a class named FormHelper 的语法。所以我的解决方案将做同样的事情,定义一个 class FormHelper,然后将它注入到名为 Form 的视图中。

  1. 创建一个 app/helpers/ 目录。
  2. 更新您的 app/config/config.php 文件,添加对我们新目录的引用:'helpersDir'=> APP_PATH . '/app/helpers/'
  3. 更新您的 app/config/loader.php 文件,将 $config->application->helpersDir 添加到注册目录。
  4. 创建一个新文件app/helpers/FormHelper.php
  5. following 代码复制并粘贴到文件中:
<?php

use Phalcon\Tag;

class FormHelper extends Tag
{
    protected $_lastAction = '';
    public function dottedNameToBracketNotation($name)
    {
       $parts=explode('.',$name);
       $first = array_shift($parts);
       $name=$first . ($parts ? '[' . implode('][', $parts) . ']' : '');
       return $name;
    }
    protected function flatten(array $data, $separator = '.')
    {
        $result = [];
        $stack = [];
        $path = null;
        reset($data);
        while (!empty($data)) {
            $key = key($data);
            $element = $data[$key];
            unset($data[$key]);
            if (is_array($element) && !empty($element)) {
                if (!empty($data)) {
                    $stack[] = [$data, $path];
                }
                $data = $element;
                reset($data);
                $path .= $key . $separator;
            } else {
                $result[$path . $key] = $element;
            }
            if (empty($data) && !empty($stack)) {
                list($data, $path) = array_pop($stack);
                reset($data);
            }
        }
        return $result;
    }
    protected function _confirm($message, $okCode, $cancelCode = '', $options = [])
    {
        $message = json_encode($message);
        $confirm = "if (confirm({$message})) { {$okCode} } {$cancelCode}";
        if (isset($options['escape']) && $options['escape'] === false) {
            $confirm = $this->h($confirm);
        }
        return $confirm;
    }
    public function h($text, $double = true, $charset = 'UTF-8')
    {
        return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, $charset, $double);
    }
    protected function _lastAction($url)
    {
        $action = $url;//Router::url($url, true);
        $query = parse_url($action, PHP_URL_QUERY);
        $query = $query ? '?' . $query : '';
        $this->_lastAction = parse_url($action, PHP_URL_PATH) . $query;
    }
    public function postLink($title, $url = null, array $options = [])
    {

        $out='';
        $options += ['block' => null, 'confirm' => null];
        $requestMethod = 'POST';
        if (!empty($options['method'])) {
            $requestMethod = strtoupper($options['method']);
            unset($options['method']);
        }
        $confirmMessage = $options['confirm'];
        unset($options['confirm']);
        $formName = str_replace('.', '', uniqid('post_', true));
        $formOptions = [
            'name' => $formName,
            'style' => 'display:none;',
            'method' => 'post',
        ];
        if (isset($options['target'])) {
            $formOptions['target'] = $options['target'];
            unset($options['target']);
        }
        $formOptions[0]=$url;
        $out.=$this->form($formOptions);
        $out .= $this->hiddenField(['_method','value' => $requestMethod]);
        $fields = [];
        if (isset($options['data']) && is_array($options['data'])) {
            foreach ($this->flatten($options['data']) as $key => $value) {
                $out .= $this->hiddenField([$this->dottedNameToBracketNotation($key),'value' => $value]);
            }
            unset($options['data']);
        }
        $out .= $this->endForm();
        //This is currently unsupported
        if ($options['block']) {
            if ($options['block'] === true) {
                $options['block'] = __FUNCTION__;
            }
            //$this->_View->append($options['block'], $out);
            $out = '';
        }
        unset($options['block']);
        $url = '#';
        $onClick = 'document.' . $formName . '.submit();';
        if ($confirmMessage) {
            $options['onclick'] = $this->_confirm($confirmMessage, $onClick, '', $options);
        } else {
            $options['onclick'] = $onClick . ' ';
        }
        $options['onclick'] .= 'event.returnValue = false; return false;';
        $options[0]=$url;
        $options[1]=$title;
        $options[2]=false;
        $out .= $this->linkTo($options);
        return $out;

    }

}
  1. 编辑您的 app/config/services.php 文件并添加:
$di->set('Form',function () {
return new FormHelper();
});

(如果你愿意,你可以将 "Form" 设为小写,两者都有效。我将其设为大写以更接近 CakePHP 的语法。请注意,在尝试访问服务时 Volt 区分大小写,但 phtml 会将其小写。 )

  1. 编辑要测试代码的模板,例如 app/views/index/test.volt
  2. 将以下代码复制粘贴到其中:
{{ Form.postLink(' Delete','',['confirm':'Are you sure you want to delete #4?','data':['a':['b','c']]]) }}

或者对于 phtml,使用:

<?php echo $this->form->postLink(' Delete', '', array('confirm' => 'Are you sure you want to delete #4?', 'data' => array('a' => array('b', 'c')))); ?>

运行 它,并观察它发挥它的魔力,只需访问地址栏中的 /index/test 即可呈现您的 index/test.volt 模板。 (确保您在索引控制器中定义了这样的操作)

在其他解决方案方面,您也可以使用$compiler->addFunction() to make functions available to volt, one at time. The page in the manual gives the example of $compiler->addFunction('shuffle', 'str_shuffle');. You can attempt to override the factoryDefault for "tag" in the DI, and use the helper we already defined which extends tag. So you'd just change it from "form" to "tag" like so: $di->set('tag',function () {return new FormHelper();}); but, as you can see, it won't make the function postLink() available to volt as a function, you'll notice you still need to access it as tag.postLink(). Rather, all the \Phalcon\Tag functions are actually hard-coded into the volt engine. You can see this clearly by viewing the zephir source code of the \Phalcon\Mvc\View\Engine\Volt\Compiler class available over here。为了您的方便,以防 link 被破坏,我在这里发布了一个片段,它显示了以伏特为单位的 "tag" 函数实际上是硬编码到其中的:

    if method_exists(className, method) {

        let arrayHelpers = this->_arrayHelpers;
        if typeof arrayHelpers != "array" {
            let arrayHelpers = [
                "link_to": true,
                "image": true,
                "form": true,
                "select": true,
                "select_static": true,
                "submit_button": true,
                "radio_field": true,
                "check_field": true,
                "file_field": true,
                "hidden_field": true,
                "password_field": true,
                "text_area": true,
                "text_field": true,
                "email_field": true,
                "date_field": true,
                "tel_field": true,
                "numeric_field": true,
                "image_input": true
            ];
            let this->_arrayHelpers = arrayHelpers;
        }

        if isset arrayHelpers[name] {
            return "$this->tag->" . method . "(array(" . arguments . "))";
        }
        return "$this->tag->" . method . "(" . arguments . ")";
    }

因此,如果您想通过扩展 \Phalcon\Tags class 来使用更多方法来 "hack",那您就走运了。然而,as demonstrated 在 volt 文档页面上,存在注册自定义扩展以使用 volt 的概念。该文档给出了以下示例:$compiler->addExtension(new PhpFunctionExtension());
class 的来源是:

<?php

class PhpFunctionExtension
{
    /**
     * This method is called on any attempt to compile a function call
     */
    public function compileFunction($name, $arguments)
    {
        if (function_exists($name)) {
            return $name . '('. $arguments . ')';
        }
    }
}

这将允许 volt 访问您想要的任何功能,而无需手动注册您可能需要的每个可能的功能。您可以通过尝试以伏特访问 str_shuffle 来测试这一点,就像我们之前使用 $compiler->addFunction('shuffle', 'str_shuffle'); 所做的那样,但这次无需注册。

在其他解决方案方面,您也可以尝试将 CakePHP 和 PhalconPHP 集成在一起,并尝试从 PhalconPHP 调用 CakePHP 的视图助手,但这样您 运行 就会陷入 CakePHP 不理解您的问题您在 Phalcon 中配置的路由器设置。但是,如果您下定决心,您可以为 CakePHP 和 运行 与 PhalconPHP 一起编写所有路由和配置代码,但我强烈反对这种绝望的解决方法。最后,如果您了解该函数的工作原理并且几乎不使用它,那么您可以首先对 HTML 进行硬编码。老实说,首先,CakePHP 的逻辑在我看来并不那么合理,因为它必须用插入的表单破坏您的 HTML 文档,这会影响您的布局。如果我们已经在使用 JavaScript,我认为使用 JavaScript 动态生成一个表单会更有意义,并在单击按钮时将其附加到 <body>,然后提交我们刚刚动态创建的表单。但是,您想要一个 CakePHP 实现,所以我将其编码为尽可能接近他们使用的逻辑。它并不完美,就支持它们的所有功能而言,例如 block,但它应该可以满足您的大部分需求。

我总是可以修改我的实现,但我认为它向那些从 CakePHP 迁移的人展示了如何很好地使用 Phalcon。