AMP

載入與顯示伺服器內容

專家模式關閉

專家模式

使用專家模式隱藏為初學者設計的網頁開發指南。

將產品新增至我們的網站

現在回到 Chico's Cheese Bikes 頁面並新增一些產品吧!我們的目標是在網站上新增一個頁面,讓我們能夠

  • 顯示每個產品的產品名稱、圖片、價格和評分。

  • 篩選產品,僅顯示特定類別的產品 (例如:自行車、安全帽、手套等)。

  • 依價格從高到低或從低到高排序產品列表。

執行此操作的最佳方式是什麼? 我們可以使用 HTML、CSS 和 AMP 元件來呈現單一產品,然後針對要新增的每個產品手動複製。 但是,這會產生一些嚴重的問題。

首先,每次我們更新產品時,都必須更新 AMP 網站的程式碼,並再次發布到網路上。 如果產品缺貨或我們進行促銷,我們的開發人員就必須更新所有產品頁面。 這是一種冗長且可能容易出錯的方式,無法讓我們的網站與產品目錄保持同步。

其次,我們需要能夠僅顯示產品的特定子集,或變更產品出現的順序。 在 AMP 中,沒有辦法重新組織或篩選頁面上已呈現的內容。 若嘗試使用繫結和狀態變數執行此操作,將需要大量程式碼,且在產品和類別更多時會變得難以管理。

相反地,我們希望產品資訊在伺服器上獨立於我們的網站維護。 當我們的網站載入時,我們希望連線到該伺服器、下載最新的產品資料,並以一致的方式顯示該資料。 我們可以定義一個由 HTML、CSS 和 AMP 元件組成的範本,描述單一產品在我們網站上的外觀。 稍後,當伺服器傳回產品資料時,每個產品都會套用至範本並新增至頁面。 接著,我們只需從伺服器取得一組新的篩選或重新排序的產品資料,再次將結果套用至範本,最後在螢幕上顯示這些結果,即可篩選或重新排序螢幕上的元素。

我們用來擷取和顯示伺服器資料的元件稱為 <amp-list>。 我們也將使用 <amp-mustache> 範本,我們在討論表單時,首次在中階訓練中提到它。

載入與顯示伺服器內容

在我們了解如何使用 AMP 擷取伺服器資料之前,讓我們先討論伺服器如何提供資料。

您可以將提供資料的伺服器視為檔案櫃。 伺服器具有由一或多個端點建構而成的 API (應用程式介面)。 每個端點都可以透過唯一的 URL 存取,並傳回不同的資料集合。 在我們的類比中,API 會是櫃子內資料夾的集合,端點會是櫃子中的個別資料夾,而 URL 位址會是貼在每個資料夾頂端的標籤,以便更容易找到。

我們將使用 <amp-list> 元件擷取和顯示伺服器資料。 <amp-list> 元件連線到指定 URL 的遠端 JSON API 端點以擷取資料。 <amp-list> 元件也包含 <amp-mustache> 範本。 從伺服器傳回的資料中的每個項目都會個別套用至 <amp-mustache> 範本,且結果會新增至頁面。 例如,下列程式碼會擷取名稱清單,並以 <p> 標記的集合形式顯示在頁面上

<amp-list width="auto" height="100" layout="fixed-height" src="https://some.url/data.json" binding="refresh">
    <template type="amp-mustache">
        <p>{{name}}</p>
    </template>
</amp-list>

<amp-list> 的 src 屬性包含伺服器端點的 URL,該端點提供要在頁面上顯示的資料。 依預設,<amp-list> 預期伺服器會以 JSON 物件回應,其中包含名為 items 的屬性,其中包含要在螢幕上顯示的物件陣列。 以上一個範例來說,從伺服器傳回的資料可能如下所示

{
    "items": [
        {
            "name": "Alice",
            "age": 42
        },
        {
            "name": "Bob",
            "Age": 55
        },
        {
            "name": "Carol",
            "Age": 28
        },
        {
            "name": "Dan",
            "age": 22
        }
    ]
}

