]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Enable event filtering per plugin (#8876)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Sat, 10 Apr 2021 19:18:30 +0000 (22:18 +0300)
committerGitHub <noreply@github.com>
Sat, 10 Apr 2021 19:18:30 +0000 (15:18 -0400)
docs/configuration/interactions.md
src/core/core.controller.js
src/core/core.plugins.js
test/specs/core.plugin.tests.js
test/specs/plugin.tooltip.tests.js

index 32824099b98aa88eb1b17248f320e04f637977d7..2b7a44f9ac0a0281b941c53cfcdbf6ee73ac4206 100644 (file)
@@ -17,7 +17,7 @@ Namespace: `options`
 
 | Name | Type | Default | Description
 | ---- | ---- | ------- | -----------
-| `events` | `string[]` | `['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove']` | The `events` option defines the browser events that the chart should listen to for tooltips and hovering. [more...](#event-option)
+| `events` | `string[]` | `['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove']` | The `events` option defines the browser events that the chart should listen to for. Each of these events trigger hover and are passed to plugins. [more...](#event-option)
 | `onHover` | `function` | `null` | Called when any of the events fire. Passed the event, an array of active elements (bars, points, etc), and the chart.
 | `onClick` | `function` | `null` | Called if the event is of type `'mouseup'` or `'click'`. Passed the event, an array of active elements, and the chart.
 
@@ -27,12 +27,32 @@ For example, to have the chart only respond to click events, you could do:
 
 ```javascript
 var chart = new Chart(ctx, {
-    type: 'line',
-    data: data,
-    options: {
-        // This chart will not respond to mousemove, etc
+  type: 'line',
+  data: data,
+  options: {
+    // This chart will not respond to mousemove, etc
+    events: ['click']
+  }
+});
+```
+
+Events for each plugin can be further limited by defining (allowed) events array in plugin options:
+
+```javascript
+var chart = new Chart(ctx, {
+  type: 'line',
+  data: data,
+  options: {
+    // All of these (default) events trigger a hover and are passed to all plugins,
+    // unless limited at plugin options
+    events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
+    plugins: {
+      tooltip: {
+        // Tooltip will only receive click events
         events: ['click']
+      }
     }
+  }
 });
 ```
 
index 6e6bfec13e191568834992b0a2c3ec319e282bf8..34b342014d8331cac64f4155142e456ddc280086 100644 (file)
@@ -1018,10 +1018,11 @@ class Chart {
         * returned value can be used, for instance, to interrupt the current action.
         * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
         * @param {Object} [args] - Extra arguments to apply to the hook call.
+   * @param {import('./core.plugins').filterCallback} [filter] - Filtering function for limiting which plugins are notified
         * @returns {boolean} false if any of the plugins return false, else returns true.
         */
-  notifyPlugins(hook, args) {
-    return this._plugins.notify(this, hook, args);
+  notifyPlugins(hook, args, filter) {
+    return this._plugins.notify(this, hook, args, filter);
   }
 
   /**
@@ -1049,15 +1050,16 @@ class Chart {
   _eventHandler(e, replay) {
     const me = this;
     const args = {event: e, replay, cancelable: true};
+    const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.type);
 
-    if (me.notifyPlugins('beforeEvent', args) === false) {
+    if (me.notifyPlugins('beforeEvent', args, eventFilter) === false) {
       return;
     }
 
     const changed = me._handleEvent(e, replay);
 
     args.cancelable = false;
-    me.notifyPlugins('afterEvent', args);
+    me.notifyPlugins('afterEvent', args, eventFilter);
 
     if (changed || args.changed) {
       me.render();
index c4c6da25c2869c1fbd4bb87c17eaca00c39eeee1..29f98cf291f31651d4832b0d742119b600d274df 100644 (file)
@@ -7,6 +7,16 @@ import {callback as callCallback, isNullOrUndef, valueOrDefault} from '../helper
  * @typedef { import("../plugins/plugin.tooltip").default } Tooltip
  */
 
+/**
+ * @callback filterCallback
+ * @param {{plugin: object, options: object}} value
+ * @param {number} [index]
+ * @param {array} [array]
+ * @param {object} [thisArg]
+ * @return {boolean}
+ */
+
+
 export default class PluginService {
   constructor() {
     this._init = [];
@@ -19,9 +29,10 @@ export default class PluginService {
         * @param {Chart} chart - The chart instance for which plugins should be called.
         * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
         * @param {object} [args] - Extra arguments to apply to the hook call.
+   * @param {filterCallback} [filter] - Filtering function for limiting which plugins are notified
         * @returns {boolean} false if any of the plugins return false, else returns true.
         */
-  notify(chart, hook, args) {
+  notify(chart, hook, args, filter) {
     const me = this;
 
     if (hook === 'beforeInit') {
@@ -29,7 +40,7 @@ export default class PluginService {
       me._notify(me._init, chart, 'install');
     }
 
-    const descriptors = me._descriptors(chart);
+    const descriptors = filter ? me._descriptors(chart).filter(filter) : me._descriptors(chart);
     const result = me._notify(descriptors, chart, hook, args);
 
     if (hook === 'destroy') {
index b699a87754ccd8a215fb4f1ed6656f96124bb194..5c1c9cccb8d6a1516b4bf232d6300b280903133e 100644 (file)
@@ -393,5 +393,32 @@ describe('Chart.plugins', function() {
         plugins: [plugin]
       });
     });
+
+    it('should filter event callbacks by plugin events array', async function() {
+      const results = [];
+      const chart = window.acquireChart({
+        options: {
+          events: ['mousemove', 'test', 'test2'],
+          plugins: {
+            testPlugin: {
+              events: ['test']
+            }
+          }
+        },
+        plugins: [{
+          id: 'testPlugin',
+          beforeEvent: function(_chart, args) {
+            results.push('before' + args.event.type);
+          },
+          afterEvent: function(_chart, args) {
+            results.push('after' + args.event.type);
+          }
+        }]
+      });
+      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 0, y: 0});
+      await jasmine.triggerMouseEvent(chart, 'test', {x: 0, y: 0});
+      await jasmine.triggerMouseEvent(chart, 'test2', {x: 0, y: 0});
+      expect(results).toEqual(['beforetest', 'aftertest']);
+    });
   });
 });
index 8060514bcfc351d488a87a2613adb8f5e12a3332..d7bc06682df1f7a9c713276fa513782d02b9c4fc 100644 (file)
@@ -1536,8 +1536,8 @@ describe('Plugin.Tooltip', function() {
     });
   });
 
-  describe('active events', function() {
-    it('should set the active events', function() {
+  describe('active elements', function() {
+    it('should set the active elements', function() {
       var chart = window.acquireChart({
         type: 'line',
         data: {
@@ -1556,4 +1556,36 @@ describe('Plugin.Tooltip', function() {
       expect(chart.tooltip.getActiveElements()[0].element).toBe(meta.data[0]);
     });
   });
+
+  describe('events', function() {
+    it('should not be called on events not in plugin events array', async function() {
+      var chart = window.acquireChart({
+        type: 'line',
+        data: {
+          datasets: [{
+            label: 'Dataset 1',
+            data: [10, 20, 30],
+            pointHoverBorderColor: 'rgb(255, 0, 0)',
+            pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+          }],
+          labels: ['Point 1', 'Point 2', 'Point 3']
+        },
+        options: {
+          plugins: {
+            tooltip: {
+              events: ['click']
+            }
+          }
+        }
+      });
+
+      const meta = chart.getDatasetMeta(0);
+      const point = meta.data[1];
+
+      await jasmine.triggerMouseEvent(chart, 'mousemove', point);
+      expect(chart.tooltip.opacity).toEqual(0);
+      await jasmine.triggerMouseEvent(chart, 'click', point);
+      expect(chart.tooltip.opacity).toEqual(1);
+    });
+  });
 });