2013-11-14 60 views
0

我需要一些關於重構我有的模態指令的建議。我剛剛開始使用指令,所以對我的問題採取其他任何方法都是值得歡迎的。AngularJS:重構確認模態指令

我的程序需要確認模式,我們可以確認或取消所需的操作。它會出現在很多地方,需要有一個可編程的按鈕。取消是一致的,因爲它只會隱藏模式,確認按鈕需要執行所需的任何操作。

我目前使用$rootScope來顯示/隱藏/配置模態。這是一個壞主意嗎?請告訴我。

這就是我的工作與現在(大概,因爲我已經削減了很多其他不必要的代碼):

的index.html

<!doctype html> 
<html lang="en"> 
    <head> 
     <title>My App</title> 
    </head> 
    <body ng-controller="MenuCtrl"> 

     <confirmmodal ng-show="$root.confirmModal.isVisible"></confirmmodal> 

     <ul> 
      <li>Home</li> 
      <li>About</li> 
      <li>Contact</li> 
     </ul> 

     <div ng-view></div> 

     <!-- build:js scripts/main.js --> 
     <script data-main="scripts/main" src="lib/requirejs/require.js"></script> 
     <!-- endbuild --> 
    </body> 
</html> 

所以我坐在模式在ng-view之上,並且可以從任何地方調用。它位於僞全局控制器內,稱爲MenuCtrl

這裏是模態指令代碼:

directives.js

/* Confirm Modal */ 
.directive('confirmmodal', [function() { 
    return { 
     restrict: 'E', 
     templateUrl: 'view/templates/modal-confirm.tpl.html' 
    }; 
}]) 

它作爲下面的代碼模板:

模式,confirm.tpl.html

<!-- Confirm Modal Template --> 
<div class="overlay"> 
    <div class="overlay-content extended"> 
     <span>{{$root.confirmModal.content}}</span> 
     <div class="buttons"> 
      <button class="btn btn-default" ng-click="$root.confirmModal.secondary.action()">{{$root.confirmModal.secondary.content}}</button> 
      <button class="btn btn-primary" ng-click="$root.confirmModal.primary.action()">{{$root.confirmModal.primary.content}}</button> 
     </div> 
    </div> 
</div> 

我設置了一堆違約的app.run功能:

app.js

app.run(['$rootScope', function ($rootScope) { 
    _.extend($rootScope, { 
     confirmModal: { 
      isVisible: false, 
      content: '', 
      primary: { 
       action: function() { 
        console.log('hello world'); 
       }, 
       content: 'Submit' 
      }, 
      secondary: { 
       action: function() { 
        $rootScope.confirmModal.isVisible = false; 
       }, 
       content: 'Cancel' 
      } 
     } 
    }); 
}]); 

所以我還編寫了一個模式觸發指令,該想法是,我可以創建不同的觸發器用模態執行不同的操作。

directives.js

/* Resolve Event */ 
.directive('resolveevent', ['RequestService', '$location', function (RequestService, $location) { 
    return { 
     restrict: 'A', 
     scope: { 
      eventtype: '@', 
      eventid: '@', 
      index: '@' 
     }, 
     controller: ['$scope', function($scope) { 

      $scope.remove = function(id) { 
       // remove the event from the events array 
       $scope.$parent.$parent.$parent.$parent.events.splice(id, 1); 
      }, 

      $scope.config = function(config) { 
       _.extend($scope.$root.confirmModal, config); 
      }, 

      $scope.isVisible = function() { 
       $scope.$apply(function() { 
        $scope.$root.confirmModal.isVisible = true; 
       }); 
      } 
     }], 
     link: function($scope, element, attrs) { 
      var config = { 
       content: 'Are you sure you wish to resolve this event?', 
       primary: { 
        action: function() { 
         var config = { 
          url: '/Events/' + $scope.eventid, 
          method: 'PUT', 
          data: { 
           event_status: 'resolved' 
          }, 
          cache: false 
         } 

         /* Update event with resolved status */ 
         RequestService.makeApiRequest(config).success(function(response) { 
          $scope.$root.confirmModal.isVisible = false; 
          $scope.remove($scope.index); 
         }); 
        }, 
        content: 'Resolve Event' 
       } 
      } 

      element.on('click', function() { 
       if (!$scope.$root.confirmModal.isVisible) { 
        $scope.config(config); 
        $scope.isVisible(); 
       } 
      }); 
     } 
    } 
}]); 