我們與 <amp-list> 搭配使用的範本是 <amp-mustache> 範本。 這表示我們不會使用屬性繫結括號語法來顯示文字。 相反地,我們可以使用雙括號 (或 mustache) 語法將值嵌入範本中。 mustache 範本中包含的變數名稱位於伺服器傳回的每個個別元素的內容中。 因此,在我們上面的範例中,{{name}} 專指傳回的 items 陣列中每個物件的 name 屬性。 我們可以使用 mustache 語法將變數插入標記和屬性 (例如 <amp-img> 元件的 src 屬性) 中。

我們在中階課程中詳細討論了 <amp-mustache> 範本。 如有必要,請參閱 <amp-mustache>文件或先前的課程,以複習此範本樣式。

<amp-list> 範本的輸出不會免除 AMP 的版面配置最佳化。 這表示在要求任何伺服器資料之前,AMP 會在頁面上保留特定空間來放置結果。 如果伺服器傳回的資料無法在可用空間中顯示,AMP 會嘗試配置額外空間。 為了讓 AMP 更可能允許 <amp-list> 擴展,請將 <amp-list> 元件放在頁面上的最後一個位置。

由於對伺服器的要求需要時間,且不保證成功,我們可能希望偵測要求何時正在進行中或何時失敗。 這可讓我們顯示正在載入資料的通知或顯示錯誤訊息。 為此,AMP 提供了 placeholderfallback 屬性。 在下列範例中,在 <amp-list> 向伺服器要求資料但尚未收到回覆後,它會顯示標記有 placeholder 屬性的元素。 如果對伺服器的要求未及時傳回或傳回錯誤代碼,則會顯示具有 fallback 屬性的元素

<amp-list src="https://foo.com/list.json" binding="refresh">
    <div placeholder>Loading ...</div>
    <div fallback>Failed to load data.</div>
</amp-list>

由於動態載入和顯示資料對於現代網頁開發非常重要,<amp-list> 具有許多功能。 值得花一些時間檢閱 <amp-list>文件,以瞭解一些其他組態選項。

案例研究:建置影片網站

為了更瞭解如何使用伺服器的動態內容建置網站,讓我們考慮一個影片網站。 影片網站的每位訪客都會看到相同的版面配置,但幾乎所有填滿該版面配置的影片都是使用者獨有的。 因此,舉例來說,所有使用者可能會看到推薦影片區段,但該區段中的影片對於每位使用者而言都不同,由伺服器提供。

影片網站版面配置範例

影片網站通常由符合特定主題的影片群組組成。 其中一個群組通常用於各種推薦影片,而其他群組可能是與熱門主題相關或由特定內容創作者發布的影片。 這些群組中的每一個都包含固定數量的影片。 群組具有標題、可能是一個圖示,以及可能是行動號召 (例如訂閱按鈕或關閉按鈕)。 每部影片都會顯示縮圖、影片長度、標題、創作者、觀看次數和發布日期。 無論我們從伺服器取得哪些影片,都會顯示關於這些影片的相同資料。 這是資料的範本。

影片網站上的骨架載入

當影片網站首次載入時,我們可以更清楚地看到範本。 請注意,所有影片列都缺少標題。 另請注意,所有影片都具有空的縮圖和實心方塊,而不是標題、創作者名稱或任何其他資訊。

這種策略類型稱為「骨架載入」。 其目的是預覽網站結構,並指出內容最終將載入的位置,同時網站會連線伺服器以取得資訊。 一旦伺服器傳送填滿每個群組的群組和影片名稱,網站就會更新以將骨架載入區塊取代為從伺服器下載的真實資料。

那麼,從我們的影片網站範例中,主要的重點是什麼? 在開發嚴重依賴動態內容的網站時,目標是專注於所有使用者都相同的網站結構。 這包括頁面的版面配置、導覽和選單系統,以及將容納動態內容的容器的外觀和風格。 在 AMP 中,一旦我們配置好頁面的靜態元素,我們就會使用 <amp-list> 將動態內容載入到我們設定的插槽中。

練習 3:重新建立影片網站

若要開始使用 <amp-list>,讓我們重新建立影片網站的一小部分:推薦影片集合。 我們的推薦影片集合將包含伺服器為我們的使用者選取的六部影片。 我們將在網站上的影片使用 <amp-youtube>。 當我們的網站連線伺服器以擷取影片資訊時,我們將使用骨架載入向使用者顯示結構,並指出正在發生某些事情。

