From c1aeba1e67593d0cf1bd75b50fb2c7724f768cee Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 2 Dec 2020 06:51:47 +0200 Subject: [PATCH] Add new scale hooks for plugins (#8112) * Add new scale hooks for plugins * Add notes * cc * cancelability --- src/core/core.controller.js | 26 +-- src/core/core.plugins.js | 271 +--------------------------- src/core/core.scale.js | 14 +- test/specs/core.controller.tests.js | 8 + test/specs/core.plugin.tests.js | 2 +- types/core/index.d.ts | 58 ++++-- 6 files changed, 79 insertions(+), 300 deletions(-) diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 7d2c564f9..a0a8b33f8 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -440,7 +440,6 @@ class Chart { update(mode) { const me = this; - const args = {mode}; let i, ilen; each(me.scales, (scale) => { @@ -458,7 +457,7 @@ class Chart { // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 me._plugins.invalidate(); - if (me.notifyPlugins('beforeUpdate', args) === false) { + if (me.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) { return; } @@ -480,7 +479,7 @@ class Chart { me._updateDatasets(mode); // Do this before render so that any plugins that need final scale updates can use it - me.notifyPlugins('afterUpdate', args); + me.notifyPlugins('afterUpdate', {mode}); me._layers.sort(compare2Level('z', '_idx')); @@ -500,7 +499,7 @@ class Chart { _updateLayout() { const me = this; - if (me.notifyPlugins('beforeLayout') === false) { + if (me.notifyPlugins('beforeLayout', {cancelable: true}) === false) { return; } @@ -531,9 +530,8 @@ class Chart { _updateDatasets(mode) { const me = this; const isFunction = typeof mode === 'function'; - const args = {mode}; - if (me.notifyPlugins('beforeDatasetsUpdate', args) === false) { + if (me.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) { return; } @@ -541,7 +539,7 @@ class Chart { me._updateDataset(i, isFunction ? mode({datasetIndex: i}) : mode); } - me.notifyPlugins('afterDatasetsUpdate', args); + me.notifyPlugins('afterDatasetsUpdate', {mode}); } /** @@ -552,7 +550,7 @@ class Chart { _updateDataset(index, mode) { const me = this; const meta = me.getDatasetMeta(index); - const args = {meta, index, mode}; + const args = {meta, index, mode, cancelable: true}; if (me.notifyPlugins('beforeDatasetUpdate', args) === false) { return; @@ -560,12 +558,13 @@ class Chart { meta.controller._update(mode); + args.cancelable = false; me.notifyPlugins('afterDatasetUpdate', args); } render() { const me = this; - if (me.notifyPlugins('beforeRender') === false) { + if (me.notifyPlugins('beforeRender', {cancelable: true}) === false) { return; } @@ -593,7 +592,7 @@ class Chart { return; } - if (me.notifyPlugins('beforeDraw') === false) { + if (me.notifyPlugins('beforeDraw', {cancelable: true}) === false) { return; } @@ -650,7 +649,7 @@ class Chart { _drawDatasets() { const me = this; - if (me.notifyPlugins('beforeDatasetsDraw') === false) { + if (me.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) { return; } @@ -675,6 +674,7 @@ class Chart { const args = { meta, index: meta.index, + cancelable: true }; if (me.notifyPlugins('beforeDatasetDraw', args) === false) { @@ -692,6 +692,7 @@ class Chart { unclipArea(ctx); + args.cancelable = false; me.notifyPlugins('afterDatasetDraw', args); } @@ -1004,7 +1005,7 @@ class Chart { */ _eventHandler(e, replay) { const me = this; - const args = {event: e, replay}; + const args = {event: e, replay, cancelable: true}; if (me.notifyPlugins('beforeEvent', args) === false) { return; @@ -1012,6 +1013,7 @@ class Chart { const changed = me._handleEvent(e, replay); + args.cancelable = false; me.notifyPlugins('afterEvent', args); if (changed) { diff --git a/src/core/core.plugins.js b/src/core/core.plugins.js index f859734d4..91e3c61a4 100644 --- a/src/core/core.plugins.js +++ b/src/core/core.plugins.js @@ -49,7 +49,7 @@ export default class PluginService { const plugin = descriptor.plugin; const method = plugin[hook]; const params = [chart, args, descriptor.options]; - if (callCallback(method, params, plugin) === false) { + if (callCallback(method, params, plugin) === false && args.cancelable) { return false; } } @@ -149,272 +149,3 @@ function createDescriptors(plugins, options, all) { return result; } - -/** - * Plugin extension hooks. - * @interface Plugin - * @typedef {object} IPlugin - * @since 2.1.0 - */ -/** - * @method IPlugin#install - * @desc Called when plugin is installed for this chart instance. This hook is called on disabled plugins (options === false). - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @since 3.0.0 - */ -/** - * @method IPlugin#start - * @desc Called when a plugin is starting. This happens when chart is created or plugin is enabled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @since 3.0.0 - */ -/** - * @method IPlugin#stop - * @desc Called when a plugin stopping. This happens when chart is destroyed or plugin is disabled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @since 3.0.0 - */ -/** - * @method IPlugin#beforeInit - * @desc Called before initializing `chart`. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#afterInit - * @desc Called after `chart` has been initialized and before the first update. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeUpdate - * @desc Called before updating `chart`. If any plugin returns `false`, the update - * is cancelled (and thus subsequent render(s)) until another `update` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {string} args.mode - The update mode - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart update. - */ -/** - * @method IPlugin#afterUpdate - * @desc Called after `chart` has been updated and before rendering. Note that this - * hook will not be called if the chart update has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {string} args.mode - The update mode - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#reset - * @desc Called during chart reset - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @since version 3.0.0 - */ -/** - * @method IPlugin#beforeDatasetsUpdate - * @desc Called before updating the `chart` datasets. If any plugin returns `false`, - * the datasets update is cancelled until another `update` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {string} args.mode - The update mode - * @param {object} options - The plugin options. - * @returns {boolean} false to cancel the datasets update. - * @since version 2.1.5 -*/ -/** - * @method IPlugin#afterDatasetsUpdate - * @desc Called after the `chart` datasets have been updated. Note that this hook - * will not be called if the datasets update has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {string} args.mode - The update mode - * @param {object} options - The plugin options. - * @since version 2.1.5 - */ -/** - * @method IPlugin#beforeDatasetUpdate - * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin - * returns `false`, the datasets update is cancelled until another `update` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {number} args.index - The dataset index. - * @param {object} args.meta - The dataset metadata. - * @param {string} args.mode - The update mode - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart datasets drawing. - */ -/** - * @method IPlugin#afterDatasetUpdate - * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note - * that this hook will not be called if the datasets update has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {number} args.index - The dataset index. - * @param {object} args.meta - The dataset metadata. - * @param {string} args.mode - The update mode - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeLayout - * @desc Called before laying out `chart`. If any plugin returns `false`, - * the layout update is cancelled until another `update` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart layout. - */ -/** - * @method IPlugin#afterLayout - * @desc Called after the `chart` has been laid out. Note that this hook will not - * be called if the layout update has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeRender - * @desc Called before rendering `chart`. If any plugin returns `false`, - * the rendering is cancelled until another `render` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart rendering. - */ -/** - * @method IPlugin#afterRender - * @desc Called after the `chart` has been fully rendered (and animation completed). Note - * that this hook will not be called if the rendering has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeDraw - * @desc Called before drawing `chart` at every animation frame. If any plugin returns `false`, - * the frame drawing is cancelled until another `render` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart drawing. - */ -/** - * @method IPlugin#afterDraw - * @desc Called after the `chart` has been drawn. Note that this hook will not be called - * if the drawing has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeDatasetsDraw - * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, - * the datasets drawing is cancelled until another `render` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart datasets drawing. - */ -/** - * @method IPlugin#afterDatasetsDraw - * @desc Called after the `chart` datasets have been drawn. Note that this hook - * will not be called if the datasets drawing has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeDatasetDraw - * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets - * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing - * is cancelled until another `render` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {number} args.index - The dataset index. - * @param {object} args.meta - The dataset metadata. - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart datasets drawing. - */ -/** - * @method IPlugin#afterDatasetDraw - * @desc Called after the `chart` datasets at the given `args.index` have been drawn - * (datasets are drawn in the reverse order). Note that this hook will not be called - * if the datasets drawing has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {number} args.index - The dataset index. - * @param {object} args.meta - The dataset metadata. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeTooltipDraw - * @desc Called before drawing the `tooltip`. If any plugin returns `false`, - * the tooltip drawing is cancelled until another `render` is triggered. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {Tooltip} args.tooltip - The tooltip. - * @param {object} options - The plugin options. - * @returns {boolean} `false` to cancel the chart tooltip drawing. - */ -/** - * @method IPlugin#afterTooltipDraw - * @desc Called after drawing the `tooltip`. Note that this hook will not - * be called if the tooltip drawing has been previously cancelled. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {Tooltip} args.tooltip - The tooltip. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#beforeEvent - * @desc Called before processing the specified `event`. If any plugin returns `false`, - * the event will be discarded. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {ChartEvent} args.event - The event object. - * @param {boolean} args.replay - True if this event is replayed from `Chart.update` - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#afterEvent - * @desc Called after the `event` has been consumed. Note that this hook - * will not be called if the `event` has been previously discarded. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {ChartEvent} args.event - The event object. - * @param {boolean} args.replay - True if this event is replayed from `Chart.update` - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#resize - * @desc Called after the chart as been resized. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {number} args.size - The new canvas display size (eq. canvas.style width & height). - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#destroy - * @desc Called after the chart has been destroyed. - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - */ -/** - * @method IPlugin#uninstall - * @desc Called after chart is destroyed on all plugins that were installed for that chart. This hook is called on disabled plugins (options === false). - * @param {Chart} chart - The chart instance. - * @param {object} args - The call arguments. - * @param {object} options - The plugin options. - * @since 3.0.0 - */ diff --git a/src/core/core.scale.js b/src/core/core.scale.js index e80a7f197..2b4e4f235 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -649,18 +649,24 @@ export default class Scale extends Element { call(this.options.afterSetDimensions, [this]); } + _callHooks(name) { + const me = this; + me.chart.notifyPlugins(name, me.getContext()); + call(me.options[name], [me]); + } + // Data limits beforeDataLimits() { - call(this.options.beforeDataLimits, [this]); + this._callHooks('beforeDataLimits'); } determineDataLimits() {} afterDataLimits() { - call(this.options.afterDataLimits, [this]); + this._callHooks('afterDataLimits'); } // beforeBuildTicks() { - call(this.options.beforeBuildTicks, [this]); + this._callHooks('beforeBuildTicks'); } /** * @return {object[]} the ticks @@ -669,7 +675,7 @@ export default class Scale extends Element { return []; } afterBuildTicks() { - call(this.options.afterBuildTicks, [this]); + this._callHooks('afterBuildTicks'); } beforeTickToLabelConversion() { diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index 23d47a2b2..ddb83d9b5 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -1396,6 +1396,14 @@ describe('Chart', function() { update: [ 'beforeUpdate', 'beforeLayout', + 'beforeDataLimits', + 'afterDataLimits', + 'beforeBuildTicks', + 'afterBuildTicks', + 'beforeDataLimits', + 'afterDataLimits', + 'beforeBuildTicks', + 'afterBuildTicks', 'afterLayout', 'beforeDatasetsUpdate', 'beforeDatasetUpdate', diff --git a/test/specs/core.plugin.tests.js b/test/specs/core.plugin.tests.js index 988d061e6..c709846d1 100644 --- a/test/specs/core.plugin.tests.js +++ b/test/specs/core.plugin.tests.js @@ -151,7 +151,7 @@ describe('Chart.plugins', function() { spyOn(plugin, 'hook').and.callThrough(); }); - var ret = chart.notifyPlugins('hook'); + var ret = chart.notifyPlugins('hook', {cancelable: true}); expect(ret).toBeFalsy(); expect(plugins[0].hook).toHaveBeenCalled(); expect(plugins[1].hook).toHaveBeenCalled(); diff --git a/types/core/index.d.ts b/types/core/index.d.ts index 1c9641a26..9fe00f4c8 100644 --- a/types/core/index.d.ts +++ b/types/core/index.d.ts @@ -597,14 +597,14 @@ export interface Plugin { * @param {object} args - The call arguments. * @param {object} options - The plugin options. */ - beforeInit?(chart: Chart, args: {}, options: O): boolean | void; + beforeInit?(chart: Chart, args: {}, options: O): void; /** * @desc Called after `chart` has been initialized and before the first update. * @param {Chart} chart - The chart instance. * @param {object} args - The call arguments. * @param {object} options - The plugin options. */ - afterInit?(chart: Chart, args: {}, options: O): boolean | void; + afterInit?(chart: Chart, args: {}, options: O): void; /** * @desc Called before updating `chart`. If any plugin returns `false`, the update * is cancelled (and thus subsequent render(s)) until another `update` is triggered. @@ -623,7 +623,7 @@ export interface Plugin { * @param {UpdateMode} args.mode - The update mode * @param {object} options - The plugin options. */ - afterUpdate?(chart: Chart, args: { mode: UpdateMode }, options: O): boolean | void; + afterUpdate?(chart: Chart, args: { mode: UpdateMode }, options: O): void; /** * @desc Called during chart reset * @param {Chart} chart - The chart instance. @@ -652,7 +652,7 @@ export interface Plugin { * @param {object} options - The plugin options. * @since version 2.1.5 */ - afterDatasetsUpdate?(chart: Chart, args: { mode: UpdateMode }, options: O): boolean | void; + afterDatasetsUpdate?(chart: Chart, args: { mode: UpdateMode }, options: O): void; /** * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin * returns `false`, the datasets update is cancelled until another `update` is triggered. @@ -675,7 +675,7 @@ export interface Plugin { * @param {UpdateMode} args.mode - The update mode. * @param {object} options - The plugin options. */ - afterDatasetUpdate?(chart: Chart, args: { index: number; meta: ChartMeta, mode: UpdateMode }, options: O): boolean | void; + afterDatasetUpdate?(chart: Chart, args: { index: number; meta: ChartMeta, mode: UpdateMode }, options: O): void; /** * @desc Called before laying out `chart`. If any plugin returns `false`, * the layout update is cancelled until another `update` is triggered. @@ -685,6 +685,38 @@ export interface Plugin { * @returns {boolean} `false` to cancel the chart layout. */ beforeLayout?(chart: Chart, args: {}, options: O): boolean | void; + /** + * @desc Called before scale data limits are calculated. This hook is called separately for each scale in the chart. + * @param {Chart} chart - The chart instance. + * @param {object} args - The call arguments. + * @param {Scale} args.scale - The scale. + * @param {object} options - The plugin options. + */ + beforeDataLimits?(chart: Chart, args: { scale: Scale }, options: O): void; + /** + * @desc Called after scale data limits are calculated. This hook is called separately for each scale in the chart. + * @param {Chart} chart - The chart instance. + * @param {object} args - The call arguments. + * @param {Scale} args.scale - The scale. + * @param {object} options - The plugin options. + */ + afterDataLimits?(chart: Chart, args: { scale: Scale }, options: O): void; + /** + * @desc Called before scale bulds its ticks. This hook is called separately for each scale in the chart. + * @param {Chart} chart - The chart instance. + * @param {object} args - The call arguments. + * @param {Scale} args.scale - The scale. + * @param {object} options - The plugin options. + */ + beforeBuildTicks?(chart: Chart, args: { scale: Scale }, options: O): void; + /** + * @desc Called after scale has build its ticks. This hook is called separately for each scale in the chart. + * @param {Chart} chart - The chart instance. + * @param {object} args - The call arguments. + * @param {Scale} args.scale - The scale. + * @param {object} options - The plugin options. + */ + afterBuildTicks?(chart: Chart, args: { scale: Scale }, options: O): void; /** * @desc Called after the `chart` has been laid out. Note that this hook will not * be called if the layout update has been previously cancelled. @@ -692,7 +724,7 @@ export interface Plugin { * @param {object} args - The call arguments. * @param {object} options - The plugin options. */ - afterLayout?(chart: Chart, args: {}, options: O): boolean | void; + afterLayout?(chart: Chart, args: {}, options: O): void; /** * @desc Called before rendering `chart`. If any plugin returns `false`, * the rendering is cancelled until another `render` is triggered. @@ -709,7 +741,7 @@ export interface Plugin { * @param {object} args - The call arguments. * @param {object} options - The plugin options. */ - afterRender?(chart: Chart, args: {}, options: O): boolean | void; + afterRender?(chart: Chart, args: {}, options: O): void; /** * @desc Called before drawing `chart` at every animation frame. If any plugin returns `false`, * the frame drawing is cancelled untilanother `render` is triggered. @@ -726,7 +758,7 @@ export interface Plugin { * @param {object} args - The call arguments. * @param {object} options - The plugin options. */ - afterDraw?(chart: Chart, args: {}, options: O): boolean | void; + afterDraw?(chart: Chart, args: {}, options: O): void; /** * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, * the datasets drawing is cancelled until another `render` is triggered. @@ -743,7 +775,7 @@ export interface Plugin { * @param {object} args - The call arguments. * @param {object} options - The plugin options. */ - afterDatasetsDraw?(chart: Chart, args: {}, options: O): boolean | void; + afterDatasetsDraw?(chart: Chart, args: {}, options: O): void; /** * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing @@ -766,7 +798,7 @@ export interface Plugin { * @param {object} args.meta - The dataset metadata. * @param {object} options - The plugin options. */ - afterDatasetDraw?(chart: Chart, args: { index: number; meta: ChartMeta }, options: O): boolean | void; + afterDatasetDraw?(chart: Chart, args: { index: number; meta: ChartMeta }, options: O): void; /** * @desc Called before processing the specified `event`. If any plugin returns `false`, * the event will be discarded. @@ -786,7 +818,7 @@ export interface Plugin { * @param {boolean} replay - True if this event is replayed from `Chart.update` * @param {object} options - The plugin options. */ - afterEvent?(chart: Chart, args: { event: ChartEvent, replay: boolean }, options: O): boolean | void; + afterEvent?(chart: Chart, args: { event: ChartEvent, replay: boolean }, options: O): void; /** * @desc Called after the chart as been resized. * @param {Chart} chart - The chart instance. @@ -794,14 +826,14 @@ export interface Plugin { * @param {number} args.size - The new canvas display size (eq. canvas.style width & height). * @param {object} options - The plugin options. */ - resize?(chart: Chart, args: { size: { width: number, height: number } }, options: O): boolean | void; + resize?(chart: Chart, args: { size: { width: number, height: number } }, options: O): void; /** * Called after the chart has been destroyed. * @param {Chart} chart - The chart instance. * @param {object} args - The call arguments. * @param {object} options - The plugin options. */ - destroy?(chart: Chart, args: {}, options: O): boolean | void; + destroy?(chart: Chart, args: {}, options: O): void; /** * Called after chart is destroyed on all plugins that were installed for that chart. This hook is also invoked for disabled plugins (options === false). * @param {Chart} chart - The chart instance. -- 2.47.2