Play1+angularjs+bootstrap ++ (idea + livereload)


我的web開發最強組合:Play1+angularjs+bootstrap ++ (idea + livereload)

首先說明我開發web的情況:

  • 個人開發
  • 前后端全部自己搞定
  • 網站類型多為傳統多頁面程序
  • 注重開發效率
  • Javascritp能力不強
  • 美術細胞很少

由於每個人情況不同,選擇技術及方案的重點也不同,所以內容僅供參考。對於我來說,這一套不論開發效率還是開發感受都是很好的。

編輯器

我選用的是idea Ultimate,它除了超強的java編輯功能之外,還提供了功能完善的play1和play2插件,angularjs插件,javascript/css編輯功能,讓人開發時事半功倍。這里截幾個圖說明一下:

 

Play1支持  
模板標簽的提示與補全 image
href屬性中,可提示controller及action image
routes文件中的提示 image
html中play標簽的高亮和格式化 image
從action中快速跳轉到view image
點擊紅框后自動打開: 
image
Play2支持 因為我不使用play2,所以未實際測試過。不過play2支持作為idea12的賣點之一,其支持程度應該不會低於play1。詳情可參看idea官網上的介紹。
對於play2中scala模板的支持應該是很多人想要的 image
Angularjs支持  
html屬性提示 image
Bootstrap支持 idea沒有對bootstrap有特別的支持,不過因為它本身對css的支持比較好,所以也可以方便的得到提示
class提示 image
Javascript支持 idea ultimate對javascript的支持非常強大,是我用過的編輯器中,對js支持最好的。不論是js文件,還是在html中嵌入的js代碼,高亮、格式化、查錯、提示等功能,都是一流
css支持 idea ultimate對css的支持也是非常強大

需要注意的是,上面提示的各功能,基本上都是idea ultimate才提供的(idea社區版中基本上沒有上述功能)。如果你經濟能力足夠並且喜歡idea,不妨購買license對它進行支持;否則的話,自行google解決。

Live Reload

LiveReload是指當我們修改了項目中的文件時,瀏覽器會自動刷新,顯示修改之后的效果。

在開發網站時,這個功能非常有用。想想你面前開着兩個顯示器,中間這個是編輯器,旁邊的那個是瀏覽器。每當修改了java/html/css/js代碼時,手不離鍵盤、光標不離編輯區,瀏覽器就自動刷新了!只需要眼睛輕輕一瞟,脖子都不用動,就能看到修改之后的效果,這種感覺何等舒暢!鈦合金的F5都不需要了!

這種方式對於web框架有要求,首先是修改文件后不需要重啟服務器。像純html/php/rails都天生支持,java中某些框架支持,而play1的支持相當優秀。不論修改html/css/javascript,還是java源代碼,甚至是配置文件,都不用重啟,直接刷新瀏覽器了。刷新時間大約為1秒到4秒。

另一點是:因為livereload檢查到文件修改后,只會觸發瀏覽器刷新一次,所以要保證一次刷新就可以看到修改后的效果。某些框架利用tomcat/jetty的自動重啟,無法很好的配合livereload。因為還沒有重啟完時,livereload就刷新了,取得的可能還是修改前的頁面,必須手動刷新多次才能確定看到的是修改后的效果。對於這種情況,livereload幾乎沒用。而play在刷新過程中,會阻塞http請求,可以保證一次刷新就拿到修改之后的頁面。

Livereload的官網是 http://livereload.com ,它支持mac/linux/windows,同時還有chrome/firefox的瀏覽器插件。它對windows的支持比較差,很容易崩潰,而且是收費的。所以我們只需要用它的瀏覽器插件就可以了(免費的),然后再找一個免費的替代器換掉服務器端。

我選擇的是: https://github.com/lepture/python-livereload ,它是一個python程序,以命令行方式啟動,可以跟livereload的瀏覽器插件通信,效果不錯。注意最好從github中下載源代碼安裝,因為通過pip或easy_install安裝的版本有點舊,使用過程中有問題。(不清楚現在是否已經更新)

使用如下:

cd myproject

cd app

livereload

然后啟用瀏覽器的livereload插件即可。