我們不會在 Chico's Cheese Bikes 專案中建置此產品頁面。 相反地,您可以使用 此 Glitch 作為此練習的起點。 注意:別忘了重新混合它,以便您可以編輯! Glitch 包含

  • 一些基本的 CSS 和 HTML,用於配置推薦影片頁面。

  • JSON 檔案中的範例影片資料。

  • 預先建置的伺服器,具有影片資料 API 端點和預先設定為查看該端點的 <amp-list> 元件。

注意:雖然您不需要建立伺服器即可完成此練習,但您必須遵循連結的 Glitch 範例中包含的 README 上的指示。 README 將引導您瞭解如何更新伺服器在其 CORS 組態中使用的環境組態。 如果您發現您的練習解決方案無法運作,即使其他所有項目似乎都正確,您可能需要依照 README 的指示更新環境變數中的位址。 如果您有興趣深入瞭解 CORS 是什麼及其重要性,請閱讀文件。

讓我們討論我們從伺服器擷取的影片資料結構

{
    "items": [
        {
            "id": "xEnifYNnDCA",
            "img": "https://...02.png",
            "title": "How to make Cheddar Cheese (Cloth Banded)",
            "creator": "Gavin Webber",
            "duration": "14:50",
            "date": "Jul 24, 2016"
        },
        ...
    ]
}

id 欄位指的是此影片的 YouTube 影片 ID。 img 欄位是指向縮圖的連結,可在 YouTube 影片初始化時用作預留位置。 titlecreatorduration 是關於影片本身的詳細資訊。 最後,date 是影片首次發布的日期。

使用 <amp-list><amp-youtube> 的文件和上述說明,更新推薦影片頁面以符合下列需求

  • 推薦影片的範本應包含 <amp-youtube> 元件、標題、創作者、影片長度和影片的發布日期。

  • <amp-youtube> 影片初始化時,它應該具有預留位置 <amp-img> 元件,其 src 來自伺服器資料。

  • <amp-list> 元件應具有預留位置 <div>,其中包含六個骨架載入影片持有者。

建議的樣式指南

  • <amp-list> 元件的 <amp-list> 範本內部包裝在具有指定類別 video<div> 中。

  • <amp-youtube> 應具有 width 470 像素和 height 280 像素,以及 responsivelayout

  • 推薦影片的範本可以使用 <h2> 作為影片標題; 使用 <p> 標記作為影片日期、創作者和長度; 以及額外的 <strong> 標記作為創作者。

  • <amp-list> 預留位置應包含具有指定類別 placeholder-container<div>

  • placeholder-container <div> 應包含六個 <div> 標記,每個標記都具有指定類別 placeholder-vid

  • 每個 placeholder-vid <div> 應包含三個 <div> 標記。 第一個應具有指定類別 vid-pl,而其他兩個應具有指定類別 title-pl

完成後,您的頁面應如下所示

結果

解決方案

解決方案可在 此 Glitch 範例中找到。 包含變更的頁面部分應如下所示

<main>
    <h2>Recommended</h2>
    <amp-list width="auto" height="600" layout="fixed-height" src="videos" binding="refresh">
        <template type="amp-mustache">
            <div class="video">
                <amp-youtube data-videoid="{{id}}" layout="responsive" width="480" height="270">
                    <amp-img src="{{img}}" placeholder layout="fill"></amp-img>
                </amp-youtube>
                <h2>{{title}}</h2>
                <p>Published on {{date}}</p>
                <p>
                    <strong>{{creator}}</strong>
                </p>
                <p>Duration: {{duration}}</p>
            </div>
        </template>
    </amp-list>
</main>

如果您在解決方案中新增了骨架載入,則應如下所示

