Repository: bokeh/bokeh

Issue:
disabled button raises button click protocol event
In `models/server/buttons.py` clicking the disabled button prints "clicked" at the console.


Discussion:

Resolving patch:
diff --git a/bokeh/model.py b/bokeh/model.py
--- a/bokeh/model.py
+++ b/bokeh/model.py
@@ -279,7 +279,7 @@ def __str__(self):
     help="""A mapping of event names to lists of CustomJS callbacks.
 
     Typically, rather then modifying this property directly, callbacks should be
-    added using the ``Model.js_on_event`` method:)
+    added using the ``Model.js_on_event`` method:
 
     .. code:: python
 
@@ -291,7 +291,7 @@ def __str__(self):
     List of events that are subscribed to by Python callbacks. This is
     the set of events that will be communicated from BokehJS back to
     Python for this model.
-     """   )
+    """)
 
     js_property_callbacks = Dict(String, List(Instance("bokeh.models.callbacks.CustomJS")), help="""
     A mapping of attribute names to lists of CustomJS callbacks, to be set up on
diff --git a/examples/models/server/buttons.py b/examples/models/server/buttons.py
--- a/examples/models/server/buttons.py
+++ b/examples/models/server/buttons.py
@@ -32,9 +32,12 @@ def checkbox_button_group_handler(active):
 def radio_button_group_handler(active):
     print("radio_button_group_handler: %s" % active)
 
-button = Button(label="Button (disabled) - still has click event", button_type="primary", disabled=True)
+button = Button(label="Button (enabled) - has click event", button_type="primary")
 button.on_click(button_handler)
 
+button_disabled = Button(label="Button (disabled) - no click event", button_type="primary", disabled=True)
+button_disabled.on_click(button_handler)
+
 toggle = Toggle(label="Toggle button", button_type="success")
 toggle.on_click(toggle_handler)
 
@@ -58,7 +61,7 @@ def radio_button_group_handler(active):
 radio_button_group = RadioButtonGroup(labels=["Option 1", "Option 2", "Option 3"], active=0)
 radio_button_group.on_click(radio_button_group_handler)
 
-widgetBox = WidgetBox(children=[button, toggle, dropdown, split, checkbox_group, radio_group, checkbox_button_group, radio_button_group])
+widgetBox = WidgetBox(children=[button, button_disabled, toggle, dropdown, split, checkbox_group, radio_group, checkbox_button_group, radio_button_group])
 
 document = Document()
 document.add_root(widgetBox)


Test patch:
diff --git a/bokehjs/test/models/widgets/button.coffee b/bokehjs/test/models/widgets/button.coffee
new file mode 100644
--- /dev/null
+++ b/bokehjs/test/models/widgets/button.coffee
@@ -0,0 +1,37 @@
+{expect} = require "chai"
+utils = require "../../utils"
+sinon = require "sinon"
+
+{Button} = utils.require("models/widgets/button")
+{Document} = utils.require("document")
+
+describe "Button.click", ->
+
+  click = (view) ->
+    view.el.querySelector('button').click()
+
+  it "should trigger on click, if enabled", ->
+    b = new Button()
+    b.attach_document(new Document())
+    bv = new b.default_view({model: b, parent: null})
+
+    spy = sinon.spy(b, 'trigger_event')
+
+    expect(spy.called).to.be.false
+    expect(b.clicks).to.be.equal 0
+    click(bv)
+    expect(b.clicks).to.be.equal 1
+    expect(spy.callCount).to.be.equal 1
+
+  it "should not trigger on click, if disabled", ->
+    b = new Button({disabled: true})
+    b.attach_document(new Document())
+    bv = new b.default_view({model: b, parent: null})
+
+    spy = sinon.spy(b, 'trigger_event')
+
+    expect(spy.called).to.be.false
+    expect(b.clicks).to.be.equal 0
+    click(bv)
+    expect(b.clicks).to.be.equal 0
+    expect(spy.called).to.be.false
diff --git a/bokehjs/test/models/widgets/index.coffee b/bokehjs/test/models/widgets/index.coffee
--- a/bokehjs/test/models/widgets/index.coffee
+++ b/bokehjs/test/models/widgets/index.coffee
@@ -1,3 +1,4 @@
+require "./button"
 require "./data_table"
 require "./paragraph"
 require "./tabs"