天天看點

雲客Drupal源碼分析之系統AJAX(二):前端原理

前端概述:

drupal AJAX API前端系統主要是指核心庫:core/drupal.ajax,下文簡稱AJAX庫,前端AJAX行為均由該庫完成,她主要依賴以下幾個重要的庫:

jquery庫core/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:

用于設定jquery的ajax方法的選項對象,指定了AJAX請求發送行為

Ajax對象的方法:

她們定義在原型對象上(Drupal.Ajax.prototype),如下:

Drupal.Ajax.prototype.execute()

開發者可以在Ajax對象上調用該方法以手動執行ajax請求,而不需要任何事件觸發,注意該方法是直接采用jquery的ajax方法,而沒有使用jquery表單庫,是以并不調用送出前方法,預設的送出前方法是一個空方法,這沒有什麼影響,但有覆寫送出前方法時,需要注意覆寫并不會産生什麼影響

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

當配置對象上設定了keypress屬性,且其值為true時(預設值為true),表示可以通過按壓空格或Enter鍵觸發配置對象中設定的AJAX觸發事件(後稱AJAX事件),程式會在元素上偵聽keypress事件,當發生該事件時便會運作該方法,這裡鍵盤代碼(event.which):Enter鍵是13,空格鍵是32,當按壓回車時會觸發AJAX事件,如果按壓的是空格則需要排除四種元素:text、textarea、tel、number,該方法在觸發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)

表單序列化之前執行的回調函數,在取回值之前提供一個機會去操縱表單,參數含義如下:

element:jquery包裝的表單對象

options:Ajax對象中的選項屬性

注:如果該方法傳回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成功後執行,去除進度條,去掉觸發元素禁用狀态,在drupal的ajax 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上,關于指令因為涉及前後端配合,本系列将在專門的主題中詳解,

3、throbber進度條預設樣式(由系統子產品原始定義):

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

覆寫定義:core/themes/stable/css/system/components/ajax-progress.module.css

4、在前端AJAX配置中,并無ajax回調概念(callback選項),僅有url,回調并不通過前端傳遞。

我是雲客,【雲遊天下,做客四方】,聯系方式見首頁,歡迎轉載,但須注明出處

繼續閱讀