Web前端發展簡史

Web前端發展簡史

有人說「前端開發」是IT界最容易被誤解的崗位,這不是空穴來風。若是你還認爲前端只是從美工那裏拿到切圖, JS和CSS一番亂燉,難搞的功能就去網上信手拈來,CtrlC + Ctrl V的話,那就正中了這份誤解的下懷。通過十幾年的發展,web前端早已脫離了原來邊緣化的形態,扮演了移動互聯網開發鏈條中最關鍵的角色,是應用或產品可否打動用戶的踹門磚。那麼什麼是前端開發,其又包含了哪些內容?css

 

前端開發的定義html

從狹義的定義來看,「前端開發」是指圍繞HTML、JavaScript、CSS這樣一套體系的開發技術,它的運行宿主是瀏覽器。從廣義的定義來看,其應該包括:前端

l  專門爲手持終端設計的相似WML這樣的類HTML語言,以及相似WMLScript的類JavaScript語言。jquery

l  VML和SVG等基於XML的描述圖形的語言。web

l  從屬於XML體系的XML,XPath,DTD等技術。數據庫

l  用於支撐後端的ASP,JSP,ASP.net,PHP,Nodejs等語言或者技術。編程

l  被第三方程序打包的一種相似瀏覽器的宿主環境,好比Adobe AIR和使用HyBird方式的一些開發技術,如PhoneGap。後端

l  Adobe Flash,Flex,Microsoft Silverlight,Java Applet,JavaFx等RIA開發技術。數組

本文主要從「web前端」,也即狹義前端的角度出發,以人類科技進步劃時代的方式,將前端開發劃分爲幾個重要的時代,帶領你們領略一下前端這十幾年來的發展歷程。瀏覽器

 

石器時代

最先期的Web界面基本都是在互聯網上使用,人們瀏覽某些內容,填寫幾個表單而且提交。當時的界面以瀏覽爲主,基本都是HTML代碼,咱們來看一個最簡單的HTML文件:

<html>

<head>

<title>測試一</title>

</head>

<body>

<h1>主標題</h1>

<p>段落內容</p>

</body>

</html>

爲了執行一些動做或進行必定的業務處理,有時候會穿插一些JavaScript,如做爲客戶端校驗這樣的基礎功能。代碼的組織比較簡單,並且CSS的運用也是比較少的。譬如:下面這個文檔將帶有一段JavaScript代碼,用於拼接兩個輸入框中的字符串,而且彈出窗口顯示。

<html>

<head>

<title>測試二</title>

</head>

<body>

<inputid="firstNameInput"type="text"/>

<inputid="lastNameInput"type="text"/>

<inputtype="button"onclick="greet()"/>

<scriptlanguage="JavaScript">

function greet(){

var firstName = document.getElementById("firstNameInput").value;

var lastName = document.getElementById("lastNameInput").value;

            alert("Hello, "+ firstName +"."+ lastName);

}

</script>

</body>

</html>

因爲靜態界面不能實現保存數據等功能,出現了不少服務端技術,早期的有CGI(Common Gateway Interface,多數用C語言或者Perl實現的),ASP(使用VBScript或者JScript),JSP(使用Java),PHP等等,Python和Ruby等語言也常被用於這類用途。

有了這類技術,在HTML中就可使用表單的post功能提交數據了,好比:

<formmethod="post"action="username.asp">

<p>First Name: <inputtype="text"name="firstName"/></p>

<p>Last Name: <inputtype="text"name="lastName"/></p>

<inputtype="submit"value="Submit"/>

</form>

在這個階段,因爲客戶端和服務端的職責未做明確的劃分,好比生成一個字符串,能夠由前端的JavaScript作,也能夠由服務端語言作。因此一般在一個界面裏,會有兩種語言混雜在一塊兒,用<%和%>標記的部分會在服務端執行,輸出結果,甚至常常有把數據庫鏈接的代碼跟頁面代碼混雜在一塊兒的狀況,給維護帶來了很大的問題。

