Physical Address

304 North Cardinal St.
Dorchester Center, MA 02124

客製化 Contact Form 7 的代碼產生器 (tag generator)

因為阿竣的建議,加上另一個本來列在待辦事項中的功能,在 1.1.0 版做了了兩項更新:

  • 隱藏區域/地址欄位
  • 限制區域、排除區域

由於限制、排除區域的功能,原本 Denny Kuo 的函式庫中就已經實作了,因此只要提供相對應的資料就可以。但為了讓使用者可以更容易使用這項功能,因此在流程設計上還是有做一點改動。

Contact Form 7 的客製化流程

在開始繼續說明前,先整理一下客製化 Contact Form 7 欄位的流程。

  1. 註冊欄位名稱 (使用 wpcf7_add_form_tag())
  2. 定義處理常式 (handler),用以轉譯 (render) 實際呈現的 HTML 代碼
  3. 定義驗證用的篩選器 (filter),用以檢驗表單傳送的資料是否符合規範
  4. 設定 CF7 的代碼產生器

事實上,只要做完步驟 1 跟 2,你的客製化功能就能以手動輸入的方式,出現在 CF7 的表單中,步驟 3 跟 4 是為了讓使用者可以更容易使用而加強的功能。

我在鐵人賽的文章〈WordPress 客製化實務:替 Contact Form 7 加上地址欄位〉中,便說明步驟 1-3 的實際執行方式。

CF7 的兩種代碼欄位屬性

在開始本次的改版前,我們需要先了解 CF7 儲存資料的方式。

項目選項 (options)數值 (values)
代碼中呈現形式[tag_name option1 option2:attribute][tag_name "values1" "values2"]
可接收資料只接受英數與特定字元 (如 -_)可以接受任何數值
範例ID 屬性、類別屬性、必填欄位預設值、下拉式選單選項值
處理常式相關方法$tag->has_option('option_name')$tag->get_option('option_name')$tag->values

這邊需要特別注意的是,values 並沒有分類的功能,但可以透過標籤|值的方式做到類似分類的效果。然而,由於這種方法的值也只接受特定字元,無法用中文作為值,因此最後仍決定用單純的字串值來儲存欄位屬性。

根據本次改版目標,將資料類型做以下定義:

  • 用選項來處理隱藏區域/地址欄位
  • 用數值來處理限制/排除區域的屬性值

隱藏區域、地址欄位,我們比照 1.0.0 版中的命名規則,直接取名作 district-hiddenstreet-hidden

為了方便起見,我們將限制/排除區域的屬性值按照原本的函式庫命名:data-onlydata-except

因此,我們預設的地址代碼就會呈現以下形式:

[address address-001 district-hidden street-hidden "data-only: values" "data-except: values"]

定義完我們的數值後,我們就可以來接著規劃處理常式。有關處理常式的寫法,會再後續另外補充。

待修改完處理常式後,我們便要來著手進行 CF7 介面的客製化了。

客製化 CF7 的代碼產生器 (tag generator)

為了可以更直覺的輸入欄位值,因此在介面上希望使用文字區域 <textarea>,讓使用者可以透過「換行」的方式,輸入想要限定、排除的地區。

要客製化 CF7 的代碼產生器,會使用到 wpcf7_admin_init 這個勾點 (hook)。

add_action( 'wpcf7_admin_init', 'wpcf7_add_tag_generator_address', 25, 0 );

function wpcf7_add_tag_generator_address() {
	$tag_generator = WPCF7_TagGenerator::get_instance();
	$tag_generator -> add( 'address', esc_html__( 'address', 'wpcf7-twaddress' ),
		'wpcf7_tag_generator_address' );
}

function wpcf7_tag_generator_address( $contact_form, $args = '' ) {
  // 以下為代碼產生器的介面,後面再補充
}

wpcf7_tag_generator_address 這個函式中,便是用來定義代碼產生器表單的欄位。為了保持與 CF7 相同的使用體驗,因此在架構上我直接將 CF7 原外掛中 modules 內的欄位複製,並加以修改。

function wpcf7_add_tag_generator_address() {
	$tag_generator = WPCF7_TagGenerator::get_instance();
	$tag_generator -> add( 'address', esc_html__( 'address', 'wpcf7-twaddress' ),
		'wpcf7_tag_generator_address' );
}