然後我用在我的ng-repeat找到該視圖,它能夠觸發模式按鈕:

eventlist.html

<li ng-repeat="event in events"> 

    <p>Event: {{ event.number }}</p> 
    <p>Group: {{ event.group_name }}</p> 
    <p>Record Date: {{ event.event_date | moment: 'MM/DD/YYYY h:mm A' }}</p> 

    <button resolveevent index="{{$index}}" eventid="{{ event.number }}" class="btn btn-default">Resolve</button> 
</li> 

這是我得到的,它正在工作,但它看起來像是矯枉過正,效率低下,而且是維護的噩夢。任何人都可以通過一種方式來改善這種狀況?感謝任何幫助,提前致謝。

+0

你有沒有考慮使用角度-UI-引導模塊?它有一個可用的模式窗口,並且需要更少的代碼。 http://angular-ui.github.io/bootstrap/ –

回答

0

我的方法可能不是根據最佳實踐,但我通常最終創建了可訪問模態範圍並操縱dom的專用服務。把它看作自注入指令。

這裏的模態的容器HTML(使用引導的造型):

<div class="modal-backdrop"></div> 
<div class="modal fade"> 
    <div class="modal-dialog" ng-style="{width: width}"> 
     <div class="modal-content"> 
      <div class="modal-header"> 
       <button type="button" class="close" ng-click="close()" aria-hidden="true">&times;</button> 
       <h4 class="modal-title">{{title}}</h4> 
      </div> 
      <div class="modal-body"> 

      </div> 
      <div class="modal-footer"> 
       <button ng-repeat="(name, callback) in buttons" type="button" ng-click="callback()">{{name}}</button> 
      </div> 
     </div> 
    </div> 
</div> 

然後有DialogService的僞代碼:由於服務的異步性質

.service('DialogService', function($compile, $http, $rootScope) { 
    this.open = function(options) { 
    //options contain various properties 
    //e.g. title, width, template or templateUrl, button map with callbacks 
    loadModalContainer() 
    .then(loadModalBody) 
    .then(init); 

    function init() { 
     modal = $('body').append(containerHtml).find('.modal'); 
     modal.append(bodyHtml); 
     scope = (options.scope || $rootScope).$new(); 
     if (options.controller) $controller(options.controller, {$scope: scope}); 
     $compile(modal)(scope); 
     listenForEscKey(); 
    } 
    function close() { 
     //clean up event listeners 
     // 
     if (options.onClose) options.onClose(); 
     scope.$destroy(); 
     $('body').find('.modal,.modal-backdrop').remove(); 
    } 
    } 
}); 

,當然,你如果第二個模式彈出,必須實現一些自動關閉邏輯。從那裏,真的很容易,你可以定義具體的對話作爲獨立服務抽象掉的細節:

.service('TermsModal', function(DialogService) { 
    this.open = function(acceptCallback, declineCallback, scope) { 
     DialogService.open({ 
     templateUrl: '', 
     width: '', 
     buttons: { 
      accept: acceptCallback, 
      decline: declineCallback 
     }, 
     scope: scope 
     }); 
    } 
}) 

然後從任何控制器,你可以打開模式有一行代碼:TermsModal.open(acceptCallback, declineCallback, $scope)

有幾個問題。首先,使用transclusion會很好,因爲現在modal的子範圍裏散佈着title, buttons, width屬性。

另一件事是我傳遞模態身體的寬度,但這只是我的懶惰(我無法正確設置引導模式身體寬度,因爲它是硬編碼)。