<html>

<body>

<p>Hello world!</p>

<p>

<%response.write("Hello world from server!")%>

</p>

</body>

</html>

 

青銅時代

青銅時代的典型標誌是出現了組件化的萌芽,着眼點主要在文件的劃分上。後端組件化比較常見的作法是,把某一類後端功能單獨作成片斷,而後其餘須要的地方來include進來,典型的有:ASP裏面數據庫鏈接的地方,把數據源鏈接的部分寫成conn.asp,而後其餘每一個須要操做數據庫的asp文件包含它。瀏覽器端則一般針對JavaScript腳本文件,把某一類的Javascript代碼寫到單獨的js文件中,界面根據須要,引用不一樣的js文件;針對界面組件,則一般利用frameset和iframe這兩個標籤。某一大塊有獨立功能的界面寫到一個HTML文件,而後在主界面裏面把它看成一個frame來載入,通常的B/S系統集成菜單的方式都是這樣的。是否是以爲很熟悉?對的,如今大多公司的內部系統正是這個時代的產物。

此外,還出現了一些基於特定瀏覽器的客戶端組件技術,好比IE瀏覽器的HTC(HTML Component)。這種技術最初是爲了對已有的經常使用元素附加行爲的,後來有些場合也用它來實現控件。微軟ASP.NET的一些版本里,使用這種技術提供了樹形列表,日曆,選項卡等功能。HTC的優勢是容許用戶自行擴展HTML標籤,能夠在本身的命名空間裏定義元素,而後,使用HTML,JavaScript和CSS來實現它的佈局、行爲和觀感。這種技術由於是微軟的私有技術,因此逐漸變得不那麼流行。Firefox瀏覽器布其後塵,也推出過一種叫XUL的技術,也一樣沒有流行起來。

 

鐵器時代

這個時代的彗星是Ajax的出現以及JS基礎框架的興起。

AJAX

AJAX實際上是一系列已有技術的組合,早在這個名詞出現以前,這些技術的使用就已經比較普遍了,GMail由於恰當地應用了這些技術,得到了很好的用戶體驗。因爲Ajax的出現,規模更大,效果更好的Web程序逐漸出現,在這些程序中,JavaScript代碼的數量迅速增長。出於代碼組織的須要,「JavaScript框架」這個概念逐步造成,當時的主流是Prototype和Mootools,二者各有千秋,提供了各自方式的面向對象組織思路。

JavaScript基礎框架

Prototype框架主要是爲JavaScript代碼提供了一種組織方式,對一些原生的JavaScript類型提供了一些擴展,好比數組、字符串,又額外提供了一些實用的數據結構,如:枚舉,Hash等,除此以外,還對dom操做,事件,表單和Ajax作了一些封裝。

Mootools框架的思路跟Prototype很接近,它對JavaScript類型擴展的方式別具一格,因此在這類框架中,常常被稱做「最優雅的」對象擴展體系。

從這兩個框架的所提供的功能來看,它們的定位是核心庫,在使用的時候通常須要配合一些外圍的庫來完成。

倚天不出,誰與爭鋒?除以上二者之外,還有YUI,jQuery等,JavaScript基礎框架在這個時代算得上是百花齊放,可是時間已經證實,真正的王者是jQuery。

jQuery與其餘的基礎框架都有所不一樣,它着眼於簡化DOM相關的代碼。例如:

l  DOM的選取

jQuery提供了一系列選擇器用於選取界面元素,在其餘一些框架中也有相似功能,但通常沒有它簡潔而強大。

$("*")//選取全部元素

$("#lastname")//選取id爲lastname的元素

$(".intro")//選取全部class="intro"的元素

$("p")//選取全部&lt;p&gt;元素

$(".intro.demo")//選取全部 class="intro"且class="demo"的元素

 

