diff --git a/src/components/sliders/attributes.js b/src/components/sliders/attributes.js index 36df48a86c4..f175aa50f57 100644 --- a/src/components/sliders/attributes.js +++ b/src/components/sliders/attributes.js @@ -53,6 +53,18 @@ var stepsAttrs = { 'Sets the value of the slider step, used to refer to the step programatically.', 'Defaults to the slider label if not provided.' ].join(' ') + }, + execute: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'When true, the API method is executed. When false, all other behaviors are the same', + 'and command execution is skipped. This may be useful when hooking into, for example,', + 'the `plotly_sliderchange` method and executing the API command manually without losing', + 'the benefit of the slider automatically binding to the state of the plot through the', + 'specification of `method` and `args`.' + ].join(' ') } }; diff --git a/src/components/sliders/defaults.js b/src/components/sliders/defaults.js index 0647b533245..0081d31edd6 100644 --- a/src/components/sliders/defaults.js +++ b/src/components/sliders/defaults.js @@ -104,6 +104,7 @@ function stepsDefaults(sliderIn, sliderOut) { coerce('args'); coerce('label', 'step-' + i); coerce('value', valueOut.label); + coerce('execute'); valuesOut.push(valueOut); } diff --git a/src/components/sliders/draw.js b/src/components/sliders/draw.js index 3965237d2ce..46f317e5142 100644 --- a/src/components/sliders/draw.js +++ b/src/components/sliders/draw.js @@ -409,7 +409,9 @@ function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTransition) var _step = sliderGroup._nextMethod.step; if(!_step.method) return; - Plots.executeAPICommand(gd, _step.method, _step.args); + if(_step.execute) { + Plots.executeAPICommand(gd, _step.method, _step.args); + } sliderGroup._nextMethod = null; sliderGroup._nextMethodRaf = null; diff --git a/src/components/updatemenus/attributes.js b/src/components/updatemenus/attributes.js index 479a2d37f6d..2da8f3a85e6 100644 --- a/src/components/updatemenus/attributes.js +++ b/src/components/updatemenus/attributes.js @@ -44,6 +44,18 @@ var buttonsAttrs = { role: 'info', dflt: '', description: 'Sets the text label to appear on the button.' + }, + execute: { + valType: 'boolean', + role: 'info', + dflt: true, + description: [ + 'When true, the API method is executed. When false, all other behaviors are the same', + 'and command execution is skipped. This may be useful when hooking into, for example,', + 'the `plotly_buttonclicked` method and executing the API command manually without losing', + 'the benefit of the updatemenu automatically binding to the state of the plot through the', + 'specification of `method` and `args`.' + ].join(' ') } }; diff --git a/src/components/updatemenus/defaults.js b/src/components/updatemenus/defaults.js index 310fd77882f..5606d045f3c 100644 --- a/src/components/updatemenus/defaults.js +++ b/src/components/updatemenus/defaults.js @@ -84,6 +84,7 @@ function buttonsDefaults(menuIn, menuOut) { coerce('args'); coerce('label'); + coerce('execute'); buttonOut._index = i; buttonsOut.push(buttonOut); diff --git a/src/components/updatemenus/draw.js b/src/components/updatemenus/draw.js index 558c4148a2d..adf32f1f486 100644 --- a/src/components/updatemenus/draw.js +++ b/src/components/updatemenus/draw.js @@ -331,7 +331,9 @@ function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) { setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex); - Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args); + if(buttonOpts.execute) { + Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args); + } gd.emit('plotly_buttonclicked', {menu: menuOpts, button: buttonOpts, active: menuOpts.active}); }); diff --git a/test/image/mocks/sliders.json b/test/image/mocks/sliders.json index 4fb9035f17c..c250b96208d 100644 --- a/test/image/mocks/sliders.json +++ b/test/image/mocks/sliders.json @@ -115,7 +115,8 @@ }, { "label": "purple", "method": "restyle", - "args": [{"marker.color": "purple"}] + "args": [{"marker.color": "purple"}], + "execute": false }], "visible": true, "x": 0.5, diff --git a/test/jasmine/tests/sliders_test.js b/test/jasmine/tests/sliders_test.js index 398c18d8314..a1430205235 100644 --- a/test/jasmine/tests/sliders_test.js +++ b/test/jasmine/tests/sliders_test.js @@ -99,15 +99,18 @@ describe('sliders defaults', function() { expect(layoutOut.sliders[0].steps).toEqual([{ method: 'relayout', label: 'Label #1', - value: 'label-1' + value: 'label-1', + execute: true }, { method: 'update', label: 'Label #2', - value: 'Label #2' + value: 'Label #2', + execute: true }, { method: 'animate', label: 'step-2', - value: 'lacks-label' + value: 'lacks-label', + execute: true }]); }); @@ -131,6 +134,7 @@ describe('sliders defaults', function() { args: ['title', 'Hello World'], label: 'step-1', value: 'step-1', + execute: true }); }); @@ -155,6 +159,7 @@ describe('sliders defaults', function() { args: ['title', 'Hello World'], label: 'step-1', value: 'step-1', + execute: true }); }); @@ -175,11 +180,13 @@ describe('sliders defaults', function() { method: 'skip', label: 'step-0', value: 'step-0', + execute: true, }, { method: 'skip', args: ['title', 'Hello World'], label: 'step-1', value: 'step-1', + execute: true, }); }); diff --git a/test/jasmine/tests/updatemenus_test.js b/test/jasmine/tests/updatemenus_test.js index 09b411f1bab..74ca4743ee3 100644 --- a/test/jasmine/tests/updatemenus_test.js +++ b/test/jasmine/tests/updatemenus_test.js @@ -111,6 +111,7 @@ describe('update menus defaults', function() { expect(layoutOut.updatemenus[0].buttons[0]).toEqual({ method: 'relayout', args: ['title', 'Hello World'], + execute: true, label: '', _index: 1 }); @@ -135,6 +136,7 @@ describe('update menus defaults', function() { expect(layoutOut.updatemenus[0].buttons[0]).toEqual({ method: 'relayout', args: ['title', 'Hello World'], + execute: true, label: '', _index: 1 }); @@ -156,11 +158,13 @@ describe('update menus defaults', function() { expect(layoutOut.updatemenus[0].buttons[0]).toEqual({ method: 'skip', label: '', + execute: true, _index: 0 }, { method: 'skip', args: ['title', 'Hello World'], label: '', + execute: true, _index: 1 }); }); @@ -444,6 +448,32 @@ describe('update menus interactions', function() { }); }); + it('should execute the API command when execute = true', function(done) { + expect(gd.data[0].line.color).toEqual('blue'); + + click(selectHeader(0)).then(function() { + return click(selectButton(2)); + }).then(function() { + // Has been changed: + expect(gd.data[0].line.color).toEqual('green'); + }).catch(fail).then(done); + }); + + it('should not execute the API command when execute = false', function(done) { + // This test is identical to the one above, except that it disables + // the command by setting execute = false first: + expect(gd.data[0].line.color).toEqual('blue'); + + Plotly.relayout(gd, 'updatemenus[0].buttons[2].execute', false).then(function() { + return click(selectHeader(0)); + }).then(function() { + return click(selectButton(2)); + }).then(function() { + // Is unchanged: + expect(gd.data[0].line.color).toEqual('blue'); + }).catch(fail).then(done); + }); + it('should emit an event on button click', function(done) { var clickCnt = 0; var data = [];