29

これはこの質問に対するフォローアップの質問です: AngularJS input with focus kills ng-repeat filter of list

基本的に、私のコードは AngularJS を使用して、右側に div (ドロワー) をポップアウトし、リストをフィルタリングしています。ほとんどの場合、これは UI がブロックされているため、ブロックしている div をクリックするとドロワーが閉じます。ただし、場合によっては、UI をブロックせず、ユーザーがドロワーの外側をクリックして検索をキャンセルしたり、ページ上の何かを選択したりできるようにする必要があります。

私の最初の考えは、引き出しが開いたときに window.onclick ハンドラーをアタッチし、引き出し以外のものがクリックされた場合は引き出しを閉じることでした。それが、純粋な JavaScript アプリで行う方法です。しかし、Angular では少し難しくなっています。

これは、@ YoshiのjsBinの例に基づいたサンプルを含むjsfiddleです: http://jsfiddle.net/tpeiffer/kDmn8/

このサンプルの関連するコードは次のとおりです。基本的に、ユーザーがドロワーの外側をクリックすると、$scope.toggleSearch が呼び出され、$scope.open が false に戻されます。

コードは機能し、$scope.open が true から false に変わっても、DOM は変更されません。これは、イベントのライフサイクルに関係があると確信しています。または、おそらく $scope を変更すると (別のイベントからのものであるため)、元のものではなくコピーであると確信しています...

これに関する最終的な目標は、最終的な再利用性のための指令になることです。誰かが私を正しい方向に向けることができれば、私は感謝します.

$scope.toggleSearch = function () {

    $scope.open = !$scope.open;

    if ($scope.open) {
        $scope.$window.onclick = function (event) {
            closeSearchWhenClickingElsewhere(event, $scope.toggleSearch);
        };
    } else {
        $scope.$window.onclick = null;
    }
};

function closeSearchWhenClickingElsewhere(event, callbackOnClose) {

    var clickedElement = event.target;
    if (!clickedElement) return;

    var elementClasses = clickedElement.classList;
    var clickedOnSearchDrawer = elementClasses.contains('handle-right') 
        || elementClasses.contains('drawer-right') 
        || (clickedElement.parentElement !== null 
            && clickedElement.parentElement.classList.contains('drawer-right'));
    if (!clickedOnSearchDrawer) {
        callbackOnClose();
    }

}
4

4 に答える 4

24

ダイジェスト サイクルの外でクリック イベントが発生し、Angular は $scope.open が変更されたことを認識しないため、ドロワーは閉じません。これを修正するには、$scope.open が false に設定された後に $scope.$apply() を呼び出すことができます。これにより、ダイジェスト サイクルがトリガーされます。

$scope.toggleSearch = function () {
    $scope.open = !$scope.open;
    if ($scope.open) {
        $scope.$window.onclick = function (event) {
            closeSearchWhenClickingElsewhere(event, $scope.toggleSearch);
        };
    } else {
        $scope.open = false;
        $scope.$window.onclick = null;
        $scope.$apply(); //--> trigger digest cycle and make angular aware. 
    }
};

これがあなたのFiddleです。

それが役立つ場合は、検索ドロワーのディレクティブも作成しようとしていました(いくつかの修正が必要です:))。ここにJSBinがあります。

于 2013-07-17T19:30:37.407 に答える
12

$event.stopPropagation(); を追加することをお勧めします。ng-clickの直後のビューで。jQuery を使用する必要はありません。

<button ng-click="toggleSearch();$event.stopPropagation();">toggle</button>

次に、この単純化された js を使用できます。

$scope.toggleSearch = function () {
    $window.onclick = null;
    $scope.menuOpen = !$scope.menuOpen;

    if ($scope.model.menuOpen) {
        $window.onclick = function (event) {
            $scope.menuOpen = false;
            $scope.$apply();
        };
    }
};
于 2014-01-16T01:11:57.187 に答える
3

ボタンをクリックして引き出し/ポップアップを閉じ、ボタンがその外側にある場合、受け入れられた回答はエラーをスローします。これは、 $apply()2回実行されるためです。

これは単純化されたバージョンで、関数全体を再度呼び出す必要がtoggleSearch()なく、 double を防ぎ$apply()ます。

$scope.toggleSearch = function() {

  $scope.open = !$scope.open;

  if ($scope.open) {
    $window.onclick = function(event) {
      var clickedElement = event.target;
      if (!clickedElement) return;

      var clickedOnSearchDrawer = elementClasses.contains('handle-right') || elementClasses.contains('drawer-right') || (clickedElement.parentElement !== null && clickedElement.parentElement.classList.contains('drawer-right'));

      if (!clickedOnSearchDrawer) {
        $scope.open = !$scope.open;
        $window.onclick = null;
        $scope.$apply();
      }
    }
  } else {
    $window.onclick = null;
  }
};
于 2014-09-08T15:23:00.210 に答える
2

100%満足できる解決策が見つかりませんでした。これは私が使用したものです:

<div class="options">
    <span ng-click="toggleAccountMenu($event)">{{ email }}</span>
    <div ng-show="accountMenu" class="accountMenu">
        <a ng-click="go('account')">Account</a>
        <a ng-click="go('logout')">Log Out</a>
    </div>
</div>

ng-click のスパンはメニューを開くために使用され、div.accountMenu は開いたり閉じたりします

$scope.accountMenu = false;
$scope.toggleAccountMenu = function(e){
    if(e) e.stopPropagation();
    $scope.accountMenu = !$scope.accountMenu;
    if ($scope.accountMenu) {
        $window.onclick = function(e) {
            var target = $(e.target);
            if(!target) return;
            if(!target.hasClass('accountMenu') && !target.is($('.accountMenu').children())){
                $scope.toggleAccountMenu();
            }               
        };
    } else if (!e) {
        $window.onclick = null;
        $scope.$apply();
    }
}

これは子のチェックにjQueryを使用しますが、必要に応じてjQueryを使用しないで実行できます。

すでにサイクルにあるときに $apply() を呼び出そうとするなど、他の人のバージョンでいくつかの厄介なエラーが発生していました。私のバージョンでは、$apply() に対する伝播と安全チェックが防止されます。

于 2013-11-23T11:25:39.820 に答える