2017-10-03 11 views
1

私のVueアプリケーションにはかなり単純なコンポーネントがあります。ユーザーがメニューを開くと、イベントリスナーを#app要素に追加して、「外側」のクリックを検出します。removeEventListenerが期待どおりに動作しない

ユーザーが外部をクリックすると、メニューが閉じられ、イベントリスナーが削除されます。これは期待どおりに動作しています。問題は、ユーザーがメニューを開くためにボタンをクリックし、メニューを閉じるためにもう一度クリックすると、removeEventListenerになります。

これは機能しません。ボタンを介してメニューを開き、ボタンを押して閉じると、イベントリスナは削除しても#app要素に残ります。

ボタンを複数回クリックしてから、値がtrueと表示されている間にボタンの外側をクリックすると、コンソールログoutside clickが複数回表示されます。

new Vue({ 
 
    el: "#app", 
 
    data: { 
 
     menuVisible: false 
 
    }, 
 
    methods: { 
 
     click(e) { 
 
      var clickSource = e.target; 
 
      var app = document.getElementById("app"); 
 
      var that = this; 
 

 
      function clickOutside(e) { 
 
       if (e.target != clickSource) { 
 
        console.log("outside click"); 
 
        that.menuVisible = false; 
 
        app.removeEventListener("click", clickOutside); 
 
       } 
 
      } 
 

 
      if (this.menuVisible === false) { 
 
       this.menuVisible = true; 
 
       app.addEventListener("click", clickOutside); 
 
      } else if (this.menuVisible === true) { 
 
       this.menuVisible = false; 
 
       app.removeEventListener("click", clickOutside); 
 
      } 
 
     } 
 
    } 
 
});
body { 
 
    margin: 0; 
 
} 
 

 
#app { 
 
min-height: 100vh; 
 
}
<script src="https://unpkg.com/vue"></script> 
 

 
<div id="app"> 
 
    {{menuVisible}} 
 
    <button @click="click"> 
 
    click 
 
    </button> 
 
</div>

+0

、あなたはそれがほぼ 'removeEventListener'が働かない唯一の理由だ' addEventListener'(に渡さremoveEventListener' 'に同じ機能を渡していない。他のさ間違った要素で呼び出すか、別の第3引数を渡す)、オフサイトに出ることなく、私は確信が持てません。 –

+1

@ T.J.Crowder申し訳ありませんが、私はよく知っている必要があります。私は質問を更新します。 – justinw

答えて

1

問題は、すべてのclickに、あなたは新しいclickOutside機能を作成しているということです。 removeEventListenerでその関数を使用すると、という正確な関数オブジェクトのイベント登録がないので、何も削除されません。あなたが除去のためにそれを使用できるように

代わりに、ハンドラオブジェクトを覚えて、ノートを参照してください。

new Vue({ 
 
    el: "#app", 
 
    data: { 
 
     menuVisible: false, 
 
     clickOutside: null 
 
    }, 
 
    methods: { 
 
     click(e) { 
 
      var clickSource = e.target; 
 
      var app = document.getElementById("app"); 
 
      var that = this; 
 
      
 
      // Only create it if we don't already have it 
 
      if (!this.clickOutside) { 
 
       // Create it 
 
       this.clickOutside = function clickOutside(e) { 
 
        console.log("clickOutside triggered"); 
 
        if (e.target != clickSource) { 
 
         console.log("It's an outside click"); 
 
         that.menuVisible = false; 
 
         // Remove it 
 
         app.removeEventListener("click", that.clickOutside); 
 
        } 
 
       }; 
 
      } 
 

 
      if (this.menuVisible === false) { 
 
       this.menuVisible = true; 
 
       app.addEventListener("click", this.clickOutside); 
 
      } else if (this.menuVisible === true) { 
 
       this.menuVisible = false; 
 
       app.removeEventListener("click", this.clickOutside); 
 
      } 
 
     } 
 
    } 
 
});
body { 
 
    margin: 0; 
 
} 
 

 
#app { 
 
    min-height: 100vh; 
 
    background-color: #ddd; 
 
}
<script src="https://unpkg.com/vue"></script> 
 

 
<div id="app"> 
 
    {{menuVisible}} 
 
    <button @click="click"> 
 
    click 
 
    </button> 
 
</div>


サイドノートを:それは使用することがより慣用的だ

if (this.menuVisible) { 
    this.menuVisible = false; 
    app.removeEventListener("click", this.clickOutside); 
} else { 
    this.menuVisible = true; 
    app.addEventListener("click", this.clickOutside); 
} 

またはさらに

​​

...というよりも===trueまたはfalseにブール値を比較します。あなたはすでにのブール値を持っていますので、私はどこで止めますか? if ((this.menuVisible) === true === true)? :-)

+0

サイドノートの良い点。私は確かに私のコードの "冗長性"を改善する方法を持っています:/ – justinw

1

これは私にとってX-Yの問題のように感じます。理想的に言えば、JS(またはVueJS even)のイベントリスナーを動的にバインド/アンバインドするべきではありません。あなたは、単に以下の行動をしたい場合:

  • ボタンをクリックし、メニューをトグルボタンの外側アプリの
  • をクリックして、メニュー

を閉じて...そしてあなたも厄介なイベントを必要としません取り扱い。第1の動作は、単純な真/偽のスイッチ、すなわち

this.menuVisible != this.menuVisible 

あなたはVueJSアプリまでバブリングからのイベントをクリックして停止することができますので、ボタンを@clickイベントバインダーに.stopを使用することを忘れないでください。

一方、#appではボタンの外側でクリックイベントが検出された場合、this.menuVisiblefalseに設定するだけで2番目の動作を実行できます。

推測で

new Vue({ 
 
    el: "#app", 
 
    data: { 
 
    menuVisible: false 
 
    }, 
 
    methods: { 
 
    click(e) { 
 
     console.log('button clicked'); 
 
     this.menuVisible = !this.menuVisible; 
 
    }, 
 

 
    appClick(e) { 
 
     console.log('outside clicked'); 
 
     this.menuVisible = false; 
 
    } 
 
    } 
 
});
body { 
 
    margin: 0; 
 
} 
 

 
#app { 
 
    min-height: 100vh; 
 
}
<script src="https://unpkg.com/vue"></script> 
 

 
<div id="app" @click="appClick"> 
 
    {{menuVisible}} 
 
    <button @click.stop="click"> 
 
    click 
 
    </button> 
 
</div>

+0

これは私の例にとっては完璧な解決策です。しかし、私は問題の簡略版を作成しました。実際には、この機能は、アプリケーション全体で再利用される単一ファイルのVueコンポーネントに存在します。 – justinw

+0

@justinwうわー、私は、単一ファイルのVueコンポーネントがどのように問題になるかは分かりません。丁寧に気遣う? – Terry

+0

正直言って私は完全にはわかりません...アプリでは、 '#app'がレイアウトにあります(' default.vue' - nuxtを使用しています)。この質問で参照されるメニューコンポーネントは、アプリ全体で数回以上使用され、常に他のコンポーネントによって使用されます。たとえば、 'appHeader.vue'はメニューコンポーネントをインポートします。だから私は 'default.vue'レイアウトに' appClick'メソッドを作成した場合、それを 'appHeader'に小道具として渡し、次にメニューコンポーネントを小道具として渡し、すべてのコンポーネントのためにそれをやり直す必要があると思いましたメニューコンポーネントを使用します。再び、私はVueには新しいので、私は/おそらく間違っている可能性があります。 – justinw

関連する問題