您如今已經瞭解了 MEAN 應用程序的機制,接下來咱們將對第一期文章中建立的 MEAN.JS應用程序進行定製。咱們在第二期文章中對該應用程序有了一個大體的瞭解。在第三期文章中,我將演示該應用程序的基本 CRUD功能。您還會了解一些有關響應式 Web 設計和 Bootstrap 的內容。css
本系列其他部分將要構建的應用程序被命名爲 UGLI:User Group List and Information 應用程序。我從 2010年開始運營 HTML5 Denver User Group(前身是 Boulder Java User Group,更早之前是 Denver Java UserGroup),所以我是本地用戶組的狂熱粉絲,可是讓我不解的是一直沒有專門的軟件來運行用戶組。如今咱們就要解決這個問題了。html
許多用戶組都在 Meetup.com 創建了一個在線主頁。我使用 MEAN 和UGLI 應用程序的目標並非要取代 Meetup.com;相反,我想與它創建更深刻的集成。Meetup.com集中了運行成功的用戶組所需的大部分核心功能;註冊新用戶,發佈會議細節、處理 RSVP等等。可是對於用戶組領導者來講仍然缺失一些關鍵功能,包括管理一組會議主持人(presenter)並連接到幻燈片(slide deck)。UGLI能夠填補這方面的空缺。(參見 下載 得到完整的樣例代碼)。web
建立應用程序 UGLI 的第一個任務就是調整應用程序的標記(branding)。須要在應用程序的服務器端對 config 和 app 目錄作一些修改;另外要對客戶端的 public 目錄作一些修改。mongodb
首先從 config/env/all.js 中的元數據開始。將標題修改成 HTML5 Denver(或您選擇的用戶組),並將描述修改成 HTML5 Denver User Group,如清單 1 所示。bootstrap
'use strict'; module.exports = { app: { title: 'HTML5 Denver', description: 'HTML5 Denver User Group', keywords: 'MongoDB, Express, AngularJS, Node.js' },
config/env/development.js 中的標題也須要修改,如清單 2 所示。上篇文章中咱們已經瞭解到 development.js 和 all.js 會在運行時合併。後端
'use strict'; module.exports = { db: 'mongodb://localhost/test-dev', app: { title: 'HTML5 Denver' },
接下來,修改導航欄左上角顯示的品牌。爲此,須要編輯
public/modules/core/views/header.client.view.html。在大概第 9 列的地方找到 anchor 標記和 navbar-brand
類,將 body 修改成 HTML5 Denver
,如清單 3 所示。瀏覽器
<div class="container" data-ng-controller="HeaderController"> <div class="navbar-header"> <button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a href="/#!/" class="navbar-brand">HTML5 Denver</a> </div> <!-- ...snip... --> </div>
要驗證所作的修改,請在命令行輸入 mongod
啓動 MongoDB,而後輸入 grunt
啓動應用程序。在瀏覽器中查看 Web 應用程序,看看標記是否顯示在菜單和標題欄中。服務器
要完成標記修改,須要替換 public/modules/core/views/home.client.view.html中的標準文本(boilerplate),該文本顯示在主頁的正文中。建立一個名爲 home.client.view.html.original的副本,這樣就能夠在稍後往回引用(若是須要的話)。架構
該文件利用 Bootstrap框架的功能,確保您的網站從一開始就面向移動應用。在繼續以前,須要瞭解 Bootstrap 提供的 12 列的網格佈局。app
查看任意硬拷貝新聞或雜誌,您都會看到其中使用了列。有時,一副圖片或標題由於某種設計風格而需跨越多個列,可是一個基本的柱狀佈局構成了幾乎全部打印頁面的基礎。
Web 頁面也是如此。例如,訪問 TIME網站。您看到它的佈局也是基於列的。可是,當您將瀏覽器窗口的寬度從全屏縮小到很是窄的時候,注意會發生什麼。可見列的數量將隨着窗口變小而減小,並隨着窗口增大而增多。
這種效應被稱爲 響應式Web 設計,由於 Web 頁面會 響應 並調整設計來適應設備所要求的屏幕尺寸。現代 Web開發人員構建的網站能夠無縫地支持從最小的手持設備到擁有最大屏幕的臺式機或壁掛屏幕等各類設備。分別使用 http://m.* 和http://www.* URL 爲智能手機、平板電腦、筆記本等建立專門的、離散的網站,這種作法早已過期。
響應式 Web 設計 並非 一個全能的解決方案;相反,它是「一個外觀要求能夠適應全部設備的網站」。您不須要選擇用戶訪問網站所使用的設備類型,所以您的設計具有內置的靈活性,能夠相應地進行自我調整。
許多流行網站(包括 Facebook 和 Instagram)更多地是經過移動設備而不是傳統計算機來進行訪問的。Twitter的用戶羣絕大多數是移動用戶。Twitter 規範了其響應式 Web 設計策略並實現了與 Bootstrap 相同的開源化。Bootstrap 有12 列的佈局,能夠根據您用來定義列的 CSS 類進行縮小或放大。
請注意,MEAN.JS 應用程序中對 MongoDB、Express、AngularJS 和 Node.js 使用了四個列的佈局,如圖 1 所示。
圖 1. Bootstrap 的列布局示例
如今查看 public/modules/core/views/home.client.view.html 中的源代碼,如清單 4 所示,看看Bootstrap 的 12 列布局是什麼樣子的。
<div class="row"> <div class="col-md-3"> <h2><strong>M</strong>ongoDB</h2> </div> <div class="col-md-3"> <h2><strong>E</strong>xpress</h2> </div> <div class="col-md-3"> <h2><strong>A</strong>ngularJS</h2> </div> <div class="col-md-3"> <h2><strong>N</strong>ode.js</h2> </div> </div>
若是您向一個父 div
添加 class="row"
,那麼您能夠向子div
添加 class="col-_xx_-_N_"
屬性來將它們分紅幾個列。_N_
值必須介於 1 和 12之間,_xx_
值取決於您但願優化佈局的設備的尺寸:
xs
適用於極小設備(低於 768 像素寬)sm
用於小型設備(768 和 991 像素之間)md
適合中型設備(992 和 1,199 像素之間)lg
適合大型設備(1,200 像素或更高)查看 Bootstrap CSS 文檔的 網格系統小節,瞭解有關的更多信息。
因爲清單 4 中的每一個列針對中型(md
)設備進行了優化,所以若是在屏幕寬度低於 992 像素的設備上訪問該頁面,列將垂直堆疊而不是水平堆疊。將您的瀏覽器窗口變得足夠窄來觸發這一更改,如圖 2 所示。
圖 2. 移動設備上的響應式 Web 設計示例
如今,可使用咱們已經需到的知識,使用特定於 UGLI 的文本替換 home.client.view.html 中的標準文本。
首先,從 W3C HTML5 徽標頁面 下載256 像素的 HTML5 徽標,並將其複製到public/modules/core/img/brand/HTML5_Logo_256.png。而後使用清單 5 中的源代碼替換public/modules/core/views/home.client.view.html 中現有的 HTML。
<section data-ng-controller="HomeController"> <div class="jumbotron text-center"> <div class="row"> <div class="col-md-4"> <img alt="HTML5" class="img-responsive center-block" src="modules/core/img/brand/HTML5_Logo_256.png" /> </div> <div class="col-md-8"> <h1>The HTML story is still being written.</h1> <h2><em>Come hear the latest chapter at the HTML5 Denver User Group.</em></h2> </div> </div> </div> </section>
在較寬的瀏覽器窗口中查看網站時,HTML5 徽標會出如今文本旁邊,如圖 3 所示。
圖 3. 新的 UGLI 主頁
當您將瀏覽器窗口變得足夠窄時,徽標會出如今文本的上方,如圖 4 所示。
圖 4. 新的 UGLI 主頁,它會出如今移動設備上
使用 Bootstrap 能夠輕鬆地讓您的網站對移動應用程序變得更友好,我在爲客戶構建每一個新網站時都使用 Bootstrap 做爲基礎技術。
如今咱們將要在 MEAN 堆棧中處理 CRUD。
Meetup.com 能夠幫助我很好地管理用戶組活動。可是,在某個活動結束後,就時間方面而言,該活動的重要性不如當天晚上的談話。
換句話說,這個網站的一個用戶用例就是:「下次會議要討論什麼?」Meetup.com 能夠很好地知足這種用戶用例。
第二個用戶用例(「向我顯示與 MEAN 堆棧有關的全部談話,不論是何時發生的」 )正是我準備經過 UGLI應用程序解決的用例。要實現這個用例,必須圍繞一個新的名爲 Talk
的模型對象建立一個 CRUD
基礎架構。幸運的是,可使用一個 Yeoman 生成器來實現這個基礎架構。
在應用程序的根目錄,輸入 yo meanjs:crud-module talks
。響應提示:
Yes
,將 CRUD 模塊連接添加到菜單。3. 當生成器詢問要使用哪一個菜單時,接受默認設置(topbar
)。清單 6 顯示了交互式命令行序列。
模塊
$ yo meanjs:crud-module talks [?] Which supplemental folders would you like to include in your angular module? css, img, directives, filters [?] Would you like to add the CRUD module links to a menu? Yes [?] What is your menu identifier? topbar create app/controllers/talks.server.controller.js create app/models/talk.server.model.js create app/routes/talks.server.routes.js create app/tests/talk.server.model.test.js create public/modules/talks/config/talks.client.routes.js create public/modules/talks/controllers/talks.client.controller.js create public/modules/talks/services/talks.client.service.js create public/modules/talks/tests/talks.client.controller.test.js create public/modules/talks/config/talks.client.config.js create public/modules/talks/views/create-talk.client.view.html create public/modules/talks/views/edit-talk.client.view.html create public/modules/talks/views/list-talks.client.view.html create public/modules/talks/views/view-talk.client.view.html create public/modules/talks/talks.client.module.js
在清單 6 中,請注意,生成器建立了服務器端基礎架構(保存在 app 目錄中):路由、一個控制器、一個模型和一個單元測試。它還在public/modules/talks 目錄下構建了全部客戶端工件。
您稍後將向 Talk
對象添加一些自定義字段。在此以前,在瀏覽器中訪問網站,查看默認狀況下會獲得哪些內容。
單擊右上角的 Signin 連接,輸入本系列早些時候建立的用戶名和密碼,或者單擊Signup 並建立一組新的憑證。
完成登陸後,能夠在左上角看到一個 Talks 菜單。從菜單中選擇 New Talk打開一個 HTML 表單,其中提供了一個獨立的 Name 字段,如圖 5 所示。
圖 5. 自定義以前的 New Talk 表單
這是一個良好的開端,可是要捕捉 Talk
的全部屬性,您須要的不只僅是一個簡單文本。
要向 Talk
添加新字段,必須編輯 6 個文件 — 四個用於顯示,兩個用於持久性:
首先要處理持久性。解決方案一半用在服務器端,另外一半用在客戶端。
服務器端模型(在 app/models/talk.server.model.js中定義)是應用程序的原型。您將在其中命名字段,提供數據類型,驗證規則等等。
客戶端控制器(在 public/modules/controllers/talks.client.controller.js中定義)收集來自用戶的數據輸入,並經過 HTTP 請求將數據推到服務器。控制器還經過鏈接得到 JSON 數據,並提供給視圖以用於演示。
此架構的一個有趣之處是對象模型永遠不會離開服務器。對象是來自客戶機的數據的具體化實現,並在 HTTP 響應中序列化到 JSON。
該應用程序有兩個控制器(一個位於服務器端,另外一個位於客戶端),可是咱們只關心客戶端控制器。服務器端控制器只是將進入的 JSON推入到模型對象。所以在向模型添加額外字段時不須要對服務器端控制器作任何調整。客戶端控制器要進行一些調整來容納新的字段。
打開 app/models/talk.server.model.js,向服務器端模型添加新的字段,如清單 7 所示。您能夠看到展開的name
字段(如 圖 5
所示),同時還定義了兩個元數據字段:created
和 user
。
/** * Talk Schema */ var TalkSchema = new Schema({ name: { type: String, default: '', required: 'Please fill Talk name', trim: true }, created: { type: Date, default: Date.now }, user: { type: Schema.ObjectId, ref: 'User' } });
這個基於 JSON 的模式無需多加解釋。在定義新字段時,您能夠指定數據類型、默認值和錯誤消息,以顯示給必要的字段。您還能夠作出許多其餘優化。查看 Mongoosedocumentation,得到有關的更多信息。
對 description
、presenter
和 slidesUrl
添加新字段,如清單 8 所示。在本例中,description
和 presenter
都是必要字段。slidesUrl
字段是可選字段。
/** * Talk Schema */ var TalkSchema = new Schema({ name: { type: String, default: '', required: 'Please fill Talk name', trim: true }, description: { type: String, default: '', required: 'Please fill Talk description', trim: true }, presenter: { type: String, default: '', required: 'Please fill Talk presenter', trim: true }, slidesUrl: { type: String, default: '', trim: true }, created: { type: Date, default: Date.now }, user: { type: Schema.ObjectId, ref: 'User' } });
此時,您的服務器端後端已經準備好接收新字段。如今您須要處理客戶端控制器。打開public/modules/controllers/talks.client.controller.js,添加新的字段,如清單 9 所示。
// Create new Talk $scope.create = function() { // Create new Talk object var talk = new Talks ({ name: this.name, description: this.description, presenter: this.presenter, slidesUrl: this.slidesUrl }); // Redirect after save talk.$save(function(response) { $location.path('talks/' + response._id); }, function(errorResponse) { $scope.error = errorResponse.data.message; }); // Clear form fields this.name = ''; this.description = ''; this.presenter = ''; this.slidesUrl = ''; };
在 $scope.create
函數中,表格字段將被彙集到一個 JSON對象,並被髮送給服務器,以便實現持久存儲。從模型向控制器添加相應的字段後,您就實現了持久存儲。
如今咱們要將注意力轉移到演示層,這樣用戶就能夠查看新字段並進行交互。
查看 public/modules/talks/views/。有四個字段與 CRUD 生命週期有關:
打開 create-talk.client.view.html,如清單 10 所示。
<section data-ng-controller="TalksController"> <div class="page-header"> <h1>New Talk</h1> </div> <div class="col-md-12"> <form class="form-horizontal" data-ng-submit="create()" novalidate> <fieldset> <div class="form-group"> <label class="control-label" for="name">Name</label> <div class="controls"> <input type="text" data-ng-model="name" id="name" class="form-control" placeholder="Name" required> </div> </div> <div class="form-group"> <input type="submit" class="btn btn-default"> </div> <div data-ng-show="error" class="text-danger"> <strong data-ng-bind="error"></strong> </div> </fieldset> </form> </div> </section>
將與 Name
有關的代碼塊複製三次,以便支持Description
、Presenter
和slidesUrl
,如清單 11 所示。我將 Description
字段設置爲textarea
,而不是一個簡單的文本字段。一樣,我從 slidesUrl
字段移除了required
屬性,並將 input type
從 text
修改成 url
。
create-talk.client.view.html
<section data-ng-controller="TalksController"> <div class="page-header"> <h1>New Talk</h1> </div> <div class="col-md-12"> <form class="form-horizontal" data-ng-submit="create()" novalidate> <fieldset> <div class="form-group"> <label class="control-label" for="name">Name</label> <div class="controls"> <input type="text" data-ng-model="name" id="name" class="form-control" placeholder="Name" required> </div> </div> <div class="form-group"> <label class="control-label" for="description">Description</label> <div class="controls"> <textarea data-ng-model="description" id="description" class="form-control" placeholder="Description" required></textarea> </div> </div> <div class="form-group"> <label class="control-label" for="presenter">Presenter</label> <div class="controls"> <input type="text" data-ng-model="presenter" id="presenter" class="form-control" placeholder="Presenter" required> </div> </div> <div class="form-group"> <label class="control-label" for="slidesUrl">Slides</label> <div class="controls"> <input type="url" data-ng-model="slidesUrl" id="slidesUrl" class="form-control" placeholder="Slides Url"> </div> </div> <div class="form-group"> <input type="submit" class="btn btn-default"> </div> <div data-ng-show="error" class="text-danger"> <strong data-ng-bind="error"></strong> </div> </fieldset> </form> </div> </section>
在 Web 瀏覽器中,您新修改的 New Talk 頁面應當相似圖 6 所示。
圖 6. 自定義後的 New Talk 表單
若是對所作的更改感到滿意,請打開 edit-talk.client.view.html 並執行相應的更改,如清單 12 所示。
<div class="col-md-12"> <form class="form-horizontal" data-ng-submit="update()" novalidate> <fieldset> <div class="form-group"> <label class="control-label" for="name">Name</label> <div class="controls"> <input type="text" data-ng-model="talk.name" id="name" class="form-control" placeholder="Name" required> </div> </div> <div class="form-group"> <label class="control-label" for="description">Description</label> <div class="controls"> <textarea data-ng-model="talk.description" id="description" class="form-control" placeholder="Description" required></textarea> </div> </div> <div class="form-group"> <label class="control-label" for="presenter">Presenter</label> <div class="controls"> <input type="text" data-ng-model="talk.presenter" id="name" class="form-control" placeholder="Presenter" required> </div> </div> <div class="form-group"> <label class="control-label" for="slidesUrl">Slides</label> <div class="controls"> <input type="url" data-ng-model="talk.slidesUrl" id="name" class="form-control" placeholder="Slides Url"> </div> </div> <div class="form-group"> <input type="submit" value="Update" class="btn btn-default"> </div> <div data-ng-show="error" class="text-danger"> <strong data-ng-bind="error"></strong> </div> </fieldset> </form> </div>
請注意,用於編輯的 HTML 與以前修改的建立表單稍微有些不一樣。在編輯時,您已經有了一個 Talk
對象,所以data-ng-model
屬性將以徹底限定的方式引用字段,好比用 talk.name
而不是name
。在 Web 瀏覽器中查看修改,如圖 7 所示。
圖 7. 自定義後的 Edit Talk 表單
view-talk.client.view.html 頁面是對象的只讀視圖。用戶在保存新的 Talk
,更新現有的Talk
或從列表頁面中選擇 Talk
後未來到該視圖。如清單 13 所示作出修改。
<div class="page-header"> <h1 data-ng-bind="talk.name"></h1> <h2><em>by {{talk.presenter}} <span ng-if="talk.slidesUrl !== '' ">[<a href="{{talk.slidesUrl}}">slides</a>]</span></em></h2> <p>{{talk.description}}</p> </div>
前面提到 slidesUrl
是可選字段。在視圖頁面中,您將使用 ng-if
指令有條件地顯示字段(若是已填充)。在瀏覽器中查看頁面,檢查這一行爲,如圖 8 所示。
圖 8. 自定義後的 View Talk 表單
List 視圖是最後一個須要作出調整的視圖。打開 list-talks.client.view.html 並如清單 14 所示進行修改。
<div class="list-group"> <a data-ng-repeat="talk in talks" data-ng-href="#!/talks/{{talk._id}}" class="list-group-item"> <h4 class="list-group-item-heading" data-ng-bind="talk.name"></h4> <p><em>by {{talk.presenter}}</em></p> </a> </div>
請注意,這裏使用 data-ng-repeat
指令顯示了服務器返回的 talk 列表中的每一個talk。在瀏覽器中查看結果,如圖 9 所示。
圖 9. 自定義後的 List Talks 表單
此時,您已經瞭解了 MEAN 堆棧交互的各個方面。您使用 Bootstrap 的響應式 Web 設計功能確保您的網站可以適應全部設備,而不只限於傳統的有 101 個鍵和鼠標的傳統臺式機。您已經領略了使用 Yeoman 生成器嚮應用程序添加新 CRUD模塊的強大之處及其便利性。該生成器將原始工件放到正確的目錄中,您只須要對它們進行自定義便可。
範例代碼:wa-mean3src.zip