另外,我從控制器傳遞本地範圍,因爲經常使用模態的主體內容是以某種方式與調用模式的控制器相關的。例如,如果我們將ItemController的item作爲範圍屬性,並且我們有一個編輯按鈕來編輯模式中的項目值,那麼子範圍必須知道它正在處理的模型。所以無論是傳遞範圍還是直接在選項中傳遞需要的值。我更喜歡範圍,因爲它提供了更大的靈活性,並且在子範圍初始化時,很難搞亂原始模型。總而言之,這種設置的強大和靈活性證明了服務與DOM有點混亂的事實是合理的。你的rootScope變得沒有全局狀態(服務管理自己的狀態,而不會給外部世界提供細節),你的主模板沒有模態部分/指令/可能使用也可能不使用。

+0

你的回答對我有用!我做了這個:https://gist.github.com/g-alonso/8278443 – Gabriel

0

我創建了,打開一個模式,並執行您想要的代碼,如果該模式被確認小的確認指令:

app.html

<button type="button" class="btn btn-default" 
nait-confirm-click 
confirm="Do you really want to remove this record?" 
confirm-if="user.disabled == true" 
do="remove(user)"> 
    Remove 
</button> 

的script.js

angular 
    .module('xyz', ['ui.bootstrap']) 
    .directive('naitConfirmClick', function($modal, $parse) { 
     return { 
      restrict: 'EA', 
      link: function(scope, element, attrs) { 
       if (!attrs.do) { 
        return; 
       } 

       // register the confirmation event 
       var confirmButtonText = attrs.confirmButtonText ? attrs.confirmButtonText : 'OK'; 
       var cancelButtonText = attrs.cancelButtonText ? attrs.cancelButtonText : 'Cancel'; 
       element.click(function() { 
        // action that should be executed if user confirms 
        var doThis = $parse(attrs.do); 

        // condition for confirmation 
        if (attrs.confirmIf) { 
         var confirmationCondition = $parse(attrs.confirmIf); 
         if (!confirmationCondition(scope)) { 
          // if no confirmation is needed, we can execute the action and leave 
          doThis(scope); 
          scope.$apply(); 
          return; 
         } 
        } 
        $modal 
         .open({ 
          template: '<div class="modal-body">' + attrs.confirm + '</div>' 
           + '<div class="modal-footer">' 
           +  '<button type="button" class="btn btn-default btn-naitsirch-confirm pull-right" ng-click="$close(\'ok\')">' + confirmButtonText + '</button>' 
           +  '<button type="button" class="btn btn-default btn-naitsirch-cancel pull-right" ng-click="$dismiss(\'cancel\')">' + cancelButtonText + '</button>' 
           + '</div>' 
         }) 
         .result.then(function() { 
          doThis(scope); 
          scope.$apply() 
         }) 
        ; 
       }); 
      } 
     }; 
    }) 
; 

現在,如果您點擊按鈕與nait-confirm-click它打開一個帶有兩個按鈕的模式,並通過傳遞的文本確認屬性。如果你點擊取消按鈕,什麼都不會發生。如果通過點擊「確定」進行確認,則將執行通過確定的表達式屬性。

如果您在可選的confirm-if屬性中傳遞表達式,則只有在表達式爲true時纔會打開模式。如果表達式爲false,則該動作將被執行而不詢問。

我希望這個片段將幫助別人;)

1

一個簡單的指令,確認:

/** 
* A generic confirmation for risky actions. 
* Usage: Add attributes: ng-really-message="Really?" ng-really-click="takeAction()" function 
*/ 
angular.module('app').directive('ngReallyClick', [function() { 
    return { 
     restrict: 'A', 
     link: function(scope, element, attrs) { 
      element.bind('click', function() { 
       var message = attrs.ngReallyMessage; 
       if (message && confirm(message)) { 
        scope.$apply(attrs.ngReallyClick); 
       } 
      }); 
     } 
    } 
}]); 
+1

好的答案@EpokK,你不是一個新手! – lightalex