function wpcf7_tag_generator_address( $contact_form, $args = '' ) {
	$args = wp_parse_args( $args, array() );

	$description = __( "Generate a form-tag for an address with Taiwan zipcode. For more details, see %s.", 'wpcf7-twaddress' );

	$desc_link = wpcf7_link( __( 'https://github.com/huanyichuang/wpcf7-twaddress', 'wpcf7-twaddress' ), __( 'my GitHub repository', 'wpcf7-twaddress' ) );
?>
<div class="control-box">
	<fieldset>
		<legend><?php echo sprintf( esc_html( $description ), $desc_link ); ?></legend>

	<table class="form-table">
	<tbody>
		<tr>
		<th scope="row"><?php echo esc_html( __( 'Field type', 'contact-form-7' ) ); ?></th>
		<td>
			<fieldset>
			<legend class="screen-reader-text"><?php echo esc_html( __( 'Field type', 'contact-form-7' ) ); ?></legend>
			<label><input type="checkbox" name="required" /> <?php echo esc_html( __( 'Required field', 'contact-form-7' ) ); ?></label>
			</fieldset>
		</td>
		</tr>

		<tr>
		<th scope="row"><label for="<?php echo esc_attr( $args['content'] . '-name' ); ?>"><?php echo esc_html( __( 'Name', 'contact-form-7' ) ); ?></label></th>
		<td><input type="text" name="name" class="tg-name oneline" id="<?php echo esc_attr( $args['content'] . '-name' ); ?>" /></td>
		</tr>

		<tr>
			<th scope="row"><?php echo esc_html( __( 'Options', 'contact-form-7' ) ); ?></th>
			<td>
				<fieldset>
				<legend class="screen-reader-text"><?php echo esc_html( __( 'Options', 'contact-form-7' ) ); ?></legend>
				<label><input type="checkbox" name="zip-hidden" class="option" /> <?php echo esc_html( __( 'Hide zipcodes', 'wpcf7-twaddress' ) ); ?></label><br />
				<label><input type="checkbox" name="district-hidden" class="option" /> <?php echo esc_html( __( 'Hide districts', 'wpcf7-twaddress' ) ); ?></label><br />
				<label><input type="checkbox" name="street-hidden" class="option" /> <?php echo esc_html( __( 'Hide address details', 'wpcf7-twaddress' ) ); ?></label><br />
				<label><input type="checkbox" name="full-addr-hidden" class="option" checked /> <?php echo esc_html( __( 'Hide full address', 'wpcf7-twaddress' ) ); ?></label><br />
				</fieldset>
			</td>
			</tr>

		<tr>
		<th scope="row"><label for="<?php echo esc_attr( $args['content'] . '-id' ); ?>"><?php echo esc_html( __( 'Id attribute', 'contact-form-7' ) ); ?></label></th>
		<td><input type="text" name="id" class="idvalue oneline option" id="<?php echo esc_attr( $args['content'] . '-id' ); ?>" /></td>
		</tr>

		<tr>
		<th scope="row"><label for="<?php echo esc_attr( $args['content'] . '-class' ); ?>"><?php echo esc_html( __( 'Class attribute', 'contact-form-7' ) ); ?></label></th>
		<td><input type="text" name="class" class="classvalue oneline option" id="<?php echo esc_attr( $args['content'] . '-class' ); ?>" /></td>
		</tr>

		<tr>
		<th scope="row"><label for="<?php echo esc_attr( $args['content'] . '-range' ); ?>"><?php echo esc_html( __( 'Location Range', 'wpcf7-twaddress' ) ); ?></label></th>
		<td><textarea type="text" 
			name="data-only" 
			class="values" 
			id="<?php echo esc_attr( $args['content'] . '-range' ); ?>" 
			placeholder="<?php echo 
				sprintf( 
					/* translators: %s is the line break for the placeholder of textareas. */
					esc_html__( '台北市%s台北市@中正區%s桃園市@龜山區|桃園區', 'wpcf7-twaddress' ),
					'&#10;','&#10'
			);?>"></textarea>
		</td>
		</tr>

		<tr>
		<th scope="row"><label for="<?php echo esc_attr( $args['content'] . '-exclusion' ); ?>"><?php echo esc_html( __( 'Location Excluded', 'wpcf7-twaddress' ) ); ?></label></th>
		<td><textarea type="text" 
			name="data-except" 
			class="values" 
			id="<?php echo esc_attr( $args['content'] . '-exclusion' ); ?>"
			placeholder="<?php echo 
				sprintf( 
					/* translators: %s is the line break for the placeholder of textareas. */
					esc_html__( '台北市%s台北市@中正區%s桃園市@龜山區|桃園區', 'wpcf7-twaddress' ),
					'&#10;','&#10'
			);?>"></textarea></td>
		</tr>

	</tbody>
	</table>
	</fieldset>
</div>

<div class="insert-box">
	<input type="text" name="address" class="tag code" readonly="readonly" onfocus="this.select()" />

	<div class="submitbox">
	<input type="button" class="button button-primary insert-tag" value="<?php echo esc_attr( __( 'Insert Tag', 'contact-form-7' ) ); ?>" />
	</div>

	<br class="clear" />

	<p class="description mail-tag"><label for="<?php echo esc_attr( $args['content'] . '-mailtag' ); ?>"><?php echo sprintf( esc_html( __( "To use the value input through this field in a mail field, you need to insert the corresponding mail-tag (%s) into the field on the Mail tab.", 'contact-form-7' ) ), '<strong><span class="mail-tag"></span></strong>' ); ?><input type="text" class="mail-tag code hidden" readonly="readonly" id="<?php echo esc_attr( $args['content'] . '-mailtag' ); ?>" /></label></p>
</div>
<?php
}