l  鏈式表達式

在jQuery中,可使用鏈式表達式來連續操做DOM,若是不使用鏈式表達式,可能須要這麼寫:

var neat = $("p.neat");

neat.addClass("ohmy");

neat.show("slow");

可是有了鏈式表達式,一行代碼就能夠搞定:

$("p.neat").addClass("ohmy").show("slow");

除此以外,jQuery還提供了一些動畫方面的特效代碼,也有大量的外圍庫,好比jQuery UI這樣的控件庫,jQuery mobile這樣的移動開發庫等。

 

農業時代

這個時代的標誌性事件是模塊加載規範(AMD以及CMD)的出現。

鐵器時代出現的基礎框架提供了代碼的組織能力,可是未能提供代碼的動態加載能力。動態加載JavaScript爲何重要呢?由於隨着Ajax的普及,jQuery等輔助庫的出現,Web上能夠作很複雜的功能,所以,單頁面應用程序(SPA,Single Page Application)也逐漸多了起來。

單個的界面想要作不少功能,須要寫的代碼是會比較多的,可是,並不是全部的功能都須要在界面加載的時候就所有引入,若是可以在須要的時候才加載那些代碼,就把加載的壓力分擔了,在這個背景下,出現了一些用於動態加載JavaScript的框架,也出現了一些定義這類可被動態加載代碼的規範。

AMD

在這些框架裏,知名度最高的是RequireJS,遵循了AMD(Asynchronous Module Definition)的規範。好比下面這段,定義了一個動態的匿名模塊,它依賴math模塊:

define(["math"],function(math){

return{

        addTen :function(x){

return math.add(x,10);

}

};

});

假設上面的代碼存放於adder.js中,當須要使用這個模塊的時候,經過以下代碼來引入adder:

<scriptsrc="require.js"></script>

<script>

    require(["adder"],function(adder){

//使用這個adder

});

</script>

RequireJS除了提供異步加載方式,也可使用同步方式加載模塊代碼。AMD規範除了使用在前端瀏覽器環境中,也能夠運行於NodeJS等服務端環境,可是NodeJS內置的模塊機制是基於CMD規範定義的。

 

CMD

值得一提的是,在瀏覽器端,除了RequireJS之外,國內的牛人淘寶玉伯開發了SeaJS異步模塊加載器,其遵循CMD規範,目前已經有超過300家大型web應用或站點採用,SeaJS一樣簡單易學:

// 全部模塊都經過 define 來定義

define(function(require, exports, module){

// 經過 require 引入依賴

var $ =require('jquery');

var Spinning =require('./spinning');

 

// 經過 exports 對外提供接口

  exports.doSomething =...

 

// 或者經過 module.exports 提供整個接口

  module.exports =...

});

 

工業時代

「這是一個最好的時代,也是一個最壞的時代。」前端自動化和MV*框架真正讓前端迎來了春天,可是這個時代框架插件衆多、體系繁複,讓前端新手無所適從。在這個時代,Web端功能日益複雜,人們不得不開始考慮這樣一些問題:

l  如何更好地模塊化開發

l  業務數據如何組織

l  界面和業務數據之間經過何種方式進行交互

在這種背景下,前端MVC、MVP、MVVM框架如雨後春筍,咱們暫且把這些框架都統稱爲MV*框架。這些框架的出現,正是爲了解決上述這些問題,具體的實現思路各有不一樣,主流的有Backbone,AngularJS,Ember三大劍客,本文主要選用Backbone和AngularJS來說述如下場景。

數據模型

在這些MV*框架裏,定義數據模型的方式與以往有些差別,主要在於數據的get和set更加有意義了,好比說,能夠把某個實體的get和set綁定到RESTful的服務上,這樣,對某個實體的讀寫能夠更新到數據庫中。另一個特色是,它們通常都提供一個事件,用於監控數據的變化,這個機制使得數據綁定成爲可能。

