“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
对话框dialog是页面中悬浮的一个用来显示内容的框,有标题栏、内容区域、控制按钮等,可以被移动,改变尺寸,当内容太长会出现滚动条,键盘导航也只能在框里面进行,看起来像覆盖在父窗体上的子窗体,通常是用来显示一个单独的源的内容,可以在不离开父窗体的情况下做一些互动操作或提供信息等。
modal称为模态对话框,是dialog的一种,通常对话框dialog在打开时,其底下的元素允许交互,而modal将禁止交互,底下的元素全部为禁用状态,直到对话框关闭为止。
基础:
drupal前端系统的对话框是建立在jquery UI基础之上的,明白了jquery UI对话框后你将很容易理解drupal对话框的实现,jquery UI组件的官方下载地址是:
https://jqueryui.com/download/
在该页面你可以选择jQuery UI的版本、下载的库中包含的组件、UI使用的主题样式,然后点击下载,这里假设你选择了全部组件,并下载解压到“jqueryui”目录,这里提供一个对话框的使用示例:
对话框示例:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>jQuery UI 对话框示范页</title>
<link href="jqueryui/jquery-ui.css" rel="stylesheet">
</head>
<body>
<h2>点击打开Dialog</h2>
<p>
<button id="dialog-link" class="ui-button ui-corner-all ui-widget">
<span class="ui-icon ui-icon-newwin"></span>Open Dialog
</button>
</p>
<div id="dialog" title="这里是Dialog对话框标题">
<p>对话框内容:drupal并不将UI下载到一个js文件中,而是按组件分别下载,然后形成不同的核心库</p>
</div>
<br><br><br><br><br>
<a href="http://www.baidu.com" target="_blank">点击打开百度</a>
<script src="jqueryui/external/jquery/jquery.js"></script>
<script src="jqueryui/jquery-ui.js"></script>
<script>
//初始化对话框
$("#dialog").dialog({
autoOpen: false,
modal: true, //为true时将使对话框仅有模态行为,底层元素被禁用
width: 400,
buttons: [
{
text: "确定",
click: function () {
$(this).dialog("close");
}
},
{
text: "取消",
click: function () {
$(this).dialog("close");
}
}
]
});
//初始化打开对话框事件
$("#dialog-link").click(function (event) {
$("#dialog").dialog("open");
event.preventDefault();
});
</script>
</body>
</html>
示例说明:
该例引用了三个关键文件: UI样式、jquery库、jquery-ui库,jquery库必须在jquery-ui库之前引用,后者扩展了前者,如你所见,简单的使用UI库提供的方法即可实现对话框功能。
关于jquery-ui库的对话框用法不属于本系列范围,如何使用请详见官方文档:
https://api.jqueryui.com/dialog/
在继续阅读前强烈建议你花几十分钟学习该API,这里仅列出常被drupal使用的选项。
对话框选项dialogOptions:
jquery-ui原生选项及含义如下:
appendTo:选择器字符串,指示对话框应该追加到哪个元素上,默认为body
autoOpen:布尔值,指示对话框初始化后是否自动打开,默认为true,否则将保持隐藏,等待调用打开方法
buttons:设置对话框中的按钮,对象或数组值,如果是对象,那么属性名为按钮文本,属性值为点击事件的回调,如果是数组,那么个元素必须是一个对象,其属性名text指定按钮文本,click指定点击回调,icon指定按钮图标类
classes:指定额外的类名到对话框各控件元素
closeOnEscape:布尔值,默认为true,在对话框有焦点时,指示能否通过按压ESC关闭
closeText:关闭按钮的文本字符串,默认为“close”,默认情况下该文本是隐藏的,当鼠标在其上时才显示
dialogClass:为整体对话框添加一个类名,已弃用,用classes项的ui-dialog属性代替
draggable:布尔值,指示是否可以通过标题栏拖动对话框
height:对话框的高度,像素为单位的整数,或“auto”,表示依据内容调整,这是默认值
hide:对话框关闭动画,为true时表示立即关闭(无动画),true时将慢慢淡出,也可以指定动画名等
maxHeight:调整尺寸时的对话框最大高度,像素为单位
maxWidth:调整尺寸时的最大宽度,像素为单位
minHeight:调整尺寸时的对话框最小高度,像素为单位,默认150
minWidth:调整尺寸时的最小宽度,像素为单位,默认150
modal:布尔值,是否将对话框指定为模态框(禁用底层元素)
position:对话框打开时的位置,默认为{ my: "center", at: "center", of: window }
resizable:布尔值,默认true,指示是否可以拖拉调整对话框尺寸
show:指示打开对话框的动画,false为无动画,立即打开,true时将慢慢淡出,也可以指定动画名等
title:对话框标题,字符串值,如为null,将用对话框原元素的title属性代替
width:对话框宽度,默认为300,像素为单位
Drupal前端dialog库:
核心库:core/drupal.dialog
提供对话框dialog操作功能
依赖以下库:
- core/jquery
- core/drupal
- core/drupalSettings(提供对话框默认选项)
- core/drupal.debounce(防抖库限制大小、拖动调整时的回调执行频率)
- core/drupal.displace(边距库,在自动位置时确定对话框位置)
- core/jquery.ui.dialog(原生jquery.ui.dialog库)
文件:
core/misc/dialog/dialog.es6.js
core/misc/dialog/dialog.position.es6.js
core/misc/dialog/dialog.jquery-ui.es6.js
在加载该库时,你依然可以使用原生jquery-ui提供的方法去打开、操作、关闭一个对话框,但是你不应该这么做,该库的目的是将原生jquery-ui库集成到drupal系统中,提供更高层次的封装,这让对话框处理和drupal其他组件产生关联,比如和位置组件的关联可以很好的处理对话框的默认位置、如果其他组件在对话框上有事件绑定可以获得通知,执行解绑、重绑等,如果单独使用原生jquery-ui库将不会和其他组件互动,是一个孤立的操作,因此在drupal中打开一个对话框应该使用以下方法:
Drupal.dialog(element, options)
参数element用于指示被当做对话框的元素,可以是一个jquery选择器,也可以是元素的dom对象,参数 options是对话框选项,在全局变量drupalSettings.dialog中储存着默认选项
该方法返回一个dialog对象,须进一步调用该对象上的以下三个方法才能完成对话框操作:
show():打开对话框
showModal():打开模态对话框
close(value):关闭对话框,参数将作为返回值保存在dialog.returnValue中
注意:dialog对象的open属性仅指示在dialog对象上执行的打开关闭操作,并不能真正表明对话框的真实状态,后者仍需要jquery-ui原生提供的方法去判断
页面其他组件可以监听相关事件感知对话框,执行相关操作时的事件触发如下:
$(window).trigger('dialog:beforecreate', [dialog, $element, settings]);
$(window).trigger('dialog:aftercreate', [dialog, $element, settings]);
$(window).trigger('dialog:beforeclose', [dialog, $element]);
$(window).trigger('dialog:afterclose', [dialog, $element]);
还可以通过以下方法监听对话框的变化:
“resize.dialogResize”、“dialogContentResize”
前端dialog.ajax库:
核心库:core/drupal.dialog.ajax
文件:/core/misc/dialog/dialog.ajax.es6.js
依赖以下库:
- core/jquery
- core/drupal
- core/drupalSettings
- core/drupal.ajax
- core/drupal.dialog
该库用于在AJAX时通过AJAX命令打开、关闭、修改对话框,当服务器返回这类命令时必须附带执行加载该库的动作,确保该库被加载,示例详见:
\Drupal\Core\Render\MainContent\DialogRenderer::renderResponse
命令需要指定一个对话框元素,当该元素尚不存在时会新建一个,并通过AJAX插入命令以html方法将内容放入,内容中以下jquery选择器对应的按钮会被移动到对话框的按钮栏:
'.form-actions input[type=submit], .form-actions a.button'
也就是表单提交按钮和链接标签按钮
AJAX打开Dialog命令:
后端php类:\Drupal\Core\Ajax\OpenDialogCommand
构造函数按序接收以下参数:
$selector:必须,一个id选择器以指示前端对话框元素,以#做前缀,在前端如果不存在将新建该ID元素
$title:必须,对话框标题,字符串值,不能传递渲染数组
$content:必须,对话框内容,既可为字符串值的html内容,也可为渲染数组,如是渲染数组可在数组中加载前文所述的dialog.ajax库
$dialog_options:可选,对话框选项数组,默认值保存在前端全局变量drupalSettings.dialog中
$settings:设置数组
前端方法(位于dialog.ajax库中):
Drupal.AjaxCommands.prototype.openDialog(ajax, response, status);
AJAX关闭Dialog命令:
后端php类:\Drupal\Core\Ajax\CloseDialogCommand
构造函数按序接收以下参数:
$selector:一个id选择器以指示前端对话框元素
$persist:布尔值,在对话框关闭后,是否让其继续保留在DOM中,不保留将调用jquery的remove()方法
前端方法(位于dialog.ajax库中):
Drupal.AjaxCommands.prototype.closeDialog(ajax, response, status);
AJAX设置Dialog选项命令:
后端php类:\Drupal\Core\Ajax\SetDialogOptionCommand
构造函数按序接收以下参数:
$selector:一个id选择器以指示前端对话框元素
$option_name:选项名,可使用任意原生jquery-ui提供的选项
$option_value:选项值
前端方法(位于dialog.ajax库中):
Drupal.AjaxCommands.prototype.setDialogOption(ajax, response, status);
其他对话框相关命令:
打开Modal命令:
类:\Drupal\Core\Ajax\OpenModalDialogCommand
模态框是对话框的一种,该命令很简单,继承自打开Dialog命令,仅设置选项参数modal为真,该命令完全可用对话框相关命令代替
关闭Modal命令:
类:\Drupal\Core\Ajax\CloseModalDialogCommand
设置对话框标题命令:
类:\Drupal\Core\Ajax\SetDialogTitleCommand
完全可用选项命令代替
主内容渲染器:
dialog主内容渲染器:
容器id:main_content_renderer.dialog
类:Drupal\Core\Render\MainContent\DialogRenderer
modal主内容渲染器:
容器id:main_content_renderer.modal
类:Drupal\Core\Render\MainContent\ModalRenderer
继承自dialog主内容渲染器
这两个主内容渲染器都很简单,着重注意加载dialog.ajax库的方法即可,加载的库会在AJAX响应附属处理服务(id:ajax_response.attachments_processor)中以追加命令添加到页面中:
加载dialog:<script src="/core/misc/dialog/dialog.ajax.js?v=8.7.1"></script>
详见本系列AJAX后端流程
应用示例一:基础
为了完整演示对话框的用法,这里提供一个示例,需要一个运行示例的模块,这里以“yunke_help”模块为例:
第一步:
在控制器:\Drupal\yunke_help\Controller\Test::test中放入以下示例代码:
$elements['#title'] = '触发AJAX并打开dialog显示内容完整示例';
$url = \Drupal\Core\Url::fromUri('internal:/yunke-help/test-1');//AJAX目的地
$elements['span'] = [//这里以span元素为列,但不限于该元素
'#type' => 'html_tag',
'#tag' => 'span',
'#value' => '点击这里将在dialog中呈现ajax内容',
'#attributes' => ['id' => 'yunkeID'],
];
$elements['link']['#attached']['drupalSettings']['ajax']['yunkeID'] = [ //以触发元素ID做键名
'event' => 'click',
'url' => $url->toString(),
'progress' => [
'type' => 'throbber',
'message' => '正在进行ajax...',
],
];
$elements['link']['#attached']['drupalSettings']['ajaxTrustedUrl'][$url->toString()] = TRUE;
$elements['link']['#attached']['library'][] = 'core/jquery.form';
$elements['link']['#attached']['library'][] = 'core/drupal.ajax';
return $elements;
第二步:
在控制器:\Drupal\yunke_help\Controller\Test::test_1中放入以下代码:
$main_content = array(
'#markup' => '<div>我是ajax获取的内容' . time() . '</div>',
);
//$main_content是要渲染的任意渲染数组
$main_content['#attached']['library'][] = 'core/drupal.dialog.ajax';
//必须加载该库
$response = new \Drupal\Core\Ajax\AjaxResponse();
$dialogSelector = "#yunke_dialog"; //dialog元素的选择器
$title = '对话框标题';
$options = ['minWidth' => 500]; //选项
$OpenDialogCommand = new \Drupal\Core\Ajax\OpenDialogCommand($dialogSelector, $title, $main_content, $options);
$response->addCommand($OpenDialogCommand);
return $response;
说明:
然后访问: http://www.你的域名.com/yunke-help/test
或点击“yunke_help”模块主页的测试按钮
依据页面提示点击查看效果
该例在页面中设置了一个AJAX事件,通过返回命令的方式来打开对话框,在第二步中直接返回了AJAX响应对象,因此不会执行主内容渲染器,但通常可以通过添加GET参数“_wrapper_format”的方式指定对话框主内容渲染器,从而第二步仅返回渲染数组即可,但与之相比,本列更加灵活可控。
应用示例二:采用对话框提交表单
仍以“yunke_help”模块为例:
第一步:建立示例访问界面
在控制器:\Drupal\yunke_help\Controller\Test::test中放入以下示例代码:
$elements['#title'] = '采用dialog进行表单提交示例';
$url = \Drupal\Core\Url::fromUri('internal:/yunke-help/test-1');//AJAX目的地
$elements['span'] = [//这里以span元素为列,但不限于该元素
'#type' => 'html_tag',
'#tag' => 'span',
'#value' => '点击打开dialog',
'#attributes' => ['id' => 'yunkeID'],
];
$elements['link']['#attached']['drupalSettings']['ajax']['yunkeID'] = [ //以触发元素ID做键名
'event' => 'click',
'url' => $url->toString(),
'progress' => [
'type' => 'throbber',
'message' => '正在进行ajax...',
],
];
$elements['link']['#attached']['drupalSettings']['ajaxTrustedUrl'][$url->toString()] = TRUE;
$elements['link']['#attached']['library'][] = 'core/jquery.form';
$elements['link']['#attached']['library'][] = 'core/drupal.ajax';
return $elements;
第二步:打开对话框并加载表单
在控制器:\Drupal\yunke_help\Controller\Test::test_1中放入以下代码:
$main_content = \Drupal::formBuilder()->getForm("\Drupal\yunke_help\Form\YunkeForm");
//$main_content是要渲染的任意渲染数组
$main_content['#attached']['library'][] = 'core/drupal.dialog.ajax';
//必须加载该库
$response = new \Drupal\Core\Ajax\AjaxResponse();
$dialogSelector = "#yunke_dialog"; //dialog元素的选择器,在前端尚不存在
$title = isset($main_content['#title']) ? $main_content['#title'] : '提交表单';
$options = []; //选项
$OpenDialogCommand = new \Drupal\Core\Ajax\OpenDialogCommand($dialogSelector, $title, $main_content, $options);
$response->addCommand($OpenDialogCommand);
return $response;
第三步:建立表单
在yunke_help/src/Form/YunkeForm.php文件中放入以下代码:
<?php
/**
* 演示表单AJAX操作
*/
namespace Drupal\yunke_help\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class YunkeForm extends FormBase
{
public function getFormId()
{
return 'yunke_help_form';
}
public function buildForm(array $form, FormStateInterface $form_state)
{
$form['#title'] = '对话框表单提交';
$form['input'] = [
'#type' => 'textfield',
'#title' => '任意输入字符:',
'#size' => '60',
];
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Submit'),
'#button_type' => 'primary',
'#ajax' => [
'callback' => '::yunke',
'event' => 'click',
'disable-refocus' => true,
'progress' => [
'type' => 'throbber',
'message' => '表单正在提交中...',
],
],
);
$form['result'] = array( //用于显示提交错误
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => ['id' => 'result',],
);
return $form;
}
public function yunke(array &$form, FormStateInterface $form_state)
{
$response = new \Drupal\Core\Ajax\AjaxResponse();
if ($form_state->get('submitOK')) {//处理提交成功的情况
$main_content = ['#markup' => '你的输入为:' . $form_state->getValue('input')];
$main_content['#attached']['library'][] = 'core/drupal.dialog.ajax';
//必须加载该库,不用担心重复,系统会自动处理
$dialogSelector = "#yunke_dialog"; //dialog元素的选择器
$title = '提交成功';
$options = []; //选项
$OpenDialogCommand = new \Drupal\Core\Ajax\OpenDialogCommand($dialogSelector, $title, $main_content, $options);
$response->addCommand($OpenDialogCommand);
return $response;
}
$errors = $form_state->getErrors();
$message = ['提交失败,请检查错误:'];
foreach ($errors as $error) {
$message[] = $error;
}
//错误会沉积在消息系统中,体验不好故删除,但不要删除非表单错误的消息
$errorMessages = \Drupal::messenger()->deleteByType('error');
foreach ($errors as $key => $error) {
$errors[$key] = (string)$error;//可能是翻译对象
}
foreach ($errorMessages as $key => $error) {
if (!in_array((string)$error, $errors)) {
\Drupal::messenger()->addMessage($error, 'error');
}
}
$html = ['#markup' => implode('<br>', $message)];
$command = new \Drupal\Core\Ajax\HtmlCommand("#result", $html);
$response->addCommand($command);
$response->addCommand(new \Drupal\Core\Ajax\CssCommand("#result", ['color' => 'red']));
return $response;
}
public function validateForm(array & $form, FormStateInterface $form_state)
{
if (empty($form_state->getValue('input'))) {
$form_state->setErrorByName('input', '输入不能为空');
}
}
public function submitForm(array & $form, FormStateInterface $form_state)
{
$form_state->cleanValues();
//some code
//在这里执行正常提交操作
$form_state->set('submitOK', TRUE);
//标识提交已执行成功
}
}
说明:
该例是对话框、AJAX、表单的综合运用,如不明白请阅读本系列AJAX后端原理等主题
反馈互动