你是如何使用Polymer構建一個單頁應用的?這個問題我們在Polymer團隊裡已經問過很多遍了。我們的答案(一如既往地)是“使用元件(component)!”。然而,使用新技術去解決現有的問題往往不會馬上得到顯著的效果。如何把一堆模組化元件組合到一個大型的實用的應用中去?
在本教程,我將會給你展示如何去構建一個功能完整的單頁應用:
- 完全使用Polymer的核心元素構建
- 使用響應式設計
- 使用資料繫結特性過渡檢視
- 使用URL路由和深層連結特性
- 可訪問鍵盤
- 按需動態載入內容(可選)
應用架構
設計佈局是開始一個專案的首要任務之一。作為核心元素集合的一部分,Polymer通過幾個佈局元素 來支撐應用程式的構架(<core-header-panel>, <core-drawer-panel>, <core-toolbar>)。這些元件本身就很好用,但是為了更快地開始專案,我們打算著重於<core-scaffold>。有了它你可以通過組裝幾個基本的元素就能做出一個響應式的移動端佈局。
<core-scaffold>的子元素可以是指定特定的元素或使用特定的標籤(或兩者一起使用)。舉個例子,使用<nav>元素建立應用抽屜選單。你可以在任意的元素裡使用navigation屬性(e.g <core-header-panel navigation>)。工具欄通過工具屬性標識。它的所有其他子元素都定義在主要內容區域裡。
例子
1 2 3 4 5 6 7 |
<body unresolved fullbleed> <core-scaffold id="scaffold"> <nav>Left drawer</nav> <core-toolbar tool>Application</core-toolbar> <div>Main content</div> </core-scaffold> </body> |
讓我們一起來深入這些內容的每一部分
抽屜選單
你放在導航元素裡的標記都定義在滑走的應用抽屜選單裡。為了我們的目標 ,我堅持使用標題(<core-toolbar>)和導航連結 (<core-menu>):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<nav> <core-toolbar><span>Single Page Polymer</span></core-toolbar> <core-menu selected="0"> <paper-item noink> <core-icon icon="label-outline"></core-icon> <a href="#one">Single</a> </paper-item> <paper-item noink> <core-icon icon="label-outline"></core-icon> <a href="#two">page</a> </paper-item> ... </core-menu> </nav> |
注意,現在<core-menu selected=”0″>被硬編碼為選擇第一個條目。我們以後會把它改為動態的。
工具欄
工具欄橫跨了頁面頂部幷包含了功能按鈕圖示。滿足這種功能的完美元素是<core-toolbar>:
1 2 3 4 5 6 7 |
<!-- flex makes the bar span across the top of the main content area --> <core-toolbar tool flex> <!-- flex spaces this element and jusifies the icons to the right-side --> <div flex>Application</div> <core-icon-button icon="refresh"></core-icon-button> <core-icon-button icon="add"></core-icon-button> </core-toolbar> |
主要內容
最後一部分是為你的內容而留的。它可以是任何的元素。<div>是一個很好的選擇:
1 2 3 |
<div layout horizontal center-center fit> <!-- fill with pages --> </div> |
fit屬性表示主要區域的內容會佈滿父元素的寬頻和高度,layout horizontal center-center屬性表示使用彈性框(flexbox)來使內容居中和垂直居中。
建立“檢視”
多檢視(或者多頁面)可以使用<core-pages>或者<core-animated-pages>來建立。在一次只展示一個子元素時,兩個元素都很有用。而使用<core-animated-pages>的好處是,它提供了更多的預設和靈活的頁面過渡。
下面的演示(demo)使用了<core-animated-pages>元素的slide-from-right過渡效果。首先,匯入元素定義和slide-from-right過渡效果。
1 2 |
<link rel="import" href="components/core-animated-pages/core-animated-pages.html"> <link rel="import" href="components/core-animated-pages/transitions/slide-from-right.html"> |
然後插入你的內容:
1 2 3 4 5 6 7 8 9 10 11 |
<div layout horizontal center-center fit> <core-animated-pages selected="0" transitions="slide-from-right"> <section layout vertical center-center> <div>Single</div> </section> <section layout vertical center-center> <div>page</div> </section> ... </core-animated-pages> </div> |
注意,現在 <core-animated-pagesselected=”0″>這行程式碼是硬編碼去選擇第一頁。不過我們以後會把它寫成動態的。
現在你應該擁有了一個基本的應用,但是這裡有一些小的問題需要注意。多虧了Polymer每個元素提供的佈局屬性和預設樣式,你可以不寫任何的CSS程式碼就可以實現一個響應式應用。當然,從material design調色盤裡獲取一些靈感,設定不到10 CSS規則就可以讓這個應該變得更好看。
使用資料繫結
我們擁有了一個應用,但這不值得一提。這離DRY還遠著。類似的標記在這裡重複出現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<nav> <core-menu selected="0"> <paper-item noink> <core-icon icon="label-outline"></core-icon> <a href="#one">Single</a> </paper-item> <paper-item noink> <core-icon icon="label-outline"></core-icon> <a href="#two">page</a> </paper-item> <paper-item noink> <core-icon icon="label-outline"></core-icon> <a href="#three">app</a> </paper-item> ... </core-menu> </nav> |
這同樣不是動態的。當使用者選擇一個選單條目時,頁面不會更新。幸運的是,這些問題都可以使用Polymer的資料繫結特性輕鬆解決。
自動繫結模板(template)
為了利用<polymer-element>外的繫結資料,包裝一個Yo應用?利用裡面的自動繫結<template>元素:
1 2 3 4 5 6 7 |
<body unresolved fullbleed> <template is="auto-binding"> <core-scaffold id="scaffold"> ... </core-scaffold> </template> </body> |
提示,<template>自動繫結元素允許我們在主要頁面裡使用{{}},表示式和on-*來宣告事件處理器。
使用資料模型( data model)簡化標記
利用資料模型來產生標記可以大量減少你寫標記的數量。在我們的案例裡,所有的選單條目和頁面都可以利用一對<template repeat>元素來呈現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<core-menu valueattr="hash" selected="{{route}}"> <template repeat="{{page in pages}}"> <paper-item hash="{{page.hash}}" noink> <core-icon icon="label-outline"></core-icon> <a href="#{{page.hash}}">{{page.name}}</a> </paper-item> </template> </core-menu> <core-animated-pages valueattr="hash" selected="{{route}}" transitions="slide-from-right"> <template repeat="{{page in pages}}"> <section hash="{{page.hash}}" layout vertical center-center> <div>{{page.name}}</div> </section> </template> </core-animated-pages> |
上面的標記由下面的資料模型來驅動:
1 2 3 4 5 6 7 8 9 |
<script> var template = document.querySelector('template[is="auto-binding"]'); template.pages = [ {name: 'Single', hash: 'one'}, {name: 'page', hash: 'two'}, {name: 'app', hash: 'three'}, ... ]; </script> |
注意,<core-animated-pages>和<core-menu>通過繫結它們的selected屬性來關聯在一起。現在,當使用者點選一個導航條目時,頁面會做出相應的更新。valueattr=”hash”設定告訴兩個元素在每個條目裡使用hash屬性作為選擇的值。
1 2 3 4 |
<!-- data-bind the menu selection with the page selection --> <core-menu valueattr="hash" selected="{{route}}"> ... <core-animated-pages valueattr="hash" selected="{{route}}"> |
URL路由(URL routing)和深層連結
<flatiron-director>是一個包裝了 flatiron director JS library(一個JS庫)的web元件。改變它的route屬性把URL#號(URL hash)更新到相同的值。
當我們想在頁面載入時維持上次的檢視時,資料繫結再次派上用場。把路由(director.js裡的director)、選單和頁面元素連線起來並使它們同步。當一個更新時,其他的同樣跟著更新。
1 2 3 4 5 |
<flatiron-director route="{{route}}" autoHash> ... <core-menu selected="{{route}}"> ... <core-animated-pages selected="{{route}}"> |
深層連結-當模板準備好時,初始化路由。
1 2 3 4 |
template.addEventListener('template-bound', function(e) { // Use URL hash for initial route. Otherwise, use the first page. this.route = this.route || DEFAULT_ROUTE; }; |
其他路由庫
如果你不喜歡<flatiron-director>,可以試試<app-router>或者<more-routing>。它們都是可以實現更復雜功能的路由(萬用字元,HTML5歷史API,動態內容)。我個人更喜歡<flatiron-director>,因為它簡單易用並且可以和<core-animated-pages>很好地配合使用
例子: <more-routing>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<more-route-switch> <template when-route="user"> <header>User {{params.userId}}</header> <template if="{{ route('user-bio').active }}"> All the details about {{params.userId}}. </template> </template> <template when-route="/about"> It's a routing demo! <a _href="{{ urlFor('user-bio', {userId: 1}) }}">Read about user 1</a>. </template> <template else> The index. </template> </more-route-switch> |
例子: <app-router>
1 2 3 4 |
<app-route path="/home" import="/pages/home-page.html"></app-route> <app-route path="/customer/*" import="/pages/customer-page.html"></app-route> <app-route path="/order/:id" import="/pages/order-page.html"></app-route> <app-route path="*" import="/pages/not-found-page.html"></app-route> |
鍵盤導航
鍵盤支援的重要性不僅僅是為了方便的訪問,它同樣會使SPA使用者剛到更開心。
<core-a11y-keys>是一個標準化瀏覽器鍵盤事件的嵌入元件。它可以在你的應用裡新增鍵盤支援。這裡有一個例子:
1 2 3 |
<core-a11y-keys target="{{parentElement}}" keys="up down left right space space+shift" on-keys-pressed="{{keyHandler}}"></core-a11y-keys> |
注意
事件的target屬性資料繫結到我們的自動繫結模組的parentElement屬性。在這個案例裡,它是<body>元素。
key屬性包含一個以空格分隔元素的列表,列表中包含了要監聽鍵位。當這些組合的其中一個被按下,<core-a11y-keys>觸發一個keys-pressed事件並呼叫你的回撥函式。
keys-pressed事件的處理器使用<core-animated-pages>的selectNext/selectPrevious API去進入下一頁或者返回上一頁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
template.keyHandler = function(e, detail, sender) { var pages = document.querySelector('#pages'); switch (detail.key) { case 'left': case 'up': pages.selectPrevious(); break; case 'right': case 'down': pages.selectNext(); break; case 'space': detail.shift ? pages.selectPrevious() : pages.selectNext(); break; } }; |
按需載入內容
如果你想使用者在你的應用裡導航時動態載入內容要怎樣做?只需一些改動,我們就可以支援動態載入頁面。
首先,更新資料模型,使它包含內容的URL:
1 2 3 4 5 |
template.pages = [ {name: 'Intro', hash: 'one', url: '/tutorial/intro.html'}, {name: 'Step 1', hash: 'two', url: '/tutorial/step-1.html'}, ... ]; |
然後改變選單連結指向page.url而不是#:
1 2 3 |
<paper-item hash="{{page.hash}}" noink> <a href="{{page.url}}">{{page.name}}</a> </paper-item> |
最後,使用我們的<core-ajax>好友來獲取內容:
1 2 3 |
<core-ajax id="ajax" auto url="{{selectedPage.page.url}}" handleAs="document" on-core-response="{{onResponse}}"> </core-ajax> |
你可以把<core-ajax>看作是一個內容控制器。它的url屬性資料繫結到selectedPage.page.url。這意味著,無論什麼時候一個新的選單條目被選中,XHR(XMLHttpRequest的縮寫,譯者注)就會去獲取相應的頁面。當core-response觸發時,onResponse就會把文件返回的一部分插入預先保留的容器裡。
1 2 3 4 5 6 7 |
template.onResponse = function(e, detail, sender) { var article = detail.response.querySelector('scroll-area article'); var pages = document.querySelector('#pages'); this.injectBoundHTML(article.innerHTML, pages.selectedItem.firstElementChild); }; |
潤飾和收尾
這裡有一些小技巧和訣竅你可以用來改善你的應用。
當一個選單條目被選擇後,關閉應用的抽屜選單(drawer):
1 |
<core-menu ... on-core-select="{{menuItemSelected}}"> |
1 2 3 4 5 |
template.menuItemSelected = function(e, detail, sender) { if (detail.isSelected) { scaffold.closeDrawer(); } }; |
為導航選擇條目設定不同的圖示:
1 2 3 |
<paper-item noink> <ore-icon icon="label{{route != page.hash ? '-outline' : ''}}"></core-icon> <core-animated-pages ... on-tap="{{cyclePages}}"> |
1 2 3 4 5 6 7 8 |
template.cyclePages = function(e, detail, sender) { // If click was on a link, navigate and don't cycle page. if (e.path[0].localName == 'a') { return; } e.shiftKey ? sender.selectPrevious(true) : sender.selectNext(true); }; |
結束語
現在,你應該瞭解使用Polymer和web元件構建的單頁應用的基本構架了。這可能和構建傳統的應用有所不同,但總的來說,元件讓事情變得簡單多了。當你重用(核心)元件和使用Polymer的資料繫結特性時,你可以寫更少的CSS/JS。可以寫更少的程式碼的感覺真好!