<main>
    <h2>Recommended</h2>
    <amp-list width="auto" height="600" layout="fixed-height" src="videos" binding="refresh">
        <div placeholder>
            <div class="placeholder-container">
                <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
            <div class="placeholder-vid">
                <div class="vid-pl"></div>
                <div class="title-pl"></div>
                <div class="title-pl"></div>
            </div>
        </div>
        <template type="amp-mustache">
            <div class="video">
                <amp-youtube data-videoid="{{id}}" layout="responsive" width="480" height="270">
                    <amp-img src="{{img}}" placeholder layout="fill"></amp-img>
                </amp-youtube>
                <h2>{{title}}</h2>
                <p>Published on {{date}}</p>
                <p>
                    <strong>{{creator}}</strong>
                </p>
                <p>Duration: {{duration}}</p>
            </div>
        </template>
    </amp-list>
</main>

篩選和排序伺服器資料

到目前為止,我們已經練習了使用 <amp-list> 將動態內容新增至我們網站的基本知識,但在我們可以將產品頁面新增至我們的 Chico's Cheese Bikes 網站之前,還有一個功能要討論。 我們需要一種方法來篩選和排序 <amp-list> 元件內的內容。 我們將仰賴伺服器來協助我們。

根據我們目前所學到的知識,擷取我們所有產品資訊的合理 API 端點位址可能如下所示

https://pathto.ourserver.com/api/v1/products

我們需要一種方法來要求伺服器僅傳回特定產品類別。 若要執行此操作,我們可以在 API 端點的結尾新增查詢字串。 這不會變更我們要求資訊的位置,但會傳遞額外資訊以供伺服器處理。 此類位址可能如下所示

https://pathto.ourserver.com/api/v1/products?category=bikes

如需更多關於 URL 中包含哪些內容的資訊,請查看文件。

接下來,我們需要一種方法來告訴伺服器排序其傳回給我們的結果。 我們可以透過擴展我們用來篩選伺服器結果的查詢字串來完成此操作。 新增一個排序參數,告知伺服器要使用的排序類型,以及是否要從高到低 (遞減) 或從低到高 (遞增) 排序。 此類位址可能如下所示

https://pathto.ourserver.com/api/v1/products?category=bikes&sort=price-asc

注意:您不必在查詢字串中使用這兩個參數即可使用排序。 類別或排序可以單獨出現。 當它們一起使用時,必須以 & 符號分隔。

現在我們知道如何從伺服器取得篩選和/或排序產品的清單,但仍然不清楚要將什麼放入 <amp-list> 元件的 src 屬性中。 當使用者變更篩選和排序設定時,他們期望頁面自動更新。 我們需要回應使用者動作來更新 src 屬性。 這聽起來非常適合狀態變數和屬性繫結!

下列範例示範如何將 <amp-list> 與屬性繫結和狀態變數結合,以完成上述範例


<amp-state id="productSettings">
    <script type="application/json">
        {
            "category": "bikes",
            "sort": "price-asc"
        }
    </script>
</amp-state>
<amp-list src="https://pathto.ourserver.com/api/v1/products"
          [src]="'https://pathto.ourserver.com/api/v1/products' +
                 ‘?category=' + productSettings.category +
                 ‘&sort=' + productSettings.sort"
          binding="refresh">
    <template type="amp-mustache">
        ...
    </template>
</amp-list>

頁面第一次載入時,它會呼叫伺服器以取得所有產品。 但是,當使用者選取不同的篩選和排序選項時,productSettings 中的狀態變數會更新,且 src 繫結會評估並產生新的 src 值。 接下來,<amp-list> 元件會連線到更新的 src 位址以下載新資料。 一旦收到新資料,它就會像頁面第一次載入時的初始資料一樣套用至範本。 新內容將取代螢幕上的舊內容。

練習 4:建立可篩選的產品清單

現在是時候將產品頁面新增至我們的 Chico's Cheese Bikes 範例了! 如果您使用的是我們在先前的課程中連結的任何 Chico's Cheese Bikes Glitch 範例,您已經擁有完成此練習所需的伺服器程式碼。 您只需要確定您已遵循 README 指示,在您的環境變數中設定位址即可。 如果您尚未執行此操作,那麼我們建置的網站將無法從伺服器下載資訊。

我們需要做的第一件事是讓我們的產品頁面可從我們的首頁存取。 我們將在滑出式選單中新增連結。 在 index.html 上的導覽中,在「我們的故事」下方,新增下列程式碼

<li class="nav-item">
    <a href="/products.html">Our Products</a>
</li>

