119. 系统AJAX(二):前端原理

前端概述:

drupal AJAX API前端系统主要是指核心库:core/drupal.ajax,下文简称AJAX库,前端AJAX行为均由该库完成,她主要依赖以下几个重要的库:

jquerycore/jquery:整个前端系统是建立在jquery基础之上的

jquery表单库core/jquery.form:为表单提交提供支持功能,请见本系列番外篇对此的介绍

core/drupal:为AJAX行为提供初始化等

core/drupalSettings:用于接收后端传递的AJAX配置

 

在流程上,渲染数组“#ajax”属性中的AJAX配置首先经过以下方法处理:

\Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm

然后传递给后端JS配置系统,也就是如下变量:

      $element['#attached']['drupalSettings']['ajax'][$element['#id']] = $AJAX_Settings;

其中$element['#id']是要触发AJAX请求的元素的ID属性值,任何一个表单元素都有id,手动指定优先,如果没有指定,则默认会在表单构建器中做如下处理:

      $unprocessed_id = 'edit-' . implode('-', $element['#parents']);

      $element['#id'] = Html::getUniqueId($unprocessed_id);

后端$element['#attached']['drupalSettings']变量会传递给前端的drupalSettings变量,然后AJAX库从该变量中获取AJAX配置进行AJAX行为设置,除从这里获取的配置外,AJAX库也会为便捷AJAX设置的元素生成AJAX配置(见后),最后统一由AJAX库的以下入口方法处理:

  Drupal.ajax(elementSettings);

这里参数elementSettings就是指AJAX配置,在内部会实例化以下方法:

  new Drupal.Ajax(base, element, settings);

这得到AJAX对象,在实例化过程中设置了AJAX行为,这里“AJAX配置”、“AJAX对象”是很重要的概念,后续多次用到。

注意:

AJAX请求均是POST请求,服务器只返回命令,即便返回的是html片段也是通过命令实现替换的

 

可信任AJAX地址

为了安全,前端并不允许向任意地址发起AJAX请求,在后端可通过以下变量设置一个可信任的地址:

      $element['#attached']['drupalSettings']['ajaxTrustedUrl'][$settings['url']] = TRUE;

在前端可信任的AJAX地址保存在全局对象drupalSettings.ajaxTrustedUrl中,以url做属性名,属性值为true,如果AJAX请求的url不在该对象中,且不是站内地址,则ajax API不允许发起ajax操作,如果是站内地址但不在该对象中,则允许发起AJAX请求,但会通过“X-Drupal-Ajax-Token”响应头来判断安全性,其值全等于1表示合法,否则响应不被接受

注意:该可信任AJAX地址不影响自定义js,也就是非AJAX api之外的内容

 

前端AJAX库:

在前端,AJAX功能封装在以下库中:

核心库:core/drupal.ajax

文件:core/misc/ajax.es6.js

这是系统ajax API的唯一前端js程序,入口方法是:

Drupal.ajax(elementSettings)

该方法接收一个配置对象(AJAX配置对象),依据其配置给元素添加AJAX功能,主要为三类元素提供AJAX功能,不同元素传递不同的配置对象,当我们自定义的前端js需要为元素添加ajax时也可以调用该方法,但前提是要明白配置对象的结构,在以下方法中为这三类元素准备了AJAX配置对象并调用了以上方法:

Drupal.behaviors.AJAX.attach(context, settings)

该方法在文档就绪后自动执行(见本系列《前端JavaScript(一)全局设置与前端API》主题),三类元素及其传递的配置对象结构如下:

 

声明在全局配置对象drupalSettings.ajax中的元素:

这一类元素通常是后端设置了“#ajax”属性的渲染数组元素,配置对象的值便来自“#ajax”属性,见:

\Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm

确保了以下配置键存在(在前端用elementSettings变量代表AJAX配置):

elementSettings.selector

