AngularJs玩轉DOM-directive

英文地址:directivejavascript

  Directive是教HTML玩一些新把戲的途徑。在DOM編譯期間,directives匹配HTML並執行。這容許directive註冊行爲或者轉換DOM結構。
  Angular自帶一組內置的directive,對於創建Web App有很大幫助。繼續擴展的話,能夠在HTML定義領域特定語言(domain specific language ,DSL)。
css

1、在HTML中引用directives

  Directive有駝峯式(camel cased)的風格的命名,如ngBind(放在屬性裏貌似用不了~)。但directive也能夠支蛇底式的命名(snake case),須要經過:(冒號)、-(減號)或_(下劃線)鏈接。做爲一個可選項,directive能夠用「x-」或者「data-」做爲前綴,以知足HTML驗證須要。這裏列出directive的合法命名:html

  • ng:bind
  • ng-bind
  • ng_bind
  • x-ng-bind
  • data-ng-bind

Directive能夠放置於元素名、屬性、class、註釋中。下面是引用myDir這個directive的等價方式。(但不少directive都限制爲「屬性」的使用方式)java

<span my-dir="exp"></span>

<span class="my-dir: exp;"></span>

<my-dir></my-dir>

<!-- directive: my-dir exp -->

Directive能夠經過多種方式引用,下面列出N種等價的方式:angularjs

<!DOCTYPE HTML>
<html lang="zh-cn" ng-app>
<head>
    <meta charset="UTF-8">
    <title>invoke-directive</title>
    <style type="text/css">
        .ng-cloak {
            display: none;
        }
    </style>
</head>
<body>
<div ng-controller="MyCtrl">
    Hello <input ng-model="name"/><hr/>
    ngBind="name" 這個用不了~~ <span ngBind="name"></span><br/>
    ng:bind="name"<span ng:bind="name"></span><br/>
    ng_bind="name"<span ng_bind="name"></span><br/>
    ng-bind="name"<span ng-bind="name"></span><br/>
    data-ng-bind="name"<span data-ng-bind="name"></span><br/>
    x-ng-bind="name"<span x-ng-bind="name"></span><br/>
</div>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
    function MyCtrl($scope) {
        $scope.name = "beauty~~";
    }
</script>
</body>
</html>

2、String interpolation

  在編譯過程當中,compiler經過$interpolate服務匹配文本與屬性中的嵌入表達式(如{{something}})。這些表達式將會註冊爲watches,而且做爲digest cycle(以前不是digest-loop嗎?!)的一部分,一同更新。下面是一個簡單的interpolation:chrome

<img src="img/{{username}}.jpg"/>Hello {{username}}!

3、Compilation process, and directive matching

  HTML「編譯」的三個步驟:express

  1. 首先,經過瀏覽器的標準API,將HTML轉換爲DOM對象。這是很重要的一步。由於模版必須是可解析(符合規範)的HTML。這裏能夠跟大多數的模版系統作對比,它們通常是基於字符串的,而不是基於DOM元素的。api

  2. 對DOM的編譯(compilation)是經過調用$comple()方法完成的。這個方法遍歷DOM,對directive進行匹配。若是匹配成功,那麼它將與對應的DOM一塊兒,加入到directive列表中。只要全部與指定DOM關聯的directive被識別出來,他們將按照優先級排序,並按照這個順序執行他們的compile() 函數。directive的編譯函數(compile function),擁有一個修改DOM結構的機會,並負責產生link() function的解析。$compile()方法返回一個組合的linking function,是全部directive自身的compile function返回的linking function的集合。數組

  3. 經過上一步返回的linking function,將模版與scope鏈接起來。這反過來會調用directive自身的linking function,容許它們在元素上註冊一些監聽器(listener),以及與scope一塊兒創建一些watches。這樣得出的結果,是在scope與DOM之間的一個雙向、即時的綁定。scope發生改變時,DOM會獲得對應的響應。瀏覽器

var $compile = ...; // injected into your code

    var scope = ...;

    var html = '<div ng-bind='exp'></div>';

    // Step 1: parse HTML into DOM element
    var template = angular.element(html);

    // Step 2: compile the template
    var linkFn = $compile(template);

    // Step 3: link the compiled template with the scope.
    linkFn(scope);

