這一章將設計一個選項卡組件,選項卡組件在手持設備上用的比較多,下面是一個示意圖:css
<img src="http://www.xmlplus.cn/img/tabbar.png" class="img-responsive"/>web
在具體實現以前,想像一下目標組件是如何使用的,對於設計會有莫大的幫助。經過觀察,能夠將選項卡組件分爲容器部分和子項部分,正以下面的 XML 結構所展現的。chrome
<!-- 05-01 --> <Tabbar id="tabbar"> <TabItem id="home" label="首頁"/> <TabItem id="setting" label="設置"/> <TabItem id="logs" label="日誌"/> <TabItem id="about" label="關於"/> </Tabbar>
如今咱們把目光切換到選項卡組件的子項部分,來看看子項部分是如何分解的。經過示意圖,你能夠發現子項部分能夠分解爲子項容器以及包含一個圖標和一個文本的子級部分。瀏覽器
<!-- 05-01 --> <a id="tabitem"> <Icon id="icon"/> <span id="label">首頁</span> </a>
因此,如今咱們的目標已經很明確了,主要設計三個組件:圖標組件 Icon、選項卡組件的子項 TabItem 以及選項卡組件的容器 Tabbar。svg
因爲該組件比較簡單,因此能夠將三種子組件放置在同一層級。但請注意,咱們還有四個圖標組件,能夠建立一個子級用於容納它們。下面給出咱們的組件結構圖:函數
Tabbar/ ├── Tabbar ├── TabItem └── Icon/ ├── About ├── Home ├── Logs └── Setting
咱們從最簡單的開始,先看四個圖標組件,圖標組件主要經過封裝 SVG 文原本實現,因爲圖標文本較長,因此這裏僅截取每一個圖標文本的一段。測試
// 05-01 About: { xml: `<svg width="48" height="48" viewBox="0 0 1024 1024"> <path d="M507.577907 23.272727C240.142852..."/> </svg>` }, Home: { xml: `<svg width="48" height="48" viewBox="0 0 1024 1024"> <path d="M949.082218 519.343245 508.704442..."/> </svg>` }, Logs: { xml: `<svg width="48" height="48" viewBox="0 0 1024 1024"> <path d="M576 125.344l32 0 0 64-32 0 0-64Z..."/> </svg>` }, Setting: { xml: `<svg width="48" height="48" viewBox="0 0 1024 1024"> <path d="M512 336.664c-96.68 0-175.336 78...."/> </svg>` }
請注意,這些圖標位於虛擬目錄 /icon
之下,也就是你要像下面這樣導入:ui
// 05-01 xmlplus("ui", function (xp, $_, t) { $_().imports({Tabbar: {... }, TabItem: {...}}); $_("icon").imports({--這裏包含了四個圖標組件--}); });
下面來實現圖標組件 Icon,這裏的圖標組件與上面是不一樣的,它會根據輸入的圖標類型實例化不一樣的圖標。這樣設計能夠複用部分相同的代碼,避免冗餘。this
// 05-01 Icon: { css: "#icon { width: 1.5em; height: 1.5em; display: inline-block; }", opt: { icon: "about" }, xml: `<span id="icon"/>`, fun: function (sys, items, opts) { sys.icon.replace("icon/" + opts.icon).addClass("#icon"); } }
該組件的函數項根據輸入的圖標類型建立圖標組件並替換已有的 span 元素對象。注意,替換完後須要從新添加樣式。spa
按從內到外的原則,接下來實現選項卡組件的子項 TabItem。對於此組件,須要在組件的映射項中作一次異名的屬性映射,把 id 屬性值映射給內部的圖標組件的 icon 屬性。
// 05-01 TabItem: { css: `a#tabitem { display: table-cell; overflow: hidden; width: 1%; height: 50px; text-align: center; ... } #label { display: block; font-size: .75em; overflow: hidden; text-overflow: ellipsis; -webkit-user-select: none; } a#primary { color: #337ab7; fill: currentColor; }`, map: {"attrs": { icon: "id->icon" } }, xml: `<a id="tabitem"> <Icon id="icon"/> <span id="label">首頁</span> </a>`, fun: function (sys, items, opts) { sys.label.text(opts.label); function select(bool) { sys.tabitem[bool ? 'addClass' : 'removeClass']("#primary"); } return Object.defineProperty({}, "selected", { set: select}); } }
此組件提供了用於選項切換時選中與非選中狀態之間切換的接口。以供選項卡容器使用。
最後來看下選項卡組件 Tabbar 的實現。該組件偵聽了用戶觸擊選項卡時的事件,在偵聽器裏主要作兩件事:一是維持選項卡狀態的切換;另外一是派發一選項卡切換時的狀態改變事件。
// 05-01 Tabbar: { css: `#tabbar { display: table; width: 100%; height: 50px; padding: 0; table-layout: fixed; -webkit-touch-callout: none; } #tabbar { z-index: 10; background-color: #f7f7f7; backface-visibility: hidden; }`, xml: `<nav id="tabbar"/>`, fun: function (sys, items, opts) { var sel = this.first(); this.on("touchend", "./*[@id]", function (e) { sel.value().selected = false; (sel = this).value().selected = true; this.trigger("switch", this.toString()); }); if (sel) sel.value().selected = true; } }
至此,一個選項卡組件算是完成了,下面來看下具體的一個測試示例。注意,最好在 chrome 瀏覽器的移動模式下作測試,這樣 touchend
事件纔會生效。
// 05-01 Index: { xml: `<Tabbar id="index"> <TabItem id="home" label="首頁"/> <TabItem id="setting" label="設置"/> <TabItem id="logs" label="日誌"/> <TabItem id="about" label="關於"/> </Tabbar>`, fun: function (sys, items, opts) { this.on("switch", (e, target) => console.log(target)); } }
在組件 Index 中,你能夠偵聽來自選項卡的切換事件來作相應的操做。好比結合後續咱們介紹的視圖棧組件作頁面之間的切換操做。