這裡面可以看到,在欄位中主要有兩種類別屬性 optionvalues,分別代表前面所提,CF7 代碼中的兩種數值屬性。

因此在上述的程式碼加入了兩個 checkbox 選項,以及兩個 <textarea> 區段。

透過 JavaScript 處理代碼產生器的字串

然而,加入了 <textarea> 之後,發現代碼產生器並不會將其中的數值帶入代碼中。因此,在本次的改版中,需要能夠在標籤中插入額外字串。

分析了原本 CF7 的檔案後,發現代碼產生器是透過 wpcf7.taggen.compose 來建構代碼字串。為了能夠擴充這項功能,我參考了 Contact Form 7 – Conditional Fields 這款外掛的做法:

// ------------------------------------
//      CF7 TAG GENERATOR OVERRIDE
// ------------------------------------

if (_wpcf7 == null) { var _wpcf7 = wpcf7}; // wpcf7 4.8 fix
var old_compose = _wpcf7.taggen.compose;
// ...before overwriting the jQuery extension point
_wpcf7.taggen.compose = function(tagType, $form)
{

    jQuery('#tag-generator-panel-group-style-hidden').val(jQuery('#tag-generator-panel-group-style').val());

    // original behavior - use function.apply to preserve context
    var ret = old_compose.apply(this, arguments);
    //tagType = arguments[0];
    //$form = arguments[1];

    // START: code here will be executed after the _wpcf7.taggen.update function
    if (tagType== 'group') ret += "[/group]";
    if (tagType== 'repeater') ret += "[/repeater]";
    // END

    if (tagType== 'togglebutton') {
        $val1 = jQuery('#tag-generator-panel-togglebutton-value-1');
        $val2 = jQuery('#tag-generator-panel-togglebutton-value-2');
        var val1 = $val1.val();
        var val2 = $val2.val();

        if (val1 == "") val1 = $val1.data('default');
        if (val2 == "") val2 = $val2.data('default');

        str_val = ' "'+val1+'" "'+val2+'"';

        ret = ret.replace(']', str_val+']');
    }

    return ret;
};

先將原始的 wpcf7.taggen.compose 存在另一個變數後,用 apply() 的方式來繼承原本建構的結果。接著才做字串的變更。

將上述的程式邏輯稍加改寫後,就變成了 address-tag-generator.js 內的程式。這裡主要做的判斷,是將原本 <textarea> 中的斷行取代為 ,,並將 address-rangeaddress-exclusion 中的值儲存為 "data-only: 值""data-except: 值" 的形式後,取代原本的字串:

( function ( $ ) {
	'use strict';
	if ( typeof wpcf7 === 'undefined' || wpcf7 === null ) {
		return;
	}
	wpcf7.taggen = wpcf7.taggen || {};
	const oldCompose = wpcf7.taggen.compose;
	wpcf7.taggen.compose = function ( tagType, $form ) {
		// original behavior - use function.apply to preserve context
		let ret = oldCompose.apply( this, arguments );
		if ( 'address' === tagType ) {
			const range = $( '#tag-generator-panel-address-range' ).val(),
				exclusion = $( '#tag-generator-panel-address-exclusion' ).val();
			ret = range.length
				? [
						ret.slice( 0, -1 ),
						' "data-only:',
						range.replace( /[\n\r]/gi, ',' ),
						'"]',
				  ].join( '' )
				: ret;
			ret = exclusion.length
				? [
						ret.slice( 0, -1 ),
						' "data-except:',
						exclusion.replace( /[\n\r]/gi, ',' ),
						'"]',
				  ].join( '' )
				: ret;
		}
		return ret;
	};
} )( jQuery );

接著透過 admin_enqueue_scripts 這個勾點,讓管理介面得以載入所需的 JavaScript 程式。

add_action( 'admin_enqueue_scripts', 'wpcf7_address_admin_script' );
function wpcf7_address_admin_script() {
	wp_enqueue_script( 'address-tag-generator',
		plugin_dir_url( __FILE__ ) . 'admin/js/address-tag-generator.js',
		array( 'jquery', 'thickbox', 'wpcf7-admin' ), false, true);
}

結語

雖然前面很冠冕堂皇的列出了 CF7 客製化的流程,但老實說,我這次並不是照這個流程走的。正因為沒有按照這個流程,先做 UI 才處理資料,導致在偵錯上花了不少時間,因此才會認真的反省開發流程。

藉由這次的實作,也了解到 CF7 針對多位元文字的處理,並沒有想像中的容易。

Eric Chuang
Eric Chuang

正職是廣告行銷人員,因為 Google Tag Manager 的關係開始踏入網站製作的領域,進一步把 WordPress 當成 PHP + HTML + CSS + JavaScript 的學習教材。此外,因為工作的關係,曾經用 Automattic 的 Underscores (_s) 替客戶與公司官網進行全客製化佈景主題開發。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料