4、Reasons behind the compile/link separation

  在這個時候,你可能會想知道爲何編譯進程會劃分爲compile和linke兩個步驟。爲了明白這一點,讓咱們看看一個真實的例子(repeater)

Hello {{user}}, you have these actions:
    <ul>
        <li ng-repeat="action in user.actions">{{action.description}}</li>
    </ul>

 簡單地講,之因此分開compile和linke兩步,是由於有時候須要在model改變後,對應的DOM結構也須要改變的狀況,如repeaters。
  當上面的例子被編譯時,編譯器會遍歷全部節點以尋找directive。{{user}}是一個interpolation directive的例子。ngRepeat又是另一個directive。但ngRepeat有一個難點。它須要可以很快地爲每個在users.actions中的action製造出新的li的能力。這意味着它爲了知足克隆li而且嵌入特定的action(這裏是指user的actions的其中一個值)的目的,須要保持一個乾淨li元素的拷貝,li元素須要被克隆和插入ul元素。但僅僅克隆li元素是不夠的。還須要編譯li,以便它的directive({{action.descriptions}})可以在正確的scope中被解析。原始的方法,通常會簡單地插入一個li元素的拷貝,而後編譯它。但編譯每個li元素的拷貝會比較緩慢,由於編譯過程須要咱們遍歷DOM節點樹,查找directive並運行它們。若是咱們有一個編譯,須要經過repeater對100個item進行處理,那麼咱們將陷入性能問題。

  問題的解決方案,是將編譯過程分解爲兩個步驟。compile階段識別出全部directive,而且將它們按照優先級進行排序,在linking階段將特定的scope與特定的li綁定在一塊兒。

  ngRepeat將各個li分開編譯以防止編譯過程落入li元素中。li元素的編譯結果是一個包含全部包含在li元素中的directive的linking function,準備與特定li元素的拷貝進行鏈接。在運行時,ngRepeat監測表達式,並做爲一個item,被加入到一個li元素拷貝的數組,爲克隆好的li元素建立新的scope,並調用該拷貝對應的link function。

  總結:

編譯函數(compile function) - 編譯函數在directive中是比較少見的,由於大多數directive只關心與指定的DOM元素工做,而不是改變DOM元素的模版(DOM自身以及內部的結構)。爲了優化性能,一些能夠被directive實例共享的操做,能夠移動到compile函數中。
鏈接函數(link function) - 極少directive是沒有link function的。link function容許directive在指定的拷貝後的DOM元素實例上註冊監聽器,也能夠將scope中特定內容複製到DOM中。

5、寫一個directive(簡易版)

  在這個例子裏面,咱們將創建一個根據輸入格式,顯示當前時間的directive。

<!DOCTYPE HTML>
<html lang="zh-cn" ng-app="TimeFormat">
<head>
    <meta charset="UTF-8">
    <title>time-format</title>
</head>
<body>


<div ng-controller="MyCtrl" id="main">
    Date format: <input ng-model="format" type="text"/><hr/>
    <!--下面使用屬性x-current-time,是爲了試試上面說的合法命名~~current:time、current-time、current_time、data-current-time -_-!!! -->
    Current time is : <span x-current-time="format" id="myFormat"></span><br/>
    <button ng-click="remove()">remove the span</button>
</div>


<script src="../angular-1.0.1.js" type="text/javascript"></script>


<script type="text/javascript">
    angular.module("TimeFormat", [])
        //在TimeFormat應用中註冊「currentTime」這個directive的工廠方法
        //前文提到過,依賴注入,能夠直接在function的參數中寫入,這裏注入了$timeout、dataFilter
            .directive("currentTime", function (dateFilter) {
                //這個是上面提到的linking function。(不須要添加compile function,爲啥?。。。)
                return function (scope, element, attr) {
                    var intervalId;
                    //更新對應element的text值,即更新時間
                    function updateTime() {
                        element.text(dateFilter(new Date(), scope.format));
                    }
                    //經過watch,監控span對象的currentTime的值(是format這個model值,即input的值!!)
                    //這個方法僅僅在format發生改變的時候執行
                    scope.$watch(attr.currentTime, function (value) {
                        scope.format = value;
                        updateTime();
                    });
                    //當span被去掉的時候,取消更新
                    element.bind("$destroy", function () {
                        clearInterval(intervalId);
                    });

                    intervalId = setInterval(updateTime, 1000);
                };
            }).controller("MyCtrl",function($scope,$rootScope) {
                $scope.format = "M/d/yy h:mm:ss a";
                $scope.remove = function() {
                    var oFormat = document.getElementById("myFormat");
                    if(oFormat) {
                        angular.element(oFormat).remove();//經過這種方式調用remove,能夠觸發$destroy事件啊!!!試了我N久。。。
                    }
                };
            });
