使用angularjs1.x構建前臺開發框架(一)——構建基礎架構
在web應用開發中,前臺模組正逐漸變得越來越厚重(這也是大勢所趨,瀏覽器的功能和效能都在迅速提升,一部分本來需要後臺支援的業務邏輯完全可以交給前臺來完成),進而演變出了前臺開發框架,而angularjs就是其中之一。但通常angularjs的學習往往侷限於資料雙向繫結、路由(ui-router或ng-route)或者自定義標籤這一類單點的功能,很少提及如何構建一個完整的可直接進行二次業務開發的web應用前臺框架(當然,同一個架構也不可能適用於所有的業務場景),下面就給出這樣一個例子。
例子的功能很簡單:實現一個選單,當點選選單項條目時,子介面切換為對應的選單項介面(但可以在此基礎上擴充套件開發業務模組介面,後續也會在這個骨幹框架的基礎上補充其他框架模組用於web開發)。
下面是專案程式碼結構圖:
下面是執行效果圖(很醜,因為沒怎麼調樣式,湊合看吧):
IDE版本和依賴庫版本:
IDE:WebStorm 10.0.3
依賴庫:
angular 1.5.1 下載地址:augularjs下載
jquery 3.1.1 下載地址:jquery下載
require 2.1.9 下載地址:requirejs下載
當這個框架工程構建完成後,我們可以在app目錄下寫業務涉及的介面,當然,這些業務介面必須在框架中註冊。不多說,下面給出程式碼乾貨:
index.html:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>歡迎</title> </head> <body> <div id="menu" ng-controller="frameworkCtrl" ng-include="menus.url"></div> <div ui-view style="min-width: 1280px; max-width: 1440px; margin: 58px auto 0; border:1px solid #F00" ng-animate="{enter: 'fade-enter'}"></div> <script type="text/javascript" src="lib/require.js" data-main="main"></script> </body> </html>
主html頁面:
第一個div為主選單模組,ng-controller為主選單模組定義控制器,ng-include則為主選單模組連結上(顯示)在控制器內定義的一個外部html(即執行效果圖上的選單介面),這個外部html在控制器內定義(即前面的frameworkCtrl中定義)。通常為模組定義控制器要求外部已宣告module的作用域(即外層模組必須宣告使用的ng-app),但這裡為什麼不需要呢?因為在下面的main入口函式有這樣一句話:
var injector = angular.bootstrap($("html"),[framework.name])
這句實際上是為html元素聲明瞭作用域(即framework這個module),效果等同於:
<html ng-app="framework">
這樣我們在為主選單模組定義控制器的時候,外部html元素已經聲明瞭module作用域,所以不會有問題。
第二個div為選單項介面,在本例中使用的是ui-router,因此路由標籤為ui-view(如果使用的是ng-route,標籤應為ng-view),當主選單觸發選單切換後,切換的對應選單項介面在這裡顯示(比如在主選單中點選了login,則對應的login子頁面會在此處顯示)。
基於框架的二次開發可以在這裡做許可權控制、頁面的排版(比如主選單div和選單項div可以進行樣式優化調整)或者為主選單引入一些選單樹控制元件。
main.js:/**
* Created by 李慶 on 2016/10/6.
*/
require.config({
"shim":{
"lib/angular":{
"deps":["lib/jquery-3.1.1"],
"exports":"angular"
},
"lib/angular-ui-router":{
"deps":["lib/angular"]
},
"lib/jquery":{
"exports":"$"
}
}
});
require(["framework/framework"],function(framework){
var injector = angular.bootstrap($("html"),[framework.name])
});
主(main)函式(入口函式):
在require.config中定義整個前臺框架的依賴庫,後續會專門寫一篇require使用的blog,這裡就不贅述了。第二個require則是為整個html宣告作用域。
當開發需要新增一些依賴庫時,需要在這裡宣告引用。在主函式裡不建議加入具體的業務處理程式碼,不過瀏覽器相容性的判斷或者session的處理可以放在這裡做。
framework.js
/**
* Created by 李慶 on 2016/10/6.
*/
define(["lib/angular","lib/angular-ui-router","framework/config/frameworkConfig",
"framework/controller/frameworkCtrl"],function(angular,router,frameworkConfig,frameworkCtrl){
var dependency = [
"ng",
"ui.router",
frameworkConfig.name
];
var framework = angular.module("framework",dependency);
framework.controller("frameworkCtrl",frameworkCtrl);
return framework;
}
)
框架module:
構造html所需的module供main函式使用,dependency是構造module的依賴module(module的名稱和module的檔名可能不同,如angular檔案的內的module名稱為ng,宣告依賴是要麼直接寫module名稱,要麼寫成檔名.name,如果用檔名.name,那麼必須保證檔案符合AMD規範),構造module完成後為module宣告控制器,即主選單介面所使用的frameworkCtrl。
框架支援多個路由樹,比如在雲端計算專案中,管理員和租戶看的選單功能是完全不同的,就可以在框架module中增加一個路由樹的依賴(每一個路由樹實際是一個獨立的module)。
frameworkConfig.js/**
* Created by 李慶 on 2016/10/6.
*/
define(["lib/angular","lib/angular-ui-router"],function(angular,router){
var serviceConfigs = ["$stateProvider","$urlRouterProvider","$controllerProvider",
function($stateProvider,$urlRouterProvider,$controllerProvider){
$urlRouterProvider.otherwise("/login");
$stateProvider.state("login",{
url:"/login",
template:"<span>login</span>"
});
$stateProvider.state("c1",{
url:"/c1",
template:"<span>Page c1</span>"
});
$stateProvider.state("c2",{
url:"/c2",
templateUrl:"app/views/c2.html",
controller:"c2.ctrl",
resolve:{
deps:function($q,$rootScope){
var deferred = $q.defer();
var dependencies = ["app/controllers/c2.js"];
require(dependencies,function(ctrl){
$rootScope.$apply(function(){
$controllerProvider.register("c2.ctrl",ctrl);
deferred.resolve();
});
});
return deferred.promise;
}
}
});
}];
var frameworkConfig=angular.module("frm",["ui.router"]);
frameworkConfig.config(serviceConfigs);
return frameworkConfig;
});
路由管理模組(使用ui-router):
宣告路由模組(路由作為module的配置來宣告),每個路由模組包含請求url、url匹配檢視、url匹配控制器等等資訊(後面會補充說明如何註冊一個功能完整的路由模組),每個路由模組相當於一個選單項介面(當在實際業務開發中需要新增介面時,必須在路由模組中註冊)。
$urlRouterProvider.otherwise是定義預設路由項,當介面請求了一個在路由管理模組未定義的路由時,則會顯示預設路由項對應的介面。
在路由管理模組中可以註冊開發完整的路由模組(註冊過的路由模組就可以在主選單上點選查看了)。
frameworkCtrl.js/**
* Created by 李慶 on 2016/10/6.
*/
define([],function(){
var frameworkControl=["$rootScope","$scope",function($rootScope,$scope){
$rootScope.menus={
"url":"framework/views/menu.html"
};
}];
return frameworkControl;
})
框架module的控制器:
在控制器內部只聲明瞭外部html的路徑。
主選單的控制器不建議處理過多的具體業務,但一些通用的全域性變數可以放在此處。
menu.html<div style="min-width: 1280px; max-width: 1440px; margin: 58px auto 0; border:1px solid #F00">
<div>
<ul>
<li>
<a ui-sref="login">login</a>
</li>
<li>
<a ui-sref="c1">c1</a>
</li>
<li>
<a ui-sref="c2">c2</a>
</li>
</ul>
</div>
</div>
框架module控制器內宣告的html:
包含三個路由項,每個路由項按ui-sref的格式來定義,ui-sref內容就是在路由管理模組中定義的url,在實際的業務開發中應當保證頁面上提供的路由必須在路由管理模組中定義過,否則會出錯(如在路由管理模組中定義了預設路由,則會顯示預設路由的介面)。
當新增一個路由模組時,同時需要在主選單介面新增一項(怎麼修改視使用哪種選單樹控制元件而定)。
c2.js/**
* Created by 李慶 on 2016/10/6.
*/
define([],function(){
var c2Controller = function($scope){
$scope.test = function(){
console.log("enter controller");
};
};
return c2Controller;
});
c2選單項路由模組定義的控制器:
在該控制器內部只定義了一個列印enter controller字串的函式。
實際上這裡是處理業務邏輯的地方,選單項介面上所有的業務處理程式碼都在此處。
c2.html<div>
<input type="button" value="test Controller" ng-click="test()">
</div>
c2選單項路由模組定義的檢視:
檢視只包含一個按鈕,點選會呼叫在控制器內部定義的列印函式。
這裡則是實際的業務介面。
在這個例子中實際包含了兩個MVC結構,一個是框架部分:frameworkCtrl.js是控制器(controller),index.html是檢視(view),frameworkConfig.js可以算是資料模型(model,裡面包含了選單樹資訊);另一個則是業務部分:c2.js是控制器,c2.html是檢視,由於業務介面內容簡單,不涉及到資料互動和展示,所以沒有資料模型。