在一些框架中,數據模型須要在原生的JavaScript類型上作一層封裝,好比Backbone的方式是這樣:

varTodo=Backbone.Model.extend({

// Default attributes for the todo item.

    defaults :function(){

return{

            title :"empty todo...",

            order :Todos.nextOrder(),

done:false

};

},

 

// Ensure that each todo created has `title`.

    initialize :function(){

if(!this.get("title")){

this.set({

"title":this.defaults().title

});

}

},

 

// Toggle the 'done' state of this todo item.

    toggle :function(){

this.save({

done:!this.get("done")

});

}

});

上述例子中,defaults方法用於提供模型的默認值,initialize方法用於作一些初始化工做,這兩個都是約定的方法,toggle是自定義的,用於保存todo的選中狀態。

除了對象,Backbone也支持集合類型,集合類型在定義的時候要經過model屬性指定其中的元素類型。

// The collection of todos is backed by *localStorage* instead of a remote server.

varTodoList=Backbone.Collection.extend({

// Reference to this collection's model.

    model :Todo,

 

// Save all of the todo items under the '"todos-backbone"' namespace.

    localStorage :newBackbone.LocalStorage("todos-backbone"),

 

// Filter down the list of all todo items that are finished.

done:function(){

returnthis.filter(function(todo){

return todo.get('done');

});

},

 

// Filter down the list to only todo items that are still not finished.

    remaining :function(){

returnthis.without.apply(this,this.done());

},

 

// We keep the Todos in sequential order, despite being saved by unordered

//GUID in the database. This generates the next order number for new items.

    nextOrder :function(){

if(!this.length)

return1;

returnthis.last().get('order')+1;

},

 

// Todos are sorted by their original insertion order.

    comparator :function(todo){

return todo.get('order');

}

});

數據模型也能夠包含一些方法,好比自身的校驗,或者跟後端的通信、數據的存取等等,在上面兩個例子中,也都有體現。

AngularJS的模型定義方式與Backbone不一樣,能夠不須要通過一層封裝,直接使用原生的JavaScript簡單數據、對象、數組,相對來講比較簡便。

控制器

在Backbone中,是沒有獨立的控制器的,它的一些控制的職責都放在了視圖裏,因此其實這是一種MVP(Model View Presentation)模式,而AngularJS有很清晰的控制器層。

仍是以這個todo爲例,在AngularJS中,會有一些約定的注入,好比$scope,它是控制器、模型和視圖之間的橋樑。在控制器定義的時候,將$scope做爲參數,而後,就能夠在控制器裏面爲它添加模型的支持。

functionTodoCtrl($scope){

    $scope.todos =[{

        text :'learn angular',

done:true

},{

        text :'build an angular app',

done:false

}];

 

    $scope.addTodo =function(){

        $scope.todos.push({

            text : $scope.todoText,

done:false

});

        $scope.todoText ='';

};

 

    $scope.remaining =function(){

var count =0;

        angular.forEach($scope.todos,function(todo){

            count += todo.done?0:1;

});

return count;

};

 

    $scope.archive =function(){

var oldTodos = $scope.todos;

        $scope.todos =[];

        angular.forEach(oldTodos,function(todo){

if(!todo.done)

                $scope.todos.push(todo);

});

};

}

本例中爲$scope添加了todos這個數組,addTodo,remaining和archive三個方法,而後,能夠在視圖中對他們進行綁定。

視圖

在這些主流的MV*框架中,通常都提供了定義視圖的功能。在Backbone中,是這樣定義視圖的:

// The DOM element for a todo item...