</script>


</body>
</html>

6、寫一個directive(詳細版)

  下面是一個建立directive樣例(directive對象定義模版)。想看詳細列表,請繼續往下看。

var myModule = angular.module(...);



myModule.directive('directiveName', function factory(injectables) {

    var directiveDefinitionObject = {

      priority: 0,

      template: '<div></div>',

      templateUrl: 'directive.html',

      replace: false,

      transclude: false,

      restrict: 'A',

      scope: false,

      compile: function compile(tElement, tAttrs, transclude) {

        return {

          pre: function preLink(scope, iElement, iAttrs, controller) { ... },

          post: function postLink(scope, iElement, iAttrs, controller) { ... }

        }

      },

      link: function postLink(scope, iElement, iAttrs) { ... }

    };

    return directiveDefinitionObject;

 });

在大多數場景下,咱們並不須要精確控制,因此上面的定義是能夠化簡的。定義模版中的每一部分,將在下面章節講解。在這個章節,咱們僅僅關注定義模版的異構體(isomers of this skeleton,沒看懂。。。期待你們補充)。

  簡化代碼的第一步是依賴默認值。所以,上面的代碼能夠簡化爲:

var myModule = angular.module(...);

    myModule.directive('directiveName', function factory(injectables) {
      var directiveDefinitionObject = {
        compile: function compile(tElement, tAttrs) {
          return function postLink(scope, iElement, iAttrs) { ... }
        }
      };
      return directiveDefinitionObject;
    });

  大多數directive只關心實例,而不是模版轉換,因此能夠進一步化簡(翻譯得很勉強。。。期待你們補充):

var myModule = angular.module(...);

    myModule.directive('directiveName', function factory(injectables) {
      return function postLink(scope, iElement, iAttrs) { ... }
    });

7、工廠方法

  工廠方法負責建立directive。它僅僅使用一次,就在compiler第一次匹配到directive的時候。你能夠在這裏執行一些初始化操做。工廠方法經過$injector.invoke執行,讓它遵照全部注入聲明規則(rules of injection annotation),讓其變爲可注入的。

8、directive定義對象說明

  directive定義對象提供了compiler的結構。屬性以下:

name - 當前scope的名稱,註冊時可使用默認值(不填)。

priority(優先級)- 當有多個directive定義在同一個DOM元素時,有時須要明確它們的執行順序。這屬性用於在directive的compile function調用以前進行排序。若是優先級相同,則執行順序是不肯定的(經初步試驗,優先級高的先執行,同級時按照相似棧的「後綁定先執行」。另外,測試時有點不當心,在定義directive的時候,兩次定義了一個相同名稱的directive,但執行結果發現,兩個compile或者link function都會執行)。

terminal(最後一組)- 若是設置爲」true」,則表示當前的priority將會成爲最後一組執行的directive。任何directive與當前的優先級相同的話,他們依然會執行,但順序是不肯定的(雖然順序不肯定,但基本上與priority的順序一致。當前優先級執行完畢後,更低優先級的將不會再執行)。

scope - 若是設置爲:

true - 將爲這個directive建立一個新的scope。若是在同一個元素中有多個directive須要新的scope的話,它仍是隻會建立一個scope。新的做用域規則不適用於根模版(root of the template),所以根模版每每會得到一個新的scope。

