訂閱設定
簡介
此範例示範如何實作訂閱設定圖示下拉式選單。這讓使用者無需離開電子郵件即可變更其訂閱設定。它也適用於網站。
設定
我們使用 amp-list
元件,在電子郵件或頁面載入時,從伺服器查詢使用者目前的訂閱設定。
<script async custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></script>
我們將使用 amp-mustache
來呈現連結至伺服器回應的訂閱狀態的下拉式選單圖示。
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></script>
我們也將使用 amp-form
,以便在使用者從圖示下拉式選單中選取新設定時,更新伺服器上的使用者訂閱設定。
<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
最後,我們使用 amp-bind
來更新下拉式選單的狀態以回應事件。
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
實作
伺服器
伺服器回應位於 https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription
的 GET 和 POST 要求。
它以類似以下的 JSON 物件回應 GET 要求
{ "currentSubscription": "only-mentions", "options": [ { "value": "watching", "isSelected": false, "text": "Watching", "imgUrl": "/images/watching.jpg" }, { "value": "only-mentions", "isSelected": true, "text": "Only mentions", "imgUrl": "/images/only-mentions.jpg" }, { "value": "ignoring", "isSelected": false, "text": "Ignoring", "imgUrl": "/images/ignoring.jpg" } ] }
currentSubscription
和 isSelected
會根據使用者目前的訂閱設定而有所不同。isSelected
一次只對 options
中的一個物件為 true
。
伺服器預期 POST 要求會為其表單資料中的 nextSubscription
欄位指定上述其中一個訂閱設定 (例如 ignoring
)。然後,它會將使用者目前的訂閱設定更新為指定的值。
AMP-HTML
步驟 1:基本下拉式選單
我們從原生 select
元素開始建立下拉式選單,並使用我們支援的訂閱設定來填入。select
元素是絕佳的選擇,因為它在所有平台上都具有高度可存取性。
<select>
<option value="watching">Watching</option>
<option value="only-mentions">Only mentions</option>
<option value="ignoring">Ignoring</option>
</select>
步驟 2:擷取目前的訂閱設定
我們將 select
元素包裝在 amp-list
中,並將其 src
設定為我們的伺服器正在監聽 GET 要求的網址。這會查詢使用者目前的訂閱設定。
我們使用 amp-mustache
範本來呈現 select
元素,並確保對應於使用者訂閱設定的 option
元素具有 selected
屬性。若要瞭解程式碼為何重複,請考量 selected
屬性是 布林屬性,並檢閱 amp-mustache
限制。
最後,我們新增 預留位置和後備,讓使用者隨時掌握使用者介面的狀態,並優雅地處理任何問題。
<amp-list src="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" binding="refresh" items="." single-item layout="fill">
<template type="amp-mustache">
<select>
{{#options}}
{{#isSelected}}
<option value="{{value}}" selected>
{{text}}
</option>
{{/isSelected}}
{{^isSelected}}
<option value="{{value}}">
{{text}}
</option>
{{/isSelected}}
{{/options}}
</select>
</template>
<div placeholder>Loading...</div>
<div fallback>Something went wrong. Please refresh</div>
</amp-list>
請注意,amp-list
已使用 single-item
進行註解,因為我們的伺服器以單一邏輯值 (訂閱設定) 回應 GET 要求,而且我們只希望針對回應呼叫範本一次。設定 items="."
可確保範本可以存取回應物件的根欄位。
步驟 3:在選取時更新訂閱設定
若要在使用者選取新設定時更新伺服器上的使用者訂閱設定,我們先將 select
元素包裝在 AMP form
元素中,將其 method
設定為 post
,並將其 action-xhr
屬性設定為我們的伺服器正在監聽 POST 要求的網址。
select
元素的 name
屬性設定為 nextSubscription
。nextSubscription
是表單資料欄位的名稱,伺服器預期使用者的新訂閱設定會位於 POST 要求中 (請參閱伺服器章節),而 select
元素包含使用者選取的訂閱設定。
最後,我們在 select
元素的值變更時觸發表單提交。我們透過使用 on
屬性,將事件處理常式附加至 select
元素的 change
事件,並叫用 form
元素的 submit
動作來執行此操作。請注意,我們為 form
元素提供 id
,以便我們可以在事件處理常式中參照它。
您可以選取新的訂閱設定並重新整理頁面來測試程式碼。頁面載入時 select
元素的訂閱設定現在會是您選取的設定!
<amp-list src="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" binding="refresh" items="." single-item layout="fill">
<template type="amp-mustache">
<form action-xhr="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" method="post" id="form1">
<select name="nextSubscription" on="change: form1.submit;">
{{#options}}
{{#isSelected}}
<option value="{{value}}" selected>
{{text}}
</option>
{{/isSelected}}
{{^isSelected}}
<option value="{{value}}">
{{text}}
</option>
{{/isSelected}}
{{/options}}
</select>
</form>
</template>
<div placeholder>Loading...</div>
<div fallback>Something went wrong. Please refresh</div>
</amp-list>
步驟 4:提交時停用
從下拉式選單中選取新的訂閱設定之後,不清楚表單何時正在提交或已提交。此外,沒有任何措施可阻止使用者在表單仍在提交時嘗試選取新的訂閱設定。
理想情況下,select
元素會在表單提交時 停用。但是,此方法存在問題:已停用控制項的值不會與表單一起提交。在不同的情況下,我們可能會考慮使用 readonly
屬性來取代 disabled
屬性,以避免此問題,但 select
元素不支援 readonly
屬性。幸運的是,我們可以透過新的 隱藏 input
元素提交 select
元素的值來解決此問題,該元素不需要停用,因為使用者無法與其互動或看到它,我們確保它始終包含與 select
元素相同的值。
我們先新增一個隱藏的 input
元素,並將 select
元素的 name
移至隱藏的 input
元素,使其值能夠提交。為了使隱藏的 input
元素的值與 select
元素的值保持同步,每當 select
元素的值變更時,我們都會使用 AMP.setState
更新新的 nextSubscription
狀態變數,並將隱藏的 input
元素的 value
屬性 繫結至狀態變數。
若要停用 select
元素,我們需要一個狀態變數來表示表單目前是否正在提交,以便我們可以在 select
元素的 disabled
屬性繫結中使用它。我們重複使用 nextSubscription
狀態變數,方法是確保在表單未提交時將其設為 null
,方法是在 form
元素的 submit-success
和 submit-error
事件中將 nextSubscription
更新為 null
。最後,我們將 select
元素的 disabled
屬性繫結至 !!nextSubscription
,因為狀態變數在表單提交時為真值。我們對其每個 option
元素執行相同的操作,以防止使用者在表單提交時使用鍵盤在值之間切換。
<amp-list src="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" binding="refresh" items="." single-item layout="fill">
<template type="amp-mustache">
<form action-xhr="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" method="post" id="form2"
on="submit-success: AMP.setState({ nextSubscription: null });
submit-error: AMP.setState({ nextSubscription: null });">
<input type="hidden" name="nextSubscription" [value]="nextSubscription">
<select
on="change: AMP.setState({ nextSubscription: event.value }), form2.submit;"
[disabled]="!!nextSubscription">
{{#options}}
{{#isSelected}}
<option value="{{value}}" [disabled]="!!nextSubscription" selected>
{{text}}
</option>
{{/isSelected}}
{{^isSelected}}
<option value="{{value}}" [disabled]="!!nextSubscription">
{{text}}
</option>
{{/isSelected}}
{{/options}}
</select>
</form>
</template>
<div placeholder>Loading...</div>
<div fallback>Something went wrong. Please refresh</div>
</amp-list>
請注意,儘管 disabled
屬性是 布林屬性,我們仍然可以有效地繫結它,因為 當繫結運算式為 false
時,amp-bind
將移除屬性。
步驟 5:處理表單提交失敗
如果表單提交失敗,我們需要顯示錯誤訊息,並將 select
元素還原為先前的值。
若要在表單提交失敗時顯示錯誤訊息,我們將包含錯誤訊息的元素新增至 form
元素的子項。我們使用 submit-error
屬性註解子項元素。此屬性可確保元素僅在表單提交失敗後才可見。
將 select
元素的值還原為先前的值,需要繫結每個 option
元素的 selected
屬性。這需要將 select
元素的目前值維護在 currentSubscription
狀態變數中,並將元素先前的值維護在 previousSubscription
狀態變數中。
當 select
元素的值變更時,我們會將 currentSubscription
設定為新值,並將 previousSubscription
設定為元素先前的值。如果這不是元素的值第一次變更,我們會將這些值儲存在 currentSubscription
狀態變數中。否則,它會儲存在 amp-list
回應的 currentSubscription
欄位中。
我們透過繫結每個 option
的 selected
屬性,確保 select
元素選取的 option
與 currentSubscription
保持同步。最後,我們透過在 form
元素的 submit-error
事件中將 currentSubscription
設定為 previousSubscription
,在表單提交失敗時將 select
元素還原為先前的值。
以下程式碼片段已設定為讓表單提交失敗。測試新行為!
<amp-list src="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" binding="refresh" items="." single-item layout="fill">
<template type="amp-mustache">
<form action-xhr="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription?fail=true" method="post" id="form3"
on="submit-success: AMP.setState({ nextSubscription: null });
submit-error:
AMP.setState({
currentSubscription: previousSubscription,
nextSubscription: null
});">
<input type="hidden" name="nextSubscription" [value]="nextSubscription">
<select
on="change:
AMP.setState({
previousSubscription: currentSubscription || '{{currentSubscription}}',
currentSubscription: event.value,
nextSubscription: event.value
}),
form3.submit;"
[disabled]="!!nextSubscription">
{{#options}}
{{#isSelected}}
<option value="{{value}}" [disabled]="!!nextSubscription"
selected [selected]="(currentSubscription || '{{currentSubscription}}') == '{{value}}'">
{{text}}
</option>
{{/isSelected}}
{{^isSelected}}
<option value="{{value}}" [disabled]="!!nextSubscription"
[selected]="(currentSubscription || '{{currentSubscription}}') == '{{value}}'">
{{text}}
</option>
{{/isSelected}}
{{/options}}
</select>
<div submit-error>Something went wrong. Please try again</div>
</form>
</template>
<div placeholder>Loading...</div>
<div fallback>Something went wrong. Please refresh</div>
</amp-list>
步驟 6:圖示下拉式選單
僅使用電子郵件用戶端支援的 CSS,將 select
元素本身的樣式設定為看起來像圖示並不可行。相反地,我們將隱藏 select
元素的持續使用者介面 (顯示目前值的方塊),並在其位置顯示每個下拉式選單狀態的不同圖示。但是,我們會在點擊時保持顯示下拉式選單。
幸運的是,設定 select
的樣式會影響持續使用者介面,而設定 option
的樣式會影響下拉式選單。我們可以利用此事實來隱藏前者,但保留後者。在使用 CSS 隱藏元素的三種常見選擇 (display: none;
、visibility: hidden;
和 opacity: 0;
) 中,opacity: 0;
最適合,因為我們希望將元素保留在 Tab 鍵順序中,並繼續接收其點擊事件
.subscription-select { opacity: 0; cursor: pointer; }
我們也為 select
元素設定 cursor: pointer;
,因為它現在看起來像按鈕。
下一步是建立新的圖示型使用者介面。我們先建立一個 div
元素,其中包含每個訂閱設定的 amp-img
元件。div
元素使用以下 icon
CSS 類別進行註解,預設會隱藏所有圖片
.icon > * { visibility: hidden; }
對應於 select
元素目前狀態的 amp-img
元件會使用以下 visible
CSS 類別設為可見
.visible { visibility: visible; }
如果 select
元素的值未變更,則我們會從 amp-list
回應中的 isSelected
欄位判斷 select
元素的目前狀態。否則,我們會從 currentSubscription
狀態變數判斷目前的狀態。
儘管一次最多只會顯示一個 amp-img
元件,但每個元件仍會佔用頁面上的空間。這表示元件預設會一個接一個地垂直放置。使用 display
屬性來取代 visibility
屬性可以修正此問題,但這會導致圖示在訂閱設定變更之間閃爍,因為 AMP 不會預先載入開始時為 display: none;
的 amp-img
元件。相反地,我們使用以下 relative
和 absolute
CSS 類別,在 Z 軸上堆疊 amp-img
元件
.relative { position: relative; } .absolute { position: absolute; top: 0; right: 0; bottom: 0; left: 0; }
請注意,amp-img
元件彼此之間的堆疊順序並不重要,因為一次最多只顯示一個元件。
我們也使用這些 CSS 類別將圖示定位在不可見的 select
使用者介面後面,以便當使用者嘗試點擊我們的其中一個圖示時,他們實際上會點擊不可見的 select
元素並觸發其事件!我們將新的圖示保持在 select
元素後面,方法是將圖示的程式碼放在 select
元素的程式碼之前 (請參閱 不使用 z-index
屬性的堆疊)。
我們使用以下 icon-sized
CSS 類別,確保 select
元素的點擊目標邊界方塊與圖示的大小相同
.icon-sized { width: 30px; height: 30px; }
螢幕閱讀器可以從 select
元素取得他們需要的所有資訊,因此我們使用 aria-hidden
屬性讓圖示對螢幕閱讀器不可見。
最後,當 select
元素聚焦時,我們使用 :focus-within
虛擬選取器在圖示周圍顯示外框
.icon-container:focus-within { outline-offset: 2px; outline: 1px solid black; outline: -webkit-focus-ring-color auto 1px; }
完成!
<amp-list src="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" binding="refresh" items="." single-item layout="fill">
<template type="amp-mustache">
<form action-xhr="https://amp.dev.org.tw/documentation/examples/interactivity-dynamic-content/subscription_settings/subscription" method="post" id="form4"
on="submit-success: AMP.setState({ nextSubscription: null });
submit-error:
AMP.setState({
currentSubscription: previousSubscription,
nextSubscription: null
});">
<div class="icon-container relative icon-sized"
[class]="(nextSubscription ? 'disabled ' : ' ') + 'icon-container relative icon-sized'">
<div class="icon icon-sized" aria-hidden>
{{#options}}
<amp-img width="30" height="30" src="{{imgUrl}}"
class="{{#isSelected}}visible {{/isSelected}}absolute"
[class]="((currentSubscription || '{{currentSubscription}}') == '{{value}}' ? 'visible ' : ' ') + 'absolute'">
</amp-img>
{{/options}}
</div>
<input type="hidden" name="nextSubscription" [value]="nextSubscription">
<select
class="subscription-select absolute icon-sized"
on="change:
AMP.setState({
previousSubscription: currentSubscription || '{{currentSubscription}}',
currentSubscription: event.value,
nextSubscription: event.value
}),
form4.submit;"
[disabled]="!!nextSubscription">
{{#options}}
{{#isSelected}}
<option value="{{value}}" [disabled]="!!nextSubscription"
selected [selected]="(currentSubscription || '{{currentSubscription}}') == '{{value}}'">
{{text}}
</option>
{{/isSelected}}
{{^isSelected}}
<option value="{{value}}" [disabled]="!!nextSubscription"
[selected]="(currentSubscription || '{{currentSubscription}}') == '{{value}}'">
{{text}}
</option>
{{/isSelected}}
{{/options}}
</select>
</div>
<div submit-error>Something went wrong. Please try again</div>
</form>
</template>
<div placeholder>Loading...</div>
<div fallback>Something went wrong. Please refresh</div>
</amp-list>
如果本頁的說明沒有涵蓋您的所有問題,請隨時與其他 AMP 使用者聯繫,討論您的確切使用案例。
前往 Stack Overflow 未說明的特色?AMP 專案強烈鼓勵您的參與和貢獻!我們希望您能成為我們開放原始碼社群的長期參與者,但我們也歡迎針對您特別感興趣的問題提供一次性貢獻。
在 GitHub 上編輯範例