varTodoView=Backbone.View.extend({

//... is a list tag.

    tagName :"li",

 

// Cache the template function for a single item.

template: _.template($('#item-template').html()),

 

// The DOM events specific to an item.

    events :{

"click .toggle":"toggleDone",

"dblclick .view":"edit",

"click a.destroy":"clear",

"keypress .edit":"updateOnEnter",

"blur .edit":"close"

},

 

// The TodoView listens for changes to its model, re-rendering. Since there's

// a one-to-one correspondence between a **Todo** and a **TodoView** in this

// app, we set a direct reference on the model for convenience.

    initialize :function(){

this.listenTo(this.model,'change',this.render);

this.listenTo(this.model,'destroy',this.remove);

},

 

// Re-render the titles of the todo item.

    render :function(){

this.$el.html(this.template(this.model.toJSON()));

this.$el.toggleClass('done',this.model.get('done'));

this.input =this.$('.edit');

returnthis;

},

 

//......

 

// Remove the item, destroy the model.

    clear :function(){

this.model.destroy();

}

});

上面這個例子是一個典型的「部件」視圖,它對於界面上的已有元素沒有依賴。也有那麼一些視圖,須要依賴於界面上的已有元素,好比下面這個,它經過el屬性,指定了HTML中id爲todoapp的元素,而且還在initialize方法中引用了另一些元素,一般,須要直接放置到界面的頂層試圖會採用這種方式,而「部件」視圖通常由主視圖來建立、佈局。

// Our overall **AppView** is the top-level piece of UI.

varAppView=Backbone.View.extend({

// Instead of generating a new element, bind to the existing skeleton of

// the App already present in the HTML.

    el : $("#todoapp"),

 

// Our template for the line of statistics at the bottom of the app.

    statsTemplate : _.template($('#stats-template').html()),

 

// Delegated events for creating new items, and clearing completed ones.

    events :{

"keypress #new-todo":"createOnEnter",

"click #clear-completed":"clearCompleted",

"click #toggle-all":"toggleAllComplete"

},

 

// At initialization we bind to the relevant events on the `Todos`

// collection, when items are added or changed. Kick things off by

// loading any preexisting todos that might be saved in *localStorage*.

    initialize :function(){

this.input =this.$("#new-todo");

this.allCheckbox =this.$("#toggle-all")[0];

 

this.listenTo(Todos,'add',this.addOne);

this.listenTo(Todos,'reset',this.addAll);

this.listenTo(Todos,'all',this.render);

 

this.footer =this.$('footer');

this.main = $('#main');

 

Todos.fetch();

},

 

// Re-rendering the App just means refreshing the statistics -- the rest

// of the app doesn't change.

    render :function(){

vardone=Todos.done().length;

var remaining =Todos.remaining().length;

 

if(Todos.length){

this.main.show();

this.footer.show();

this.footer.html(this.statsTemplate({

done:done,

                remaining : remaining

}));

}else{

this.main.hide();

this.footer.hide();

}

 

this.allCheckbox.checked=!remaining;

},

 

//......

});

對於AngularJS來講,基本不須要有額外的視圖定義,它採用的是直接定義在HTML上的方式,好比:

<divng-controller="TodoCtrl">

<span>{{remaining()}} of {{todos.length}} remaining</span>

<ahref=""ng-click="archive()">archive</a>

<ulclass="unstyled">

<ling-repeat="todo in todos">

<inputtype="checkbox"ng-model="todo.done">

<spanclass="done-{{todo.done}}">{{todo.text}}</span>

</li>

</ul>

<formng-submit="addTodo()">

<inputtype="text"ng-model="todoText"size="30"

placeholder="add new todo here">

<inputclass="btn-primary"type="submit"value="add">

</form>

</div>

在這個例子中,使用ng-controller注入了一個TodoCtrl的實例,而後,在TodoCtrl的$scope中附加的那些變量和方法均可以直接訪問了。注意到其中的ng-repeat部分,它遍歷了todos數組,而後使用其中的單個todo對象建立了一些HTML元素,把相應的值填到裏面。這種作法和ng-model同樣,都創造了雙向綁定,即:

l  改變模型能夠隨時反映到界面上

