Repository: bokeh/bokeh

Issue:
RangeSlider css_classes not being updated anymore
It seems in 0.12.7 the `css_classes` attribute of the RangeSlider is not being updated anymore by the callbacks. It was working in 0.12.6.
In the example below, both the text input and the slider starts with the `initial` class as expected. When the slider changes, both should gain the `sent-later` class, but only the text input is gaining it...

```
from bokeh.io import curdoc
from bokeh.models import TextInput, RangeSlider
from bokeh.layouts import widgetbox
from bokeh.layouts import column
from bokeh.models import Div

div1 = Div(text="""
        <style>
        .sent-later {
            border-radius: 0.5em;
            padding: 1em;
            border: 3px solid #2397D8;
        }
        </style>
""")


def on_slider_change(attr, old, new):
    slider.css_classes.append('sent-later')
    text.css_classes.append('sent-later')
    print(slider.css_classes)
    print(text.css_classes)


text = TextInput(
        value=str(2),
        title='Population Beta:')

slider = RangeSlider(
        start=0,
        end=10,
        value=(0, 2),
        step=1,
        title='Population Beta')

slider.css_classes = []
slider.css_classes.append('initial')
text.css_classes = []
text.css_classes.append('initial')

slider.on_change('value', on_slider_change)

curdoc().add_root(column(div1, widgetbox(text, slider)))

```



Discussion:
@andresmrm The entire slider widget implementation was replace so that we could jettison jquery entirely. Thanks for your patience. 

@mattpap I suspect it's this:

https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/models/widgets/abstract_slider.coffee#L24-L26

Looks like `render` (whose `super` is what calls `_render_classes`) will only ever be called once. 
@mattpap actually after some more investigation, the updated value for `css_classes` is never different as far as I can tell. It's either not getting sent at all, or getting ignored/dropped on the floor for some reason on the BokehJS side

Yah, if these are added to the example code:
```

slider.js_on_change('css_classes', CustomJS(code='console.log("SLIDER")'))
text.js_on_change('css_classes', CustomJS(code='console.log("TEXT")'))
```
no messages are ever shown. 
FYI I was trying this change to BokehJS:
```
❯ git diff
diff --git a/bokehjs/src/coffee/models/widgets/abstract_slider.coffee b/bokehjs/src/coffee/models/widgets/abstract_slider.coffee
index 3f5f3e550..9cca899d1 100644
--- a/bokehjs/src/coffee/models/widgets/abstract_slider.coffee
+++ b/bokehjs/src/coffee/models/widgets/abstract_slider.coffee
@@ -25,6 +25,9 @@ export class AbstractSliderView extends WidgetView
       # XXX: temporary workaround for _render_css()
       super()

+    @_render_classes()
+
     if @model.callback?
       callback = () => @model.callback.execute(@model)
```
If I also make this change to the example code:
```
    slider.css_classes = ['sent-later'] #.append('sent-later')
    text.css_classes = ['sent-later'] #append('sent-later')
```
Then the CSS updates as expected. So one thing seems to be an issue with change detection on `.append`
Without the code change above the text css updates but the slider does not. @mattpap do you consider the extra call to `@_render_classes()` to be acceptable (at least for now)? If so I will make a PR.

I'll have to look into the `.append` thing 
OH, I am almost certain it does not work because it is `Seq` now, instead of `List`. Was it `List` previously? perhaps we can make it `List` with an `.accepts` to convert tuples?
cc @mattpap for thoughts
The route suggested above works,I will create a PR

Resolving patch:
diff --git a/bokeh/models/layouts.py b/bokeh/models/layouts.py
--- a/bokeh/models/layouts.py
+++ b/bokeh/models/layouts.py
@@ -58,10 +58,14 @@ class LayoutDOM(Model):
 
     """)
 
-    css_classes = Seq(String, help="""
+    # List in order for in-place changes to trigger changes, ref: https://github.com/bokeh/bokeh/issues/6841
+    css_classes = List(String, help="""
     A list of css class names to add to this DOM element. Note: the class names are
     simply added as-is, no other guarantees are provided.
-    """)
+
+    It is also permissible to assign from tuples, however these are adapted -- the
+    property will always contain a list.
+    """).accepts(Seq(String), lambda x: list(x))
 
 
 class Spacer(LayoutDOM):


Test patch:
diff --git a/bokeh/models/tests/test_layouts.py b/bokeh/models/tests/test_layouts.py
--- a/bokeh/models/tests/test_layouts.py
+++ b/bokeh/models/tests/test_layouts.py
@@ -1,6 +1,6 @@
 import pytest
 from bokeh.plotting import Figure
-from bokeh.models.layouts import Row, Column, WidgetBox
+from bokeh.models.layouts import Row, Column, WidgetBox, LayoutDOM
 from bokeh.models.widgets import Slider
 from bokeh.models.sources import ColumnDataSource
 
@@ -76,3 +76,11 @@ def check_widget_box_children_prop(layout_callable):
 def test_WidgetBox():
     check_props(WidgetBox())
     check_widget_box_children_prop(WidgetBox)
+
+def test_LayoutDOM_css_classes():
+    m = LayoutDOM()
+    assert m.css_classes == []
+    m.css_classes = ['foo']
+    assert m.css_classes == ['foo']
+    m.css_classes = ('bar', )
+    assert m.css_classes == ['bar']