该元素的jquery选择器,默认为id选择器,也可在后端“#ajax”属性中直接指定“selector”值

elementSettings.element

该元素的DOM对象,非jquery对象,将在其上绑定事件

elementSettings.base

该元素的id值(字符串值),不带“#”前缀

 

所有具备类属性“use-ajax”的元素:

通常是链接元素,为其AJAX配置elementSettings自动生成以下属性:

progress

进度指示,值默认为:{ type: 'throbber' }

dialogType:

对话框类型,值:$linkElement.data('dialog-type'),在后端将决定采用的主内容渲染器

dialog

对话框选项,值为:$linkElement.data('dialog-options'),

dialogRenderer

对话框子渲染器,值为:$linkElement.data('dialog-renderer'),和dialogType选项共同决定后端主内容渲染器

base

元素id属性值,字符串值,来自$linkElement.attr('id')

element

链接元素DOM对象,非jquery对象,将在其上绑定事件

url

链接url,仅在链接有href属性时才存在

event

要阻止的默认事件,默认为:“click”,仅在链接有href属性时才存在

 

所有具备类属性“use-ajax-submit”的元素:

通常是表单提交元素,如按钮,自动为其产生elementSettings,属性如下:

url

来自表单元素的action属性

setClick

布尔值,默认true,告诉后端表单是哪个元素提交的,这样才能执行相应验证、提交处理器

event

要阻止的默认事件,默认为:“click

progress

进度指示,值默认为:{ type: 'throbber' }

base

元素id属性值,字符串值

element

按钮元素DOM对象,非jquery对象,将在其上绑定事件

 

Ajax对象

添加AJAX的行为全部在实例化Ajax对象的过程中:

const ajax = new Drupal.Ajax(base, element, settings);

实例化的Ajax对象保存在以下全局数组中:

Drupal.ajax.instances

Ajax对象instanceIndex属性为在该数组中的索引

Ajax对象的属性:

合并了所有的AJAX配置属性,此外新增有以下属性

commands

命令对象,可通过该对象运行命令对应的方法,是后端返回命令被运行的关键

element

元素的DOM对象,非jquery对象,在其上设置AJAX事件

elementSettings

保存AJAX配置对象,别名element_settings

$form

如果元素是一个表单元素,将存在该属性,保存包含该元素的表单的jquery对象

url

发送AJAX的请求地址,不过已经被处理过,如果路径中存在“/nojs/”、“/nojs$”、“/nojs?”、“/nojs#”,那么会将其“nojs”替换成“ajax”,以此向服务器表明是通过ajax发送的请求,并将新的url添加到可信ajax路径中,(在后端需要关注是否为ajax请求的路由,在设置中可以以此设置占位变量,通常设置为“js”,通过判断其值为“nojs”还是“ajax”进行识别)

options

用于设置jqueryajax方法的选项对象,指定了AJAX请求发送行为

 

Ajax对象的方法:

她们定义在原型对象上(Drupal.Ajax.prototype),如下:

Drupal.Ajax.prototype.execute()

开发者可以在Ajax对象上调用该方法以手动执行ajax请求,而不需要任何事件触发,注意该方法是直接采用jqueryajax方法,而没有使用jquery表单库,因此并不调用提交前方法,默认的提交前方法是一个空方法,这没有什么影响,但有覆写提交前方法时,需要注意覆写并不会产生什么影响

 

Drupal.Ajax.prototype.keypressResponse(element, event)

当配置对象上设置了keypress属性,且其值为true时(默认值为true),表示可以通过按压空格或回车键触发配置对象中设置的AJAX触发事件(后称AJAX事件),程序会在元素上侦听keypress事件,当发生该事件时便会运行该方法,这里键盘代码(event.which):回车键是13,空格键是32,当按压回车时会触发AJAX事件,如果按压的是空格则需要排除四种元素:texttextareatelnumber,该方法在触发AJAX事件的同时也会阻止默认动作和事件传播

 