來段gif演示一下(注意每次刷新都是我按了ctrl+s后自動觸發的):

xxx

Why play, and why play1

參考我寫的另一個日志: http://freewind.me/blog/20120728/965.html

Why angularjs

曾經有一段時間,我對前端javascript框架很感興趣,試用了很多,比如backbone/knockout/knockback/angularjs/…(以及一大堆已經忘了名字的),其中有兩個讓我印象深刻。一個是backbone,一個是angularjs。

Backbone的優點在於學習成本很低,與jquery的思路接近,偏向於底層及手動控制。采用backbone的項目比較多,資料也比較多,社區也比較大,還有一些基於backbone發展起來的高一級的框架(如 https://github.com/backbone-boilerplate/grunt-bbb )。這個bbb我沒有用過,只是在js群中有人說使用它之后,開發效率比之前純backbone有很大的提高,所以這里提一下。對於技術較普通的團隊來說,使用backbone可能會比較保險一些。

而angularjs則是一個讓我感到驚艷的框架,相對於同類無數個mv**框架,它的優勢達到了數量級。如果用幾個詞來形容它,應該是:學習成本高,開發效率高,寫代碼時思路流暢。

它擁有雙向綁定、directive、直接改寫html標簽等特性,使用它你可以對現有的html標簽進行改進和增強,甚至還可以重寫一套完全屬於自己的html標簽。也許你會覺得像“雙向綁定”這種爛大街的特性有什么值得拿出來說的,關鍵在於anguarljs的設計非常統一,各個功能搭配得很流暢、一氣呵成。這種感覺就像是eclipse與idea在操作上的區別:idea雖然有很多功能eclipse也提供了,但是用起來總不像idea中那么流暢 — 不論編輯什么類型的文件,在idea中都可以使用非常類似的操作,得到非常類似的界面反饋。

Angularjs的學習成本比較高,主要原因是其設計思路與我們以前寫jquery代碼時有很大的不同,不能套用。Angularjs的核心思想就是“復用”,它的“復用”體現在"directive"上。Directive既是angularjs的核心,也是它的重點、難點和殺手級特性。簡單的說,directive可以是一個自定義的html標簽、或者屬性、或者注釋,它的背后是一些函數,可以對所在的html標簽進行增強,比如修改html的dom內容,增強它的功能(如用第三方js插件包裝它)。

編寫Directive比較復雜,需要理解它的內部原理才能定義出自己的directive。在掌握它以前,以前一些很簡單的事情可能都沒辦法做,容易讓人沮喪。比如在使用jquery時,經常會這樣操作:

$("#mydiv").dialog();

但這種寫法在使用angularjs的html頁面中,是無法使用的。你必須把它寫成一個directive(比如ui-dialog),然后在它的postLink()方法中,對傳入的element元素操作:

element.dialog()

如果不理解postLink的各參數以及它是如何被angularjs使用的話,很難寫出來。所以在使用angularjs的前期,很容易被卡住。

在學習angularjs時,一定要細讀官網提供的develop guide (http://docs.angularjs.org/guide/ ),把各章節讀懂,知道angularjs的內部運行原理。千萬別按jquery的方式學習,光看示例是絕對不夠的。

Angularjs的另一個殺手級特性,就是把流程控制、事件綁定等代碼,直接寫在html標簽上。這其實就是前面所說的directive的使用方式。先看一段代碼:

image

在這段html代碼中,你可以看到那些位於html標簽上由藍色背影標出來的內容,都是angularjs提供的directive。有的是綁定事件(如ng-click,ng-submit),有的是控制流程(如ng-repeat)。這種方式我非常喜歡,簡單直接,可讀性又很好。當然有人不喜歡這種方式,認為html就應該干干凈凈,應該把這些東西分享到javascript中,就像下面這樣:

$("form").submit(function() { … });

其實對於這種情況,angularjs也有相似的做法,即為該form定義一個directive,比如my-add-form,然后把那些邏輯代碼放到它里面:

<form my-add-form>…</form>

// js code

module.directive("myAddForm", function() {

    // the logic

});

不過這種方式對於一個不那么通用的邏輯來說有點重。所以我們通常還是采用在html標簽上寫控制,在controller中寫邏輯的方式來做,通過合理的分配,在可讀性與方便性之間取得平衡。

直接在標簽上綁定事情處理函數,可以減少大量的命名,減少無謂代碼,而且閱讀起來更直觀。想當初看backbone代碼時,發現有二分之一的代碼,都是通地css selector來獲取元素,再將其某個事件與某個函數綁定起來。這樣的代碼一旦寫完,html就不敢隨便動了。因為若更改了html結構、id或者css class,這邊的js代碼都可能無法正常執行。

熟悉angularjs以后,會發現實現前端效果時,開發效率很高。在寫html的同時,基本上就可以把大部分的交互效果寫出來。同時,angularjs以model為中心,在編碼時只需要考慮model。當改變了model的內容時,view就會自動更新,這可以讓我們需要關注的東西更少。使用了angularjs后,你會發現html標簽的表現力變強了,以前需要一些js插件實現的功能(比如簡單的tab、tree等),使用angularjs幾行代碼就可以實現,而且所有的東西都是可定制的。

比如一個tab:

<div>

<a ng-click="tab=1">Tab1</a>

<a ng-click="tab=2">Tab2</a>

<a ng-click="tab=3">Tab3</a>

</div>

<div ng-show="tab==1">This is tab1</div>

<div ng-show="tab==2">This is tab2</div>

<div ng-show="tab==3">This is tab3</div>

比如一個tree

<script type="ng/template" id="’node.html’">

{{node.name}}

<ul>

<li ng-repeat="node in node.children" ng-include="’node.html’"></li>

</ul>

</script>

<ul>

<li ng-repeat="node in rootNodes" ng-include="’node.html’"></li>

</ul>

我在做某個網站后台時,開始打算用angularjs,后來感覺功能比較簡單,就直接采用jquery,少引用一個庫。開始還好,很快就發現只要增加一點復雜的功能時,所花的時間就大大增加。如果同樣的功能使用angularjs來寫,會非常簡單。

單頁面程序以及前后端交互

一般認為前端mv**框架適合於單頁面程序,無刷新、局部更新的那種。前后端完全分離,之間以restful api交互,使用json交換數據。在前端做好router,當點擊了某個按鈕需要展示新內容時,直接由前端獲取並顯示另一個局部html頁面,同時調用某個restful api獲取json數據填充。這種程序,通常前端的功能比較復雜,而對后端要求較少。采用這類mv**框架,前端程序員們可以充分發揮自己的才智,完全使用javascript/css/html來實現功能。而對於后台,只需知道restful api接口即可。這是前端mv**推薦的方式,也是目前來說比較好的方式。其特點是“ 以前端js框架為主,后端為輔 ”。

Anguarljs對於這種方式,有着非常好的支持。它除了提供前端router外,還提供了一些與后台交互的service,如與ajax相關的$http,與restful相關的$resource;對於cookie與本地存儲支持也很好,基本上使用angularjs就可以把程序做完。后台可以使用各種語言、各種框架來提供restful api。比如,我嘗試過couchdb這個數據庫,它直接在數據庫層面提供了restful api作為外界操作數據庫的接口,angularjs與它配合起來,連服務端程序都不用了。

在開發android程序時,我也嘗試過將phonegap與angularjs結合起來,直接使用angularjs來實現程序。與后台之間通過restful api交互。最后雖然因為性能要求改用了android原生方式,但對於普通的安卓或ios應用來說,這種方式是一種很好的選擇,開發效率很高。

傳統多頁面程序

對於我來說,大部分的網站還是傳統多頁面的。比如一個信息管理系統的后台。這種情況下能否使用angularjs呢?

最開始的時候,我想采用單頁面的方式來做,按照前面所說的流程。但是很快遇到了不少麻煩:

  1. 頁面跳轉:這種網站頁面很多,在前端定義比較麻煩,因為需要寫長長的url,容易出錯,檢查也不方便。而使用play的模板引擎,可以直接寫@{Controller.action(params)},play會自動把它變為url,並且會檢查是否有拼寫錯誤,非常方便。
  2. 權限:頁面上某些按鈕的顯示與隱藏,取決於當前用戶的角色。所以每次顯示某個新頁面時,都需要后台傳過來一些json數據,例如:{ buttons: [{show: true}, {edit: false}]},來告訴前台顯示或隱藏哪些按鈕。有的時候這個操作非常繁瑣。
  3. 多次請求:當顯示一個新頁面時,可能需要多次請求。首先html模板一次,然后取json數據一次(或多次)。這樣給人的感覺就有點慢。雖然可通過一些手段(如緩存html模板,為每一個請求返回一個大的合並過的json數據),但由於模板顯示的時間與取得json數據的時間之間總有一些間隔,有時候還是會讓人覺得不太流暢,卡。

這幾個問題我想了很久也沒好辦法,甚至打算放棄angularjs,還是采用以前完全由服務端生成頁面的方式來做。好在最后改變了思路,“ 以后端框架為主,前端為輔 ”,找到了比較好的辦法:

  1. 后端能做的事情盡量在后端做,比如頁面跳轉,模板引擎,權限處理等,顯然后端有優勢
  2. 因為在angularjs中,各數據都要以model的方式由angularjs管理,所以在html中,將原來直接顯示的數據以json方式嵌入,賦給angularjs。這樣打開一個新頁面時,angularjs所需的全部數據都有了,不需要再次請求。
  3. 后端不提供restful接口,相反生成一個js文件,把各action以函數的方式暴露出來,讓angularjs直接調用,而不需要寫長長的url。既方便又不容易出錯。
  4. 前端完全不使用router,還是按照傳統的方式跳轉,整頁刷新

這樣就把前后端的優點結合起來了:前端專注於頁面顯示及效果,后端處理其它的雜事。不追求無刷新,而追求開發效率與寫代碼時的舒適性。

這種方式更注重后端的功能,對前端的美工要求較低。與前面單頁面程序的開發正好相反。我覺得應該根據程序的情況采用不同的方式,不要硬套。

關於angularjs的$resource

在angularjs中提供了一個service叫$resource:http://docs.angularjs.org/api/ngResource.$resource ,它可以通過一個url和一些參數,定義一個resouce對象。通過調用該對象的某些方法,可以與后台交互,並且直接得到經過它處理之后的數據。使用的感覺有點像我們在后端常用的dao。

這個$resource服務對於“單頁面,以restful api交互”的情況比較合適。它要求所給出來的url可以按restful api的方式調用,正好滿足適合這種情況。

但對於“傳統多頁面程序”不好用,特別是那種信息管理系統。因為它們的url形式並不重要,是否restful也不重要,只要提供get/post兩種方式,能把參數傳過去就行了。如果在這里使用$resource,按它的規定來套,需要花很多心思來設計url,非常痛苦。我在這里卡了很長時間,因為我想不明白,為什么這個$resource看起來很好,但用起來就是不對勁呢。最后終於想通,原來我的情況不需要restful api。

最后我的做法是,在服務端把各action收集起來,生成一個js文件,在這個文件里把action以js函數的方式暴露出來,供angularjs直接調用(內部使用了angularjs提供的$http服務,而沒有用$resource)。在本例中,這個js文件中定義了一個叫JsRoutes的object供使用。

Angularjs調用它的方式是這樣的:

function Ctrl($scope, JsRoutes) {

    $scope.submit = function() {

        JsRoutes.Users.create.post({

              username: username,

              password: password

        }, function(res) {

              alert("ok");

        });

    }

}

這樣,當我需要向后台傳user相關的數據時,直接調用預定義的JsRoutes.Users.create即可,而不需要關注它對應的url到底是什么。

這個js文件的代碼是這樣的:

angular.module('JsRoutes', []).factory('JsRoutes', function ($http) { var defaultErrorHandler = function (data, status, headers, config) { alert('Sorry, server responses ' + status + ' error: ' + data); }; // angular post json by default, change it to key value pairs var keyValuesTransformFn = function (d) { return jQuery.param(d); }; var commonHandler = function (method, url, params, data, success, error, config) { config = config || {}; config.method = config.method || method; config.params = config.params || params; config.data = config.data || data; config.url = config.url || url; config.timeout = config.timeout || 120 * 1000; var postType = config.postType || 'form'; if (postType === 'form') { config.transformRequest = keyValuesTransformFn; config.headers = config.headers || {}; config.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; // config.headers['Accept'] = "application/json, text/html, text/plain, */*"; } $http(config).success(success).error(error || defaultErrorHandler); }; var jsRoutesHandler = function (path) { return { get: function (params, success, error, config) { commonHandler('get', path, params, {}, success, error, config); }, post: function (data, success, error, config) { commonHandler('post', path, {}, data, success, error, config); } } }; return { Application: { "index": jsRoutesHandler('/Application/index'), "toAction": jsRoutesHandler('/Application/toAction'), "showViewSource": jsRoutesHandler('/Application/showViewSource') }, Admins: { "index": jsRoutesHandler('/Admins/index') } }; });

采用這種方式后,在前端js代碼中完全不需要跟url打交道,因為它們都是在服務器端根據routes文件生成的。以后url有什么變化,js這里不需要修改一行代碼。

另外需要注意的是,angularjs默認會向服務端發送json格式的數據,而play對key-value形式的數據處理的比較好,所以我就把它默認值改為了’application/x-www-form-urlencoded’

Angularjs中如何集成第三方js插件

有時候需要用一些第三方插件,比如datepicker,slider,或者tree等。以前的做法是直接通過jquery取得某個元素,然后調用某個方法即可。但在angularjs中,不能直接這么寫,必須寫在directive中。

有一個叫augular-ui的項目: https://github.com/angular-ui/angular-ui ,已經集成了一些常用的插件(來自jqueryui),很方便。但如果還是需要自己定義,該怎么做呢?

基本的思路就是,創建一個directive,把調用jquery插件的代碼放在它里面。這里以jqueryui的slider為例:

image

樣子如上,其示例在: http://jqueryui.com/slider/

在jquery中,它的調用方式是這樣的:

<div id="slider"></div>

$("#slider" ).slider({

    min: 0, 
    max: 100,

    value: 10,

    step: 5    
});

 

而在angularjs中,我們需要定義一個directive,假設是ui-slider:

app.directive('slider', function () { return { require: '?ngModel', restrict: 'A', link: function (scope, element, attrs, ngModel) { var opts; opts = angular.extend({}, scope.$eval(attrs.slider)); var slider = element.slider({ min: opts.min || 0, max: opts.max || 100, step: opts.step || 10, value: attrs.ngModel && scope.$eval(attrs.ngModel) || 50, slide: function (event, ui) { if (ngModel) { scope.$apply(function () { ngModel.$setViewValue(ui.value); }) } } }); scope.$watch(attrs.ngModel, function (v) { slider.slider({ value: v }); }); } }; });

在html中的調用方式是:

<div slider="{min:0,max:500,step:5}" ng-model="row.width"></div>

在directive中的代碼比jquery的多了不少,不過主要是增加了與雙向綁定有關的兩個函數。例如在"slide: function(event, ui)“中,是把sider的值傳回給某個model(本例中為row.width)。在后面的scope.$watch中,是把model的值傳給slider。這樣當拖動slider上的刻度時,row.witdh的值會自動改變;或者改變了row.width的值之后,slider的刻度也會自動變化。

基本的思路就是這樣,更詳細的需要看相關文檔。與angularjs相關的就講到這里,在這里你可以看到很多angularjs的可執行的例子:https://github.com/angular/angular.js/wiki/JsFiddle-Examples ,可以直接感受。

最后需要補充的兩點:

  1. Angularjs的社區氣氛很好。其google group人氣很旺,有問題很快就能得到詳細的回復
  2. Angularjs對IE6/7支持不好。如果一定要支持ie6/7,可考慮backbone或其它框架。

Bootstrap

Bootstrap是像我們這樣缺少藝術細胞的程序員的福音。只需要記一些css class和某些js component的用法,就可以做出看起來比較美觀、專業的頁面效果出來。雖然過不了多久,人們也許就會對它出現審美疲勞,但好在它的社區已經形成,已經有不少基於它的模板出來,相信以后會有更多更美觀的bootstrap theme可供選擇:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM