Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable event filtering per plugin #8876

Merged
merged 1 commit into from Apr 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 25 additions & 5 deletions docs/configuration/interactions.md
Expand Up @@ -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.

Expand All @@ -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']
}
}
}
});
```

Expand Down
10 changes: 6 additions & 4 deletions src/core/core.controller.js
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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();
Expand Down
15 changes: 13 additions & 2 deletions src/core/core.plugins.js
Expand Up @@ -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 = [];
Expand All @@ -19,17 +29,18 @@ 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') {
me._init = me._createDescriptors(chart, true);
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') {
Expand Down
27 changes: 27 additions & 0 deletions test/specs/core.plugin.tests.js
Expand Up @@ -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']);
});
});
});
36 changes: 34 additions & 2 deletions test/specs/plugin.tooltip.tests.js
Expand Up @@ -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: {
Expand All @@ -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);
});
});
});