{}(object hash) - 將建立一個新的、獨立(isolate)的scope。」isolate」 scope與通常的scope的區別在於它不是經過原型繼承於父scope的。這對於建立可複用的組件是頗有幫助的,能夠有效防止讀取或者修改父級scope的數據。這個獨立的scope會建立一個擁有一組來源於父scope的本地scope屬性(local scope properties)的object hash。這些local properties對於爲模版建立值的別名頗有幫助(useful for aliasing values for templates --!)。本地的定義是對其來源的一組本地scope property的hash映射(Locals definition is a hash of local scope property to its source #&)$&@#)($&@#):

@或@attr - 創建一個local scope property到DOM屬性的綁定。由於屬性值老是String類型,因此這個值老是返回一個字符串。若是沒有經過@attr指定屬性名稱,那麼本地名稱將與DOM屬性的名稱一直。例如 ,widget的scope定義爲:{localName:’@myAttr’}。那麼,widget scope property的localName會映射出」hello {{name}}"轉換後的真實值。name屬性值改變後,widget scope的localName屬性也會相應地改變(僅僅單向,與下面的」=」不一樣)。name屬性是在父scope讀取的(不是組件scope)

=或=expression(這裏也許是attr) - 在本地scope屬性與parent scope屬性之間設置雙向的綁定。若是沒有指定attr名稱,那麼本地名稱將與屬性名稱一致。例如 ,widget定義的scope爲:{localModel:’=myAttr’},那麼widget scope property 「localName」將會映射父scope的「parentModel」。若是parentModel發生任何改變,localModel也會發生改變,反之亦然。(雙向綁定)

&或&attr - 提供一個在父scope上下文中執行一個表達式的途徑。若是沒有指定attr的名稱,那麼local name將與屬性名稱一致。例如 ,widget的scope定義爲:{localFn:’increment()’},那麼isolate scope property 「localFn」會指向一個包裹着increment()表達式的function。通常來講,咱們但願經過一個表達式,將數據從isolate scope傳到parent scope中。這能夠經過傳送一個本地變量鍵值的映射到表達式的wrapper函數中來完成。例如,若是表達式是increment(amount),那麼咱們能夠經過localFn({amount:22})的方式調用localFn以指定amount的值(上面的例子真的沒看懂,&跑哪去了?)。

controller - controller 構造函數。controller會在pre-linking步驟以前進行初始化,並容許其餘directive經過指定名稱的require進行共享(看下面的require屬性)。這將容許directive之間相互溝通,加強相互之間的行爲。controller默認注入瞭如下本地對象:

$scope - 與當前元素結合的scope

$element - 當前的元素

$attrs - 當前元素的屬性對象

$transclude - 一個預先綁定到當前轉置scope的轉置linking function :function(cloneLinkingFn)。(A transclude linking function pre-bound to the correct transclusion scope)

require - 請求另外的controller,傳入當前directive的linking function中。require須要傳入一個directive controller的名稱。若是找不到這個名稱對應的controller,那麼將會拋出一個error。名稱能夠加入如下前綴:

? - 不要拋出異常。這使這個依賴變爲一個可選項。

^ - 容許查找父元素的controller

restrict - EACM的子集的字符串,它限制directive爲指定的聲明方式。若是省略的話,directive將僅僅容許經過屬性聲明:

E - 元素名稱:

A - 屬性名:

C - class名:

M - 註釋 :

template - 若是replace 爲true,則將模版內容替換當前的HTML元素,並將原來元素的屬性、class一併遷移;若是爲false,則將模版元素看成當前元素的子元素處理。想了解更多的話,請查看「Creating Widgets」章節(在哪啊。。。Creating Components就有。。。)

templateUrl - 與template基本一致,但模版經過指定的url進行加載。由於模版加載是異步的,因此compilation、linking都會暫停,等待加載完畢後再執行。

replace - 若是設置爲true,那麼模版將會替換當前元素,而不是做爲子元素添加到當前元素中。(注:爲true時,模版必須有一個根節點)

transclude - 編譯元素的內容,使它可以被directive所用。須要(在模版中)配合ngTransclude使用(引用)。transclusion的優勢是linking function可以獲得一個預先與當前scope綁定的transclusion function。通常地,創建一個widget,建立isolate scope,transclusion不是子級的,而是isolate scope的兄弟。這將使得widget擁有私有的狀態,transclusion會被綁定到父級(pre-isolate)scope中。(上面那段話沒看懂。但實際實驗中,若是經過 {{name}}</any my-directive>調用myDirective,而transclude設置爲true或者字符串且template中包含 的時候,將會將{{name}}的編譯結果插入到sometag的內容中。若是any的內容沒有被標籤包裹,那麼結果sometag中將會多了一個span。若是原本有其餘東西包裹的話,將維持原狀。但若是transclude設置爲’element’的話,any的總體內容會出如今sometag中,且被p包裹)

true - 轉換這個directive的內容。(這個感受上,是直接將內容編譯後搬入指定地方)

‘element’ - 轉換整個元素,包括其餘優先級較低的directive。(像將總體內容編譯後,看成一個總體(外面再包裹p),插入到指定地方)

compile - 這裏是compile function,將在下面章節詳細講解

link - 這裏是link function ,將在下面章節詳細講解。這個屬性僅僅是在compile屬性沒有定義的狀況下使用。

9、Compile function

function compile ( tElement, tAttrs, transclude) { … }

  compile function用於處理DOM模版的轉換。因爲大多數directive都不須要轉換模版,因此compile不會常常被使用到。須要compile function的directive,通常是那種須要轉換DOM模版的(如ngRepeat),或者是須要異步加載內容的(如ngView)。compile function擁有一下的參數:

tElement - 模版元素 – 使用當前directive的元素。僅僅在當前元素或者當前元素子元素下作模版轉換是安全的。
tAttrs - 模版屬性 - 標準化的屬性,在當前元素聲明的,能夠在各個directive之間共享。詳情請看Attributes章節
transclude – 一個轉換用的linking function: function(scope,cloneLinking).
  注意:若是template被克隆過,那麼template實例和link實例不能是同一個object。爲此,在compile function中作任何除了DOM轉換之外的事情,是不安全的,這將應用到全部克隆中。特別是,DOM事件監聽器的註冊的操做,應該在linking function中進行,而不是compile function。

  compile function 能夠有一個返回值,類型能夠是function或者object。

返回function – 一般在不須要compile function(爲空)時使用,等同於經過link(directive定義模版的屬性)註冊linking function。
返回包含pre、post屬性的object - 容許咱們控制在linking phase期間什麼時候調用linking function。詳情請看下面關於pre-linking、post-linking function的章節。

10、Linking function

function link( scope, iElement, iAttrs, controller) { … }

  link function負責註冊DOM事件監聽器,也能夠進行DOM的更新操做。link function會在模版克隆操做完畢以後執行。這裏存放着directive大多數的邏輯。

  • scope - Scope - 被directive用於註冊watches(http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch)。
  • iElement - 元素實例 - directive使用的元素。只有在postLink function裏面對子元素進行操做,纔是安全的。由於子元素已經被link(與model鏈接嗎?!)。
  • iAttrs - 屬性實例 - 標準的當前元素的屬性列表。在全部directive的linking function之間分享的。
  • controller - controller實例 - 若是在當前元素的directive中,有其中一個定義了controller,那麼能夠在這裏獲取到該controller的實例。這個controller是在全部directive之間共享的,容許各個directive將controller看成一個它們之間溝通頻道。

Pre-link function

  在子元素linked以前執行。在這裏作DOM轉換是不安全的,由於compiler的linking function在link時可能會定位不到正確的元素。

Post-link function

  在子元素linked以後執行。在這裏執行DOM轉換是安全的。

11、Attributes

  attribute object - 在link()或compile()中做爲參數 - 是一個訪問如下東東的途徑:

  • 標準化的屬性名稱:因爲directive,例如ngBind,能夠表現爲多種形式,如」ng:bind」、」x-ng-bind」……這個attribute對象容許咱們經過標準命名(駝峯式)訪問屬性。
  • directive 之間通信:全部directive共享一個attribute對象實例,使得directive能夠經過attribute 對象進行directive之間的內部通信。
  • 支持interpolation:interpolation屬性是分配到attribute對象中,容許其餘directive讀取該interpolated value。
  • 觀察interpolated屬性:經過attr.$observe去觀察屬性值的變化,包括interpolation(例如src=」{{bar}}」)。不僅僅頗有效,並且是簡單地獲取真實值惟一的辦法。由於在linking階段,interpolation還沒被賦值(替換真實值),因此在這時候訪問它,結果是undefined。
<!DOCTYPE HTML>
<html lang="zh-cn" ng-app="DirectiveProperty">
<head>
    <meta charset="UTF-8">
    <title>directive-attribute-test</title>
    <style type="text/css">
        .ng-cloak {
            display: none;
        }
    </style>
</head>
<body ng-controller="MyCtrl">
<input type="text" ng-model="name" value="myName"/>
<p my-attr="123" directive-p2 attr-dd="{{name}}"></p>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
    var app = angular.module("DirectiveProperty", []);
    app.controller("MyCtrl", function ($scope) {
        $scope.name = "my little dada";
    });
    var directiveP2 = app.directive("directiveP2", function () {
        return {
            link:function postLink(scope,lEle,lAttr) {                console.log("myAttr:" + lAttr.myAttr);//123
                console.log("myAttr:" + lAttr.attrDd);//undefinded
                lAttr.$observe('attrDd', function(value) {
                    console.log('attrDd has changed value to ' + value); //my little dada
                    //除此之外,還可啥辦法能夠拿到這個值啊。。。¥&()@#&¥(@#
                });
            }
        };
    });
</script>
</body>
</html>

12、理解transclusion和scope

  咱們常常須要一些可重用的組件。下面是一段僞代碼,展現一個簡單的dialog組件如何可以工做。

  



< div> <button ng-click="show=true">show</button> <dialog title="Hello {{username}}." visible="show" on-cancel="show = false" on-ok="show = false; doSomething()"> Body goes here: {{username}} is {{title}}. </dialog>

  點擊「show」按鈕將會打開dialog。dialog有一個標題,與數據「username」綁定,還有一段咱們想放置在dialog內部的內容。
  下面是一段爲了dialog而編寫的模版定義:



<div ng-show="show()"> <h3>{{title}}</h3> <div class="body" ng-transclude></div> <div class="footer"> <button ng-click="onOk()">Save changes</button> <button ng-click="onCancel()">Close</button> </div> </div>

 這將沒法正確渲染,除非咱們對scope作一些特殊處理。

  第一個咱們須要解決的問題是,dialog模版指望title會被定義,而在初始化時會與username綁定。此外,按鈕須要onOk、onCancel兩個function出如今scope裏面。這限制了插件的效能(this limits the usefulness of the widget。。。)。爲了解決映射問題,經過以下的本地方法(locals,估計在說directive定義模版中的scope)去建立模版期待的本地變量:

scope :{

    title: 'bind', // set up title to accept data-binding

    onOk: 'expression', // create a delegate onOk function

    onCancel: 'expression', // create a delegate onCancel function

    show: 'accessor' // create a getter/setter function for visibility.

}

  在控件scope中建立本地屬性,帶來了兩個問題:
  1. isolation(屬性隔離?) - 若是用戶忘記了在控件模版設置title這個元素屬性,那麼title將會與祖先scope的屬性」title」(若有)綁定。這是不可預知、不可取的。

  2. transclusion - transcluded DOM能夠查看控件的locals(isolate scope?),locals會覆蓋transclusion中真正須要綁定的屬性。在咱們的例子中,插件中的title屬性破壞了transclusion的title屬性。

  爲了解決這個缺少屬性隔離的問題,咱們須要爲這個directive定義一個isolated scope。isoloted scope不是經過從child scope(這裏爲啥是child scope?不是parent scope嗎?)原型繼承的,因此咱們無需擔憂屬性衝突問題(做爲當前scope的兄弟)。

  然而,isolated scope帶來了一個新問題:若是transcluded DOM 是控件的isolated scope的一個子元素,那麼他將不能與任何東西綁定(if a transcluded DOM is a child of the widget isolated scope then it will not be able to bind to anything)。所以,transcluded scope是原始scope的一個子scope,在控件爲本地屬性而建立isolated scope以前建立的。transcluded與控件isolated scope屬於兄弟節點(scope樹中)。

  這也許看起來會有點意想不到的複雜,但這樣作讓控件用戶與控件開發者至少帶來了驚喜。(解決了問題)

  所以,最終的directive定義大體以下:

transclude:true,

scope :{

    title: 'bind', // set up title to accept data-binding

    onOk: 'expression', // create a delegate onOk function

    onCancel: 'expression', // create a delegate onCancel function

    show: 'accessor' // create a getter/setter function for visibility.

    //我試過這麼整,失敗……請繼續往下看

}

我嘗試把上面的代碼拼湊成一個完整的例子。直接複製的話,沒法達到預期效果。但稍做修改後,插件能夠運行了。

<!DOCTYPE html>
<html ng-app="Dialog">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>directive-dialog</title>
    <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
    <script src="../angular.js" type="text/javascript"></script>
</head>
<body>
    <div ng-controller="MyCtrl">
        <button ng-click="show=true">show</button>
        <dialog title="Hello {{username}}"
                visible="{{show}}"
                on-cancel="show=false;"
                on-ok="show=false;methodInParentScope();">
                <!--上面的on-cancel、on-ok,是在directive的isoloate scope中經過&引用的。若是表達式中包含函數,那麼須要將函數綁定在parent scope(當前是MyCtrl的scope)中-->
            Body goes here: username:{{username}} , title:{{title}}.
            <ul>
                <!--這裏還能夠這麼玩~names是parent scope的-->
                <li ng-repeat="name in names">{{name}}</li>
            </ul>
        </dialog>
    </div>
    <script type="text/javascript">
        var myModule = angular.module("Dialog", []);
        myModule.controller("MyCtrl", function ($scope) {
            $scope.names = ["name1", "name2", "name3"];
            $scope.show = false;
            $scope.username = "Lcllao";
            $scope.title = "parent title";
            $scope.methodInParentScope = function() {
                alert("記住。。scope裏面經過&定義的東東,是在父scope中玩的!!。。。");
            };
        });
        myModule.directive('dialog', function factory() {
            return {
                priority:100,
                template:['<div ng-show="visible">',
                    '    <h3>{{title}}</h3>',
                    '    <div class="body" ng-transclude></div>',
                    '    <div class="footer">',
                    '        <button ng-click="onOk()">OK</button>',
                    '        <button ng-click="onCancel()">Close</button>',
                    '    </div>',
                    '</div>'].join(""),
                replace:false,
                transclude: true,
                restrict:'E',
                scope:{
                    title:"@",//引用dialog標籤title屬性的值
                    onOk:"&",//以wrapper function形式引用dialog標籤的on-ok屬性的內容
                    onCancel:"&",//以wrapper function形式引用dialog標籤的on-cancel屬性的內容
                    visible:"@"//引用dialog標籤visible屬性的值
                }
            };
        });
    </script>
</body>
</html>

十3、Creating Components

  咱們一般會指望經過複雜的DOM結構替換掉directive(所在的元素?目的大概是使directive內部複雜點,看起來牛點@_@)。這讓directive成爲使用可複用組件的創建應用的一個快捷方式。

  下面是一個可複用組件的例子:

<!DOCTYPE html>
<html ng-app="ZippyModule">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>ZippyModule</title>
    <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
    <style type="text/css">
        .zippy {
            border: 1px solid black;
            display: inline-block;
            width: 250px;
        }
        .zippy.opened > .title:before { content: '▼ '; }
        .zippy.opened > .body { display: block; }
        .zippy.closed > .title:before { content: '► '; }
        .zippy.closed > .body { display: none; }
        .zippy > .title {
            background-color: black;
            color: white;
            padding: .1em .3em;
            cursor: pointer;
        }
        .zippy > .body {
            padding: .1em .3em;
        }
    </style>
    <script src="../angular.js" type="text/javascript"></script>
</head>
<body>
    <div ng-controller="MyCtrl">
        Title: <input ng-model="title" type="text"><br/>
        Text: <textarea ng-model="text" ></textarea>
        <hr/>
        <div class="zippy" zippy-title="Details: {{title}}...">{{text}}</div>
    </div>
    <script type="text/javascript">
        var myModule = angular.module("ZippyModule", []);
        myModule.controller("MyCtrl", function ($scope) {
            $scope.title = "這裏是標題";
            $scope.text = "這裏是內容哇。。。";
        });
        myModule.directive('zippy', function () {
            return {
                template: '<div>' +
                        '   <div class="title">{{title}}</div>' +//這個title屬於當前directive isolate scope的property
                        '   <div class="body" ng-transclude></div>' + //這裏的東西,獲取的是父scope的property咯
                        '</div>',
                replace:true,
                transclude: true,
                restrict:'C',
                scope:{
                    title:"@zippyTitle"//綁定directive元素身上的zippy-title屬性
                },
                link:function(scope,element,attrs) {
                    var title = angular.element(element.children()[0]),
                            opened = false;

                    title.bind("click", toogle);
                    element.addClass("closed");

                    function toogle() {
                        opened = !opened;
                        element.removeClass(opened ? "closed" : "opened");
                        element.addClass(opened ? "opened" : "closed");
                    }
                }
            };
        });
    </script>
</body>
</html>
相關文章
相關標籤/搜索