l  在界面上作的操做(輸入,選擇等等)能夠實時反映到模型裏。

並且,這種綁定都會自動忽略其中可能由於空數據而引發的異常狀況。

模板

模板是這個時代一種很典型的解決方案。咱們經常有這樣的場景:在一個界面上重複展現相似的DOM片斷,例如微博。以傳統的開發方式,也能夠輕鬆實現出來,好比:

var feedsDiv = $("#feedsDiv");

 

for(var i =0; i <5; i++){

var feedDiv = $("<div class='post'></div>");

 

var authorDiv = $("<div class='author'></div>");

var authorLink = $("<a></a>")

.attr("href","/user.html?user='"+"Test"+"'")

.html("@"+"Test")

.appendTo(authorDiv);

    authorDiv.appendTo(feedDiv);

 

var contentDiv = $("<div></div>")

.html("Hello, world!")

.appendTo(feedDiv);

var dateDiv = $("<div></div>")

.html("發佈日期:"+newDate().toString())

.appendTo(feedDiv);

 

    feedDiv.appendTo(feedsDiv);

}

可是使用模板技術,這一切能夠更加優雅,以經常使用的模板框架UnderScore爲例,實現這段功能的代碼爲:

var templateStr ='<div class="post">'

+'<div class="author">'

+'<a href="/user.html?user={{creatorName}}">@{{creatorName}}</a>'

+'</div>'

+'<div>{{content}}</div>'

+'<div>{{postedDate}}</div>'

+'</div>';

vartemplate= _.template(templateStr);

template({

    createName :"Xufei",

    content:"Hello, world",

    postedDate:newDate().toString()

});

也能夠這麼定義:

<scripttype="text/template"id="feedTemplate">

<% _.each(feeds,function(item){%>

<div class="post">

<div class="author">

<a href="/user.html?user=<%= item.creatorName %>">@<%= item.creatorName %></a>

</div>

<div><%= item.content %></div>

<div><%= item.postedData %></div>

</div>

<%});%>

</script>

 

<script>

$('#feedsDiv').html( _.template($('#feedTemplate').html(), feeds));

</script>

除此以外,UnderScore還提供了一些很方便的集合操做,使得模板的使用更加方便。若是你打算使用BackBone框架,而且須要用到模板功能,那麼UnderScore是一個很好的選擇,固然,也能夠選用其它的模板庫,好比Mustache等等。

若是使用AngularJS,能夠不須要額外的模板庫,它自身就提供了相似的功能,好比上面這個例子能夠改寫成這樣:

<divclass="post"ng-repeat="post in feeds">

<divclass="author">

<ang-href="/user.html?user={{post.creatorName}}">@{{post.creatorName}}</a>

</div>

<div>{{post.content}}</div>

<div>

發佈日期:{{post.postedTime | date:'medium'}}

</div>

</div>

主流的模板技術都提供了一些特定的語法,有些功能很強。值得注意的是,他們雖然與JSP之類的代碼寫法相似甚至相同,但原理差異很大,這些模板框架都是在瀏覽器端執行的,不依賴任何服務端技術,即便界面文件是.html也能夠,而傳統好比JSP模板是須要後端支持的,執行時間是在服務端。

路由

一般路由是定義在後端的,可是在這類MV*框架的幫助下,路由能夠由前端來解析執行。好比下面這個Backbone的路由示例:

varWorkspace=Backbone.Router.extend({

    routes:{

"help":"help",// #help

"search/:query":"search",// #search/kiwis

"search/:query/p:page":"search"// #search/kiwis/p7

},

 

    help:function(){

...

},

 

    search:function(query, page){

...

}

});

在上述例子中,定義了一些路由的映射關係,那麼,在實際訪問的時候,若是在地址欄輸入"#search/obama/p2",就會匹配到"search/:query/p:page"這條路由,而後,把"obama"和"2"看成參數,傳遞給search方法。

