Repository: bokeh/bokeh

Issue:
Multi-line TextInput box?
In general I've found it very difficult to find information on Bokeh widgets in the documentation, so maybe this is already possible, but is there a way to specify a paragraph-sized TextInput box?  I'm using one for the user to be able to edit some code in a domain-specific language, which is awkward to do in a single-line box.  

If multiple lines are not possible, is there at least a way to change the size of the box?



Discussion:
Right now, the TextInput box accepts multi-line strings, but the newlines just show up as spaces, with everything still on one line.

It looks like the TextInput box maps to an HTML input object, and I'd need an HTML textarea object in this case.  

One complication is that the input object triggers a re-render based on pressing enter, but textarea would need to use enter internally.  But there may be no problem with this, since both of them appear to trigger an update when the widget loses focus.

Another complication is that a textarea would need some sizing parameters to determine how large to make it.  That might be a good reason to make a new type of Bokeh widget for multi-line input, rather than just adding a multiline=True option to TextInput.

Actually, needing to press return _is_ an issue.  I tested it out by hacking the textinput template to use textarea:

```
diff --git a/bokehjs/src/coffee/widget/text_input_template.eco b/bokehjs/src/coffee/widget/text_input_template.eco
index 48d790c..8d75812 100644
--- a/bokehjs/src/coffee/widget/text_input_template.eco
+++ b/bokehjs/src/coffee/widget/text_input_template.eco
@@ -1,2 +1,2 @@
 <label for="<%= @id %>"> <%= @title %> </label>
-<input class="bk-widget-form-input" type="text" id="<%=@id %>" name="<%= @name %>" value="<%= @value %>"/>
+<textarea class="bk-widget-form-input" rows="10" cols="50" id="<%=@id %>" name="<%= @name %>"> <%= @value %> </textarea>
```

This works in terms of displaying the multi-line text and editing it, but I've defined a CustomJS callback for the textinput widget, and it apparently never gets called now, presumably because enter is now taken literally as a carriage return.

This has to be implemented as a separate widget, so that we don't mix concerns. I may actually do it as part of custom models work, because this touches some interesting issues.

Interesting sounds scary! :-)  I'd wished for easy and boring. 

a shame mas-dse-pjmulroo's PR did not work. Is there any plans to make a widget like this in the future?
What would be a simple way of adding a multiline text input to a bokeh app?

(If it's of relevance - it's supposed to accept an SQL query, and display results in a DataTable widget)


Thanks,
Eli

Resolving patch:
diff --git a/bokeh/models/widgets/inputs.py b/bokeh/models/widgets/inputs.py
--- a/bokeh/models/widgets/inputs.py
+++ b/bokeh/models/widgets/inputs.py
@@ -45,6 +45,7 @@
     'PasswordInput',
     'Select',
     'TextInput',
+    'TextAreaInput'
 )
 
 #-----------------------------------------------------------------------------
@@ -97,6 +98,24 @@ class TextInput(InputWidget):
     """)
 
 
+class TextAreaInput(TextInput):
+    ''' Multi-line input widget.
+
+    '''
+
+    cols = Int(default=20, help="""
+    Specifies the width of the text area (in average character width). Default: 20
+    """)
+
+    rows = Int(default=2, help="""
+    Specifies the height of the text area (in lines). Default: 2
+    """)
+
+    max_length = Int(default=500, help="""
+    Max count of characters in field
+    """)
+
+
 class PasswordInput(TextInput):
     ''' Single-line password input widget.
         Note: Despite ``PasswordInput`` inheriting from ``TextInput`` the
diff --git a/sphinx/source/docs/user_guide/examples/interaction_textarea.py b/sphinx/source/docs/user_guide/examples/interaction_textarea.py
new file mode 100644
--- /dev/null
+++ b/sphinx/source/docs/user_guide/examples/interaction_textarea.py
@@ -0,0 +1,9 @@
+from bokeh.io import output_file, show
+from bokeh.layouts import widgetbox
+from bokeh.models.widgets import TextAreaInput
+
+output_file("text_area_input.html")
+
+text_area_input = TextAreaInput(value="default", title="Label:", rows=5, cols=50, max_length=200)
+
+show(widgetbox(text_area_input))


Test patch:
diff --git a/tests/integration/widgets/test_textarea_input.py b/tests/integration/widgets/test_textarea_input.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/widgets/test_textarea_input.py
@@ -0,0 +1,84 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2012 - 2017, Anaconda, Inc. All rights reserved.
+#
+# Powered by the Bokeh Development Team.
+#
+# The full license is in the file LICENSE.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Boilerplate
+#-----------------------------------------------------------------------------
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import pytest ; pytest
+from selenium.webdriver.common.keys import Keys
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+# Standard library imports
+
+# External imports
+
+# Bokeh imports
+from bokeh.layouts import column
+from bokeh.models import Circle, ColumnDataSource, CustomAction, CustomJS, Plot, Range1d, TextAreaInput
+from bokeh._testing.util.selenium import enter_text_in_element, RECORD
+
+#-----------------------------------------------------------------------------
+# Tests
+#-----------------------------------------------------------------------------
+
+pytest_plugins = (
+    "bokeh._testing.plugins.bokeh",
+)
+
+
+foo = []
+
+
+def modify_doc(doc):
+    source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"]))
+    plot = Plot(plot_height=400, plot_width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0)
+    plot.add_glyph(source, Circle(x='x', y='y', size=20))
+    code = RECORD("data", "s.data")
+    plot.add_tools(CustomAction(callback=CustomJS(args=dict(s=source), code=code)))
+    text_input = TextAreaInput(cols=20, css_classes=["foo"])
+    def cb(attr, old, new):
+        foo.append((old, new))
+        source.data['val'] = [old, new]
+    text_input.on_change('value', cb)
+    doc.add_root(column(text_input, plot))
+
+
+@pytest.mark.integration
+@pytest.mark.selenium
+class Test_TextInput(object):
+
+    def test_displays_text_input(self, bokeh_model_page):
+        text_input = TextAreaInput(css_classes=["foo"])
+        page = bokeh_model_page(text_input)
+        input_div = page.driver.find_element_by_class_name('foo')
+        el = input_div.find_element_by_tag_name("textarea")
+        assert el.tag_name == 'textarea'
+        assert page.has_no_console_errors()
+
+    def test_displays_placeholder(self, bokeh_model_page):
+        text_input = TextAreaInput(placeholder="placeholder", css_classes=["foo"])
+        page = bokeh_model_page(text_input)
+        input_div = page.driver.find_element_by_class_name('foo')
+        el = input_div.find_element_by_tag_name("textarea")
+        assert el.get_attribute('placeholder') == "placeholder"
+        assert page.has_no_console_errors()
+
+    def test_server_on_change_round_trip(self, bokeh_server_page):
+        page = bokeh_server_page(modify_doc)
+
+        el = page.driver.find_element_by_class_name('foo')
+
+        enter_text_in_element(page.driver, el, "val1" + Keys.TAB)
+
+        page.click_custom_action()
+        results = page.results
+        assert results['data']['val'] == ["", "val1"]