Drupal.Ajax.prototype.eventResponse(element, event)

元素AJAX事件的处理函数,如果元素是一个表单元素,如按钮、输入框等,那么将立即提交表单,这里用到了jQuery表单库(jquery.form.js),详见本系列番外篇对该库的介绍,传递给ajaxSubmit方法的是 Ajax对象的options属性,表单库将依次执行Ajax对象原型(Drupal.Ajax.prototype)上的以下方法

beforeSerialize(element, options);

beforeSubmit(formValues, element, options);

beforeSend(xmlhttprequest, options);

success(response, status);

如果元素不是表单元素,如链接,那么将采用jquery本身的方法($.ajax(ajax.options);)执行ajax,此时将依次运行Ajax对象原型(Drupal.Ajax.prototype)上的以下方法:

beforeSerialize(element, options);

beforeSend(xmlhttprequest, options);

success(response, status);

注意:以上均没有执行error回调,这是因为会在complete回调中执行错误处理,最终会执行Ajax对象原型(Drupal.Ajax.prototype)上的以下方法:

error(xmlhttprequest, ajax.url);

这个方法抛出的异常会采用window.alert方法弹框给用户(云客备注:此处没有用到翻译机制,须改进)

 

Drupal.Ajax.prototype.beforeSerialize(element, options)

表单序列化之前执行的回调函数,在取回值之前提供一个机会去操纵表单,参数含义如下:

elementjquery包装的表单对象

optionsAjax对象中的选项属性

注:如果该方法返回false将阻止表单提交,该特性可用方法覆写

主要工作是为AJAX提供额外请求参数,参数如下:

_drupal_ajax:值为1,告诉后端这是一个ajax请求

ajax_page_state[theme]:页面所用的主题

ajax_page_state[theme_token]:主题token

ajax_page_state[libraries]:页面已加载的资源库,避免ajax重复加载

如果元素是表单元素且在DOM中,那么还会执行全局方法:

Drupal.detachBehaviors(form, settings, ‘serialize’);

这样其他js组件就可以定义drupal行为对象(Drupal.behaviors)来修改表单的内容,参数含义如下:

form:表单DOM对象

settings:如果后端“#ajax”数组中设置了该项便采用,否则采用drupal全局设置对象drupalSettings

 

Drupal.Ajax.prototype.beforeSubmit(formValues, element, options)

仅用于表单元素,提供一个机会在提交前修改整个表单的值或选项对象,如果返回false将阻止表单提交,默认该方法是空的,如有需要可以覆写,三个参数为:数组格式的表单数据、jquery包装的表单对象和选项对象

 

Drupal.Ajax.prototype.beforeSend(xmlhttprequest, options)

在该方法中,变量“options.extraData”用到了jquery表单库的一个内部功能,在老旧浏览器中通过AJAX上传文件时,jquery表单库会采用iframe模拟提交,变量“options.extraData中的值仅在采用iframe模拟提交时才会被提交给服务器,否则并不被提交,现代浏览器已经不需要iframe模拟了,该方法在变量“options.extraData”中设置了“ajax_iframe_upload”参数,这样做的结果是:当服务器发现POST数据中有“ajax_iframe_upload”参数,那么就知道客户端是通过iframe模拟提交的,此时服务器可将JSON响应包装在TEXTAREA元素中返回;同理由于iframe模拟提交是浏览器的原生提交,因此不会提交被禁用的元素,所以对当前元素进行了特殊处理以得到提交;此外该方法做以下工作:

将元素设置为禁用,防止重复操作(在ajax完成或出错时在其他方法中会重新启用元素);设置进度指示器

注意:在非iframe模拟提交的情况下,该方法已经无法给请求添加额外的参数了

 

Drupal.Ajax.prototype.setProgressIndicatorBar()

Drupal.Ajax.prototype.setProgressIndicatorThrobber()