AngularJS中定義路由的方式有些區別,它使用一個$routeProvider來提供路由的存取,每個when表達式配置一條路由信息,otherwise配置默認路由,在配置路由的時候,能夠指定一個額外的控制器,用於控制這條路由對應的html界面:

app.config(['$routeProvider',

function($routeProvider){

    $routeProvider.when('/phones',{

        templateUrl :'partials/phone-list.html',

        controller :PhoneListCtrl

}).when('/phones/:phoneId',{

        templateUrl :'partials/phone-detail.html',

        controller :PhoneDetailCtrl

}).otherwise({

        redirectTo :'/phones'

});

}]);

注意,在AngularJS中,路由的template並不是一個完整的html文件,而是其中的一段,文件的頭尾均可以不要,也能夠不要那些包含的外部樣式和JavaScript文件,這些在主界面中載入就能夠了。

自定義組件

用過XAML或者MXML的人必定會對其中的可擴充標籤印象深入,對於前端開發人員而言,基於標籤的組件定義方式必定是優於其餘任何方式的,看下面這段HTML:

<div>

<inputtype="text"value="hello, world"/>

<button>test</button>

</div>

即便是剛剛接觸這種東西的新手,也可以理解它的意思,而且可以照着作出相似的東西,若是使用傳統的面嚮對象語言去描述界面,效率遠遠沒有這麼高,這就是在界面開發領域,聲明式編程比命令式編程適合的最重要緣由。

可是,HTML的標籤是有限的,若是咱們須要的功能不在其中,怎麼辦?在開發過程當中,咱們可能須要一個選項卡的功能,可是,HTML裏面不提供選項卡標籤,因此,通常來講,會使用一些li元素和div的組合,加上一些css,來實現選項卡的效果,也有的框架使用JavaScript來完成這些功能。總的來講,這些代碼都不夠簡潔直觀。

若是可以有一種技術,可以提供相似這樣的方式,該多麼好呢?

<tabs>

<tabname="Tab 1">content 1</tab>

<tabname="Tab 2">content 2</tab>

</tabs>

在AngularJS的首頁,能夠看到這麼一個區塊「Create Components」,在它的演示代碼裏,可以看到相似的一段:

<tabs>

<panetitle="Localization">

        ...

</pane>

<panetitle="Pluralization">

        ...

</pane>

</tabs>

那麼,它是怎麼作到的呢?祕密在這裏:

angular.module('components',[]).directive('tabs',function(){

return{

        restrict :'E',

        transclude :true,

        scope :{},

        controller :function($scope, $element){

var panes = $scope.panes =[];

 

            $scope.select=function(pane){

                angular.forEach(panes,function(pane){

                    pane.selected =false;

});

                pane.selected =true;

}

 

this.addPane =function(pane){

if(panes.length ==0)

                    $scope.select(pane);

                panes.push(pane);

}

},

template:'<div class="tabbable">'

+'<ul class="nav nav-tabs">'

+'<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'

+'<a href="" ng-click="select(pane)">{{pane.title}}</a>'

+'</li>'

+'</ul>'

+'<div class="tab-content" ng-transclude></div>'

+'</div>',

        replace :true

};

}).directive('pane',function(){

return{

require:'^tabs',

        restrict :'E',

        transclude :true,

        scope :{

            title :'@'

},

        link :function(scope, element, attrs, tabsCtrl){

            tabsCtrl.addPane(scope);

},

template:'<div class="tab-pane" ng-class="{active: selected}" ng-transclude>'+'</div>',

        replace :true

};

})

這段代碼裏,定義了tabs和pane兩個標籤,而且限定了pane標籤不能脫離tabs而單獨存在,tabs的controller定義了它的行爲,二者的template定義了實際生成的html,經過這種方式,開發者能夠擴展出本身須要的新元素,對於使用者而言,這不會增長任何額外的負擔。

相關文章
相關標籤/搜索