檢查 products.html 頁面。 產品頁面已包含

  • 完成此練習所需的 CSS。

  • 產品和索引頁面之間共用的版面配置部分,例如標頭和滑出式選單。

  • <amp-state> 和選項下拉式選單,以涵蓋伺服器預期的所有屬性和選項值。

  • 您完成練習應該需要的 AMP 元件指令碼。

若要完成此練習,我們必須將選取輸入連接到其對應的狀態變數、將狀態變數繫結到 <amp-list> 元件,並開發我們產品的範本。 目前,範本將包含產品圖片、產品名稱、產品的客戶評分和價格。

讓我們看看我們從伺服器擷取的產品資料結構

{
    "items": [
        {
            "id": "cheddar-chaser",
            "type": "bicycle",
            "url": "/pages/cheddar-chaser.html",
            "name": "Cheddar Chaser",
            "img": "https://...cheddar-chaser.jpg",
            "stars": "5.0",
            "price": 599,
            "description": "Lorem ipsum dolor sit amet, ..."
        },
        ...
    ]
}

idtype 欄位實際上僅供伺服器使用,因此您可以在此練習中忽略它們。 url 欄位代表此產品的產品頁面位址。 我們不會在此練習中實作這些頁面。 img 欄位包含產品圖片的 URL。 stars 欄位表示此產品的星級使用者評分。 price 欄位是此產品的美元價格。 最後,description 欄位是行銷文案,我們不會在此練習中使用,但在本訓練的其中一個選用練習中使用。

使用 <amp-bind><amp-list> 的文件和上述所有說明,建立符合下列需求的產品清單頁面

  • 當產品類型選取輸入更新時,它應將其新的選取值儲存到 ID 為 products<amp-state> 元件中的 category 狀態變數中。

  • 當排序依據選取輸入更新時,它應將其新的選取值儲存到 sort 狀態變數中。

  • 每當 categorysort 狀態變數更新時,<amp-list> 元件都應從伺服器擷取更新的產品清單。 注意:應傳送至伺服器的查詢參數與狀態變數 (categorysort) 同名。

  • 產品的範本應包含產品圖片、產品名稱、產品的使用者評分和價格。

建議的樣式指南

  • 範本的內容應包裝在具有指定類別 product-card<div> 中。

  • 每個產品圖片的大小應為 200 x 150 像素。

  • 關於產品的文字詳細資訊應包裝在具有指定類別 product-details<div> 中。

  • 產品名稱、評分和價格可以放置在具有指定類別 product-namestar-rankproduct-price<p> 標記中。

完成後,您的頁面應如下所示

結果

解決方案

包含產品清單的頁面部分現在應如下所示

<main>
    <div class="main-content">
        <h2 class="main-heading">Our Products</h2>
        <div class="filter-sort-selectors">
            <p>Product Type:</p>
            <select
                class="product-selector"
                on="change:AMP.setState({
                        products: {
                            category: event.value
                        }
                    })">
                <option value="all">All</option>
                <option value="bicycle">Bikes</option>
                <option value="helmet">Helmets</option>
                <option value="gloves">Gloves</option>
                <option value="basket">Baskets</option>
                <option value="bottle">Water Bottles</option>
            </select>
            <p class="sort-by">Sort By:</p>
            <select
                class="order-selector"
                on="change:AMP.setState({
                        products: {
                            sort: event.value
                        }
                    })">
                <option value="price-desc">Price: High-Low</option>
                <option value="price-asc">Price: Low-High</option>
            </select>
        </div>
        <amp-list id="amp-list-bikes" class="product-list" width="auto"
            height="600" layout="fixed-height" src="/products/filter"
            [src]="'/products/filter?sort=' + products.sort +
                   '&category=' + products.category"
            binding="refresh">
            <template type="amp-mustache">
                <div class="product-card">
                    <amp-img
                        width="200"
                        height="150"
                        layout="responsive"
                        alt="{{name}}"
                        src="{{img}}">
                    </amp-img>
                    <div class="product-details">
                        <p class="product-name">{{name}}</p>
                        <p class="star-rank">{{stars}} ★</p>
                        <p class="product-price">
                            ${{price}}
                        </p>
                    </div>
                </div>
            </template>
        </amp-list>
    </div>
</main>