Drupal.Ajax.prototype.setProgressIndicatorFullscreen()

默认提供的三个进度指示器设置方法,用户可自定义进度指示器,设置方法名为“setProgressIndicator”前缀加类型名,其中类型名首字母大写,其他全小写,里面的this指向Ajax对象

 

Drupal.Ajax.prototype.success(response, status)

ajax成功后执行,去除进度条,去掉触发元素禁用状态,在drupalajax API中响应均是json对象,包含着各种命令,命令即是Drupal.AjaxCommands.prototype中的各种方法,调用是传递如下参数:

command(Ajax, response[i], status);

分别为:Ajax对象、命令对象、请求状态信息

请求状态信息为字符串值,可能值有"success", "notmodified", "nocontent", "error", "timeout", "abort", or "parsererror"

 

发送给后端的额外参数:

在进行AJAX请求时有些额外参数会传递给后端,她们有很重要的含义,影响着后端执行,如下:

ajax_form

GET查询参数,布尔值1,用于指示该AJAX是一个表单提交AJAX,如果在“#ajax”属性中AJAX配置仅有回调没有url时,后端会自动附加该参数到URL中,该参数名定义在以下常量中:

\Drupal\Core\Form\FormBuilderInterface::AJAX_FORM_REQUEST

前端并不处理该参数。该参数对于后端来说非常重要,直接影响系统执行流程(有该参数时表单流程采用异常控制流)

 

_wrapper_format

GET查询参数,用于在后端指定主内容渲染器(关于主内容渲染器见后端原理篇),AJAX前端无条件附加,必定存在,默认值为:“drupal_ajax”,该参数名后端定义在以下常量中:

\Drupal\Core\EventSubscriber\MainContentViewSubscriber::WRAPPER_FORMAT

前端定义在Drupal.ajax.WRAPPER_FORMAT变量中,并没有进行前后端传递。

 

_drupal_ajax

POST请求参数,用于告诉后端请求是一个AJAX请求(不一定是表单AJAX请求,而是所有类型的AJAX请求),AJAX前端无条件附加,必定存在,值为1,该参数名定义在以下常量中:

\Drupal\Core\EventSubscriber\AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER

前端定义在Drupal.Ajax.AJAX_REQUEST_PARAMETER变量中,并没有进行前后端传递。

 

ajax_iframe_upload

POST请求参数,用于告诉后端AJAX是借助iframe模拟发起的;在web早期浏览器并不支持文件等AJAX上传,人们变通的借助iframe元素模拟AJAX(可参考本系列番外篇jquery表单库了解这种技术),随着现代浏览器的支持现在已经不会这样做了,但有部分老旧浏览器还在使用,此时仍将采用iframe模拟,这种情况下便会传递该参数给服务器,值为“1”,当页面在现代浏览器中运行时,通常不会传递该参数,除非故意指定采用iframe模拟;在后端如果服务器检测到该参数,那么就可以进行回退处理,实际上当drupal 在格式协商阶段发现该参数时会将请求格式设置为“iframeupload”。该参数在前后端均未采用常量定义。

 

补充:

1、在一些路由中,会用“js”作为路径占位变量,其值为“nojs”时表示客户端非js提交,为“ajax”表示通过AJAX提交,AJAX前端将替换路径中的“nojs”为“ajax”,这样后端便可借此侦测客户端环境

2、服务器的ajax响应均以命令方式返回,前端命令函数定义在Drupal.AjaxCommands.prototype上,关于命令因为涉及前后端配合,本系列将在专门的主题中详解,

3throbber进度条默认样式(由系统模块原始定义):

原始定义:core/modules/system/css/components/ajax-progress.module.css

覆写定义:core/themes/stable/css/system/components/ajax-progress.module.css

4、在前端AJAX配置中,并无ajax回调概念(callback选项),仅有url,回调并不通过前端传递。

 

本书共130小节:

评论 (写第一个评论)