##// END OF EJS Templates
More fixes
Jonathan Frederic -
Show More
@@ -1,121 +1,126 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // BoolWidget
9 // BoolWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgets/widget"], function(widget_manager){
17 define(["notebook/js/widgets/widget"], function(widget_manager){
18 var CheckBoxView = IPython.DOMWidgetView.extend({
18 var CheckBoxView = IPython.DOMWidgetView.extend({
19
19
20 // Called when view is rendered.
20 // Called when view is rendered.
21 render : function(){
21 render : function(){
22 this.$el
22 this.$el
23 .addClass('widget-hbox-single');
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
24 this.$label = $('<div />')
25 .addClass('widget-hlabel')
25 .addClass('widget-hlabel')
26 .appendTo(this.$el)
26 .appendTo(this.$el)
27 .hide();
27 .hide();
28 var that = this;
29 this.$checkbox = $('<input />')
28 this.$checkbox = $('<input />')
30 .attr('type', 'checkbox')
29 .attr('type', 'checkbox')
31 .click(function(el) {
30 .appendTo(this.$el)
32
31 .click($.proxy(this.handle_click, this));
33 // Calling model.set will trigger all of the other views of the
34 // model to update.
35 that.model.set('value', that.$checkbox.prop('checked'), {updated_view: this});
36 that.touch();
37 })
38 .appendTo(this.$el);
39
32
40 this.$el_to_style = this.$checkbox; // Set default element to style
33 this.$el_to_style = this.$checkbox; // Set default element to style
41 this.update(); // Set defaults.
34 this.update(); // Set defaults.
42 },
35 },
36
37 handle_click: function() {
38 // Calling model.set will trigger all of the other views of the
39 // model to update.
40 var value = this.model.get('value');
41 this.model.set('value', ! value, {updated_view: this});
42 this.touch();
43 },
43
44
44 update : function(options){
45 update : function(options){
45 // Update the contents of this view
46 // Update the contents of this view
46 //
47 //
47 // Called when the model is changed. The model may have been
48 // Called when the model is changed. The model may have been
48 // changed by another view or by a state update from the back-end.
49 // changed by another view or by a state update from the back-end.
49 if (options === undefined || options.updated_view != this) {
50 this.$checkbox.prop('checked', this.model.get('value'));
50 this.$checkbox.prop('checked', this.model.get('value'));
51
51
52 if (options === undefined || options.updated_view != this) {
52 var disabled = this.model.get('disabled');
53 var disabled = this.model.get('disabled');
53 this.$checkbox.prop('disabled', disabled);
54 this.$checkbox.prop('disabled', disabled);
54
55
55 var description = this.model.get('description');
56 var description = this.model.get('description');
56 if (description.length === 0) {
57 if (description.length === 0) {
57 this.$label.hide();
58 this.$label.hide();
58 } else {
59 } else {
59 this.$label.html(description);
60 this.$label.html(description);
60 this.$label.show();
61 this.$label.show();
61 }
62 }
62 }
63 }
63 return CheckBoxView.__super__.update.apply(this);
64 return CheckBoxView.__super__.update.apply(this);
64 },
65 },
65
66
66 });
67 });
67
68
68 widget_manager.register_widget_view('CheckBoxView', CheckBoxView);
69 widget_manager.register_widget_view('CheckBoxView', CheckBoxView);
69
70
70 var ToggleButtonView = IPython.DOMWidgetView.extend({
71 var ToggleButtonView = IPython.DOMWidgetView.extend({
71
72
72 // Called when view is rendered.
73 // Called when view is rendered.
73 render : function(){
74 render : function() {
75 var that = this;
74 this.setElement($('<button />')
76 this.setElement($('<button />')
75 .addClass('btn')
77 .addClass('btn')
76 .attr('type', 'button')
78 .attr('type', 'button')
77 .attr('data-toggle', 'button'));
79 .on('click', function (e) {
80 e.preventDefault();
81 that.handle_click();
82 }));
78
83
79 this.update(); // Set defaults.
84 this.update(); // Set defaults.
80 },
85 },
81
86
82 update : function(options){
87 update : function(options){
83 // Update the contents of this view
88 // Update the contents of this view
84 //
89 //
85 // Called when the model is changed. The model may have been
90 // Called when the model is changed. The model may have been
86 // changed by another view or by a state update from the back-end.
91 // changed by another view or by a state update from the back-end.
87 if (this.model.get('value')) {
92 if (this.model.get('value')) {
88 this.$el.addClass('active');
93 this.$el.addClass('active');
89 } else {
94 } else {
90 this.$el.removeClass('active');
95 this.$el.removeClass('active');
91 }
96 }
92
97
93 if (options === undefined || options.updated_view != this) {
98 if (options === undefined || options.updated_view != this) {
99
94 var disabled = this.model.get('disabled');
100 var disabled = this.model.get('disabled');
95 this.$el.prop('disabled', disabled);
101 this.$el.prop('disabled', disabled);
96
102
97 var description = this.model.get('description');
103 var description = this.model.get('description');
98 if (description.length === 0) {
104 if (description.length === 0) {
99 this.$el.html(' '); // Preserve button height
105 this.$el.html(' '); // Preserve button height
100 } else {
106 } else {
101 this.$el.html(description);
107 this.$el.html(description);
102 }
108 }
103 }
109 }
104 return ToggleButtonView.__super__.update.apply(this);
110 return ToggleButtonView.__super__.update.apply(this);
105 },
111 },
106
112
107 events: {"click" : "handleClick"},
108
109 // Handles and validates user input.
113 // Handles and validates user input.
110 handleClick: function(e) {
114 handle_click: function(e) {
111
115
112 // Calling model.set will trigger all of the other views of the
116 // Calling model.set will trigger all of the other views of the
113 // model to update.
117 // model to update.
114 this.model.set('value', ! $(this.$el).hasClass('active'), {updated_view: this});
118 var value = this.model.get('value');
119 this.model.set('value', ! value, {updated_view: this});
115 this.touch();
120 this.touch();
116 },
121 },
117 });
122 });
118
123
119 widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView);
124 widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView);
120
125
121 });
126 });
@@ -1,126 +1,76 b''
1 // Test the widget framework.
1 // Test the widget framework.
2 casper.notebook_test(function () {
2 casper.notebook_test(function () {
3 var index;
3 var index;
4
4
5 this.then(function () {
5 this.then(function () {
6
6
7 // Check if the WidgetManager class is defined.
7 // Check if the WidgetManager class is defined.
8 this.test.assert(this.evaluate(function() {
8 this.test.assert(this.evaluate(function() {
9 return IPython.WidgetManager != undefined;
9 return IPython.WidgetManager != undefined;
10 }), 'WidgetManager class is defined');
10 }), 'WidgetManager class is defined');
11 });
11 });
12
12
13 index = this.append_cell(
13 index = this.append_cell(
14 'from IPython.html import widgets\n' +
14 'from IPython.html import widgets\n' +
15 'from IPython.display import display, clear_output\n' +
15 'from IPython.display import display, clear_output\n' +
16 'print("Success")');
16 'print("Success")');
17 this.execute_cell_then(index);
17 this.execute_cell_then(index);
18
18
19 this.wait(500); // Wait for require.js async callbacks to load dependencies.
19 this.wait(500); // Wait for require.js async callbacks to load dependencies.
20
20
21 this.then(function () {
21 this.then(function () {
22 // Check if the widget manager has been instanciated.
22 // Check if the widget manager has been instanciated.
23 this.test.assert(this.evaluate(function() {
23 this.test.assert(this.evaluate(function() {
24 return IPython.widget_manager != undefined;
24 return IPython.widget_manager != undefined;
25 }), 'Notebook widget manager instanciated');
25 }), 'Notebook widget manager instanciated');
26 });
26 });
27
27
28 index = this.append_cell(
29 'names = [name for name in dir(widgets)' +
30 ' if name.endswith("Widget") and name!= "Widget" and name!= "DOMWidget"]\n' +
31 'for name in names:\n' +
32 ' print(name)\n');
33 this.execute_cell_then(index, function(index){
34
35 // Get the widget names that are registered with the widget manager. Assume
36 // a 1 to 1 mapping of model and widgets names (model names just have 'model'
37 // suffixed).
38 var javascript_names = this.evaluate(function () {
39 names = [];
40 for (var name in IPython.widget_manager._model_types) {
41 names.push(name.replace('Model',''));
42 }
43 return names;
44 });
45
46 // Get the widget names registered in python.
47 var python_names = this.get_output_cell(index).text.split('\n');
48
49 // Make sure the two lists have the same items.
50 for (var i in javascript_names) {
51 var javascript_name = javascript_names[i];
52 var found = false;
53 for (var j in python_names) {
54 var python_name = python_names[j];
55 if (python_name==javascript_name) {
56 found = true;
57 break;
58 }
59 }
60 this.test.assert(found, javascript_name + ' exists in python');
61 }
62 for (var i in python_names) {
63 var python_name = python_names[i];
64 if (python_name.length > 0) {
65 var found = false;
66 for (var j in javascript_names) {
67 var javascript_name = javascript_names[j];
68 if (python_name==javascript_name) {
69 found = true;
70 break;
71 }
72 }
73 this.test.assert(found, python_name + ' exists in javascript');
74 }
75 }
76 });
77
78 throttle_index = this.append_cell(
28 throttle_index = this.append_cell(
79 'import time\n' +
29 'import time\n' +
80 'textbox = widgets.StringWidget()\n' +
30 'textbox = widgets.TextBoxWidget()\n' +
81 'display(textbox)\n'+
31 'display(textbox)\n'+
82 'textbox.add_class("my-throttle-textbox")\n' +
32 'textbox.add_class("my-throttle-textbox")\n' +
83 'def handle_change(name, old, new):\n' +
33 'def handle_change(name, old, new):\n' +
84 ' print(len(new))\n' +
34 ' print(len(new))\n' +
85 ' time.sleep(0.5)\n' +
35 ' time.sleep(0.5)\n' +
86 'textbox.on_trait_change(handle_change)\n' +
36 'textbox.on_trait_change(handle_change)\n' +
87 'print("Success")');
37 'print("Success")');
88 this.execute_cell_then(throttle_index, function(index){
38 this.execute_cell_then(throttle_index, function(index){
89 this.test.assert(this.get_output_cell(index).text == 'Success\n',
39 this.test.assert(this.get_output_cell(index).text == 'Success\n',
90 'Test throttling cell executed with correct output');
40 'Test throttling cell executed with correct output');
91
41
92 this.test.assert(this.cell_element_exists(index,
42 this.test.assert(this.cell_element_exists(index,
93 '.widget-area .widget-subarea'),
43 '.widget-area .widget-subarea'),
94 'Widget subarea exists.');
44 'Widget subarea exists.');
95
45
96 this.test.assert(this.cell_element_exists(index,
46 this.test.assert(this.cell_element_exists(index,
97 '.my-throttle-textbox'), 'Textbox exists.');
47 '.my-throttle-textbox'), 'Textbox exists.');
98
48
99 // Send 20 characters
49 // Send 20 characters
100 this.sendKeys('.my-throttle-textbox', '....................');
50 this.sendKeys('.my-throttle-textbox', '....................');
101 });
51 });
102
52
103 this.wait(2000); // Wait for clicks to execute in kernel
53 this.wait(2000); // Wait for clicks to execute in kernel
104
54
105 this.then(function(){
55 this.then(function(){
106 var resume = true;
56 var resume = true;
107 var i = 0;
57 var i = 0;
108 while (resume) {
58 while (resume) {
109 i++;
59 i++;
110 var output = this.get_output_cell(throttle_index, i);
60 var output = this.get_output_cell(throttle_index, i);
111 if (output === undefined || output === null) {
61 if (output === undefined || output === null) {
112 resume = false;
62 resume = false;
113 i--;
63 i--;
114 }
64 }
115 }
65 }
116
66
117 // Only 4 outputs should have printed, but because of timing, sometimes
67 // Only 4 outputs should have printed, but because of timing, sometimes
118 // 5 outputs will print. All we need to do is verify num outputs <= 5
68 // 5 outputs will print. All we need to do is verify num outputs <= 5
119 // because that is much less than 20.
69 // because that is much less than 20.
120 this.test.assert(i <= 5, 'Messages throttled.');
70 this.test.assert(i <= 5, 'Messages throttled.');
121
71
122 // We also need to verify that the last state sent was correct.
72 // We also need to verify that the last state sent was correct.
123 var last_state = this.get_output_cell(throttle_index, i).text;
73 var last_state = this.get_output_cell(throttle_index, i).text;
124 this.test.assert(last_state == "20\n", "Last state sent when throttling.");
74 this.test.assert(last_state == "20\n", "Last state sent when throttling.");
125 })
75 })
126 });
76 });
@@ -1,98 +1,86 b''
1 // Test widget bool class
1 // Test widget bool class
2 casper.notebook_test(function () {
2 casper.notebook_test(function () {
3 index = this.append_cell(
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
6 'print("Success")');
7 this.execute_cell_then(index);
7 this.execute_cell_then(index);
8
8
9 var bool_index = this.append_cell(
9 var bool_index = this.append_cell(
10 'bool_widgets = [widgets.BoolWidget(description="Title", value=True) for i in range(2)]\n' +
10 'bool_widgets = [widgets.CheckBoxWidget(description="Title", value=True),\n' +
11 ' widgets.ToggleButtonWidget(description="Title", value=True)]\n' +
11 'display(bool_widgets[0])\n' +
12 'display(bool_widgets[0])\n' +
12 'bool_widgets[1].view_name = "ToggleButtonView"\n' +
13 'display(bool_widgets[1])\n' +
13 'display(bool_widgets[1])\n' +
14 'for widget in bool_widgets:\n' +
15 ' def handle_change(name,old,new):\n' +
16 ' for other_widget in bool_widgets:\n' +
17 ' other_widget.value = new\n' +
18 ' widget.on_trait_change(handle_change, "value")\n' +
19 'print("Success")');
14 'print("Success")');
20 this.execute_cell_then(bool_index, function(index){
15 this.execute_cell_then(bool_index, function(index){
21
16
22 this.test.assert(this.get_output_cell(index).text == 'Success\n',
17 this.test.assert(this.get_output_cell(index).text == 'Success\n',
23 'Create bool widget cell executed with correct output.');
18 'Create bool widget cell executed with correct output.');
24
19
25 this.test.assert(this.cell_element_exists(index,
20 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea'),
21 '.widget-area .widget-subarea'),
27 'Widget subarea exists.');
22 'Widget subarea exists.');
28
23
29 this.test.assert(this.cell_element_exists(index,
24 this.test.assert(this.cell_element_exists(index,
30 '.widget-area .widget-subarea .widget-hbox-single input'),
25 '.widget-area .widget-subarea .widget-hbox-single input'),
31 'Checkbox exists.');
26 'Checkbox exists.');
32
27
33 this.test.assert(this.cell_element_function(index,
28 this.test.assert(this.cell_element_function(index,
34 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
29 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
35 'Checkbox is checked.');
30 'Checkbox is checked.');
36
31
37 this.test.assert(this.cell_element_exists(index,
32 this.test.assert(this.cell_element_exists(index,
38 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel'),
33 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel'),
39 'Checkbox label exists.');
34 'Checkbox label exists.');
40
35
41 this.test.assert(this.cell_element_function(index,
36 this.test.assert(this.cell_element_function(index,
42 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel', 'html')=="Title",
37 '.widget-area .widget-subarea .widget-hbox-single .widget-hlabel', 'html')=="Title",
43 'Checkbox labeled correctly.');
38 'Checkbox labeled correctly.');
44
39
45 this.test.assert(this.cell_element_exists(index,
40 this.test.assert(this.cell_element_exists(index,
46 '.widget-area .widget-subarea button'),
41 '.widget-area .widget-subarea button'),
47 'Toggle button exists.');
42 'Toggle button exists.');
48
43
49 this.test.assert(this.cell_element_function(index,
44 this.test.assert(this.cell_element_function(index,
50 '.widget-area .widget-subarea button', 'html')=="Title",
45 '.widget-area .widget-subarea button', 'html')=="Title",
51 'Toggle button labeled correctly.');
46 'Toggle button labeled correctly.');
52
47
53 this.test.assert(this.cell_element_function(index,
48 this.test.assert(this.cell_element_function(index,
54 '.widget-area .widget-subarea button', 'hasClass', ['active']),
49 '.widget-area .widget-subarea button', 'hasClass', ['active']),
55 'Toggle button is toggled.');
50 'Toggle button is toggled.');
56
51
57 });
52 });
58
53
59 index = this.append_cell(
54 index = this.append_cell(
60 'bool_widgets[0].value = False\n' +
55 'bool_widgets[0].value = False\n' +
56 'bool_widgets[1].value = False\n' +
61 'print("Success")');
57 'print("Success")');
62 this.execute_cell_then(index, function(index){
58 this.execute_cell_then(index, function(index){
63
59
64 this.test.assert(this.get_output_cell(index).text == 'Success\n',
60 this.test.assert(this.get_output_cell(index).text == 'Success\n',
65 'Change bool widget value cell executed with correct output.');
61 'Change bool widget value cell executed with correct output.');
66
62
67 this.test.assert(! this.cell_element_function(bool_index,
63 this.test.assert(! this.cell_element_function(bool_index,
68 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
64 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
69 'Checkbox is not checked. (1)');
65 'Checkbox is not checked. (1)');
70
66
71 this.test.assert(! this.cell_element_function(bool_index,
67 this.test.assert(! this.cell_element_function(bool_index,
72 '.widget-area .widget-subarea button', 'hasClass', ['active']),
68 '.widget-area .widget-subarea button', 'hasClass', ['active']),
73 'Toggle button is not toggled. (1)');
69 'Toggle button is not toggled. (1)');
74
75 // Try toggling the bool by clicking on the toggle button.
76 this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
77
78 this.test.assert(this.cell_element_function(bool_index,
79 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
80 'Checkbox is checked. (2)');
81
82 this.test.assert(this.cell_element_function(bool_index,
83 '.widget-area .widget-subarea button', 'hasClass', ['active']),
84 'Toggle button is toggled. (2)');
85
70
86 // Try toggling the bool by clicking on the checkbox.
71 // Try toggling the bool by clicking on the checkbox.
87 this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox-single input', 'click');
72 this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox-single input', 'click');
88
73
89 this.test.assert(! this.cell_element_function(bool_index,
74 this.test.assert(this.cell_element_function(bool_index,
90 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
75 '.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
91 'Checkbox is not checked. (3)');
76 'Checkbox is checked. (2)');
92
77
93 this.test.assert(! this.cell_element_function(bool_index,
78 // Try toggling the bool by clicking on the toggle button.
79 this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
80
81 this.test.assert(this.cell_element_function(bool_index,
94 '.widget-area .widget-subarea button', 'hasClass', ['active']),
82 '.widget-area .widget-subarea button', 'hasClass', ['active']),
95 'Toggle button is not toggled. (3)');
83 'Toggle button is toggled. (3)');
96
84
97 });
85 });
98 }); No newline at end of file
86 });
@@ -1,447 +1,447 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import inspect
16 import inspect
17 import types
17 import types
18
18
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool
22 from IPython.utils.py3compat import string_types
22 from IPython.utils.py3compat import string_types
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Classes
25 # Classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 class Widget(LoggingConfigurable):
27 class Widget(LoggingConfigurable):
28
28
29 # Shared declarations (Class level)
29 # Shared declarations (Class level)
30 widget_construction_callback = None
30 widget_construction_callback = None
31 widgets = {}
31 widgets = {}
32
32
33 def on_widget_constructed(callback):
33 def on_widget_constructed(callback):
34 """Class method, registers a callback to be called when a widget is
34 """Class method, registers a callback to be called when a widget is
35 constructed. The callback must have the following signature:
35 constructed. The callback must have the following signature:
36 callback(widget)"""
36 callback(widget)"""
37 Widget.widget_construction_callback = callback
37 Widget.widget_construction_callback = callback
38
38
39 def _call_widget_constructed(widget):
39 def _call_widget_constructed(widget):
40 """Class method, called when a widget is constructed."""
40 """Class method, called when a widget is constructed."""
41 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
41 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
42 Widget.widget_construction_callback(widget)
42 Widget.widget_construction_callback(widget)
43
43
44
44
45
45
46 # Public declarations (Instance level)
46 # Public declarations (Instance level)
47 model_name = Unicode('WidgetModel', help="""Name of the backbone model
47 model_name = Unicode('WidgetModel', help="""Name of the backbone model
48 registered in the front-end to create and sync this widget with.""")
48 registered in the front-end to create and sync this widget with.""")
49 view_name = Unicode(help="""Default view registered in the front-end
49 view_name = Unicode(help="""Default view registered in the front-end
50 to use to represent the widget.""", sync=True)
50 to use to represent the widget.""", sync=True)
51
51
52 @contextmanager
52 @contextmanager
53 def property_lock(self, key, value):
53 def property_lock(self, key, value):
54 """Lock a property-value pair.
54 """Lock a property-value pair.
55
55
56 NOTE: This, in addition to the single lock for all state changes, is
56 NOTE: This, in addition to the single lock for all state changes, is
57 flawed. In the future we may want to look into buffering state changes
57 flawed. In the future we may want to look into buffering state changes
58 back to the front-end."""
58 back to the front-end."""
59 self._property_lock = (key, value)
59 self._property_lock = (key, value)
60 try:
60 try:
61 yield
61 yield
62 finally:
62 finally:
63 self._property_lock = (None, None)
63 self._property_lock = (None, None)
64
64
65 def should_send_property(self, key, value):
65 def should_send_property(self, key, value):
66 """Check the property lock (property_lock)"""
66 """Check the property lock (property_lock)"""
67 return key != self._property_lock[0] or \
67 return key != self._property_lock[0] or \
68 value != self._property_lock[1]
68 value != self._property_lock[1]
69
69
70 @property
70 @property
71 def keys(self):
71 def keys(self):
72 if self._keys is None:
72 if self._keys is None:
73 self._keys = []
73 self._keys = []
74 for trait_name in self.trait_names():
74 for trait_name in self.trait_names():
75 if self.trait_metadata(trait_name, 'sync'):
75 if self.trait_metadata(trait_name, 'sync'):
76 self._keys.append(trait_name)
76 self._keys.append(trait_name)
77 return self._keys
77 return self._keys
78
78
79 # Private/protected declarations
79 # Private/protected declarations
80 _comm = Instance('IPython.kernel.comm.Comm')
80 _comm = Instance('IPython.kernel.comm.Comm')
81
81
82 def __init__(self, **kwargs):
82 def __init__(self, **kwargs):
83 """Public constructor
83 """Public constructor
84 """
84 """
85 self.closed = False
85 self.closed = False
86 self._property_lock = (None, None)
86 self._property_lock = (None, None)
87 self._display_callbacks = []
87 self._display_callbacks = []
88 self._msg_callbacks = []
88 self._msg_callbacks = []
89 self._keys = None
89 self._keys = None
90 super(Widget, self).__init__(**kwargs)
90 super(Widget, self).__init__(**kwargs)
91
91
92 self.on_trait_change(self._handle_property_changed, self.keys)
92 self.on_trait_change(self._handle_property_changed, self.keys)
93 Widget._call_widget_constructed(self)
93 Widget._call_widget_constructed(self)
94
94
95 def __del__(self):
95 def __del__(self):
96 """Object disposal"""
96 """Object disposal"""
97 self.close()
97 self.close()
98
98
99 def close(self):
99 def close(self):
100 """Close method. Closes the widget which closes the underlying comm.
100 """Close method. Closes the widget which closes the underlying comm.
101 When the comm is closed, all of the widget views are automatically
101 When the comm is closed, all of the widget views are automatically
102 removed from the front-end."""
102 removed from the front-end."""
103 if not self.closed:
103 if not self.closed:
104 self._comm.close()
104 self._comm.close()
105 self._close()
105 self._close()
106
106
107
107
108 def _close(self):
108 def _close(self):
109 """Unsafe close"""
109 """Unsafe close"""
110 del Widget.widgets[self.model_id]
110 del Widget.widgets[self.model_id]
111 self._comm = None
111 self._comm = None
112 self.closed = True
112 self.closed = True
113
113
114
114
115 @property
115 @property
116 def comm(self):
116 def comm(self):
117 if self._comm is None:
117 if self._comm is None:
118 # Create a comm.
118 # Create a comm.
119 self._comm = Comm(target_name=self.model_name)
119 self._comm = Comm(target_name=self.model_name)
120 self._comm.on_msg(self._handle_msg)
120 self._comm.on_msg(self._handle_msg)
121 self._comm.on_close(self._close)
121 self._comm.on_close(self._close)
122 Widget.widgets[self.model_id] = self
122 Widget.widgets[self.model_id] = self
123
123
124 # first update
124 # first update
125 self.send_state()
125 self.send_state()
126 return self._comm
126 return self._comm
127
127
128 @property
128 @property
129 def model_id(self):
129 def model_id(self):
130 return self.comm.comm_id
130 return self.comm.comm_id
131
131
132 # Event handlers
132 # Event handlers
133 def _handle_msg(self, msg):
133 def _handle_msg(self, msg):
134 """Called when a msg is received from the front-end"""
134 """Called when a msg is received from the front-end"""
135 data = msg['content']['data']
135 data = msg['content']['data']
136 method = data['method']
136 method = data['method']
137 if not method in ['backbone', 'custom']:
137 if not method in ['backbone', 'custom']:
138 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
138 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
139
139
140 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
140 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
141 if method == 'backbone' and 'sync_data' in data:
141 if method == 'backbone' and 'sync_data' in data:
142 sync_data = data['sync_data']
142 sync_data = data['sync_data']
143 self._handle_receive_state(sync_data) # handles all methods
143 self._handle_receive_state(sync_data) # handles all methods
144
144
145 # Handle a custom msg from the front-end
145 # Handle a custom msg from the front-end
146 elif method == 'custom':
146 elif method == 'custom':
147 if 'custom_content' in data:
147 if 'custom_content' in data:
148 self._handle_custom_msg(data['custom_content'])
148 self._handle_custom_msg(data['custom_content'])
149
149
150
150
151 def _handle_receive_state(self, sync_data):
151 def _handle_receive_state(self, sync_data):
152 """Called when a state is received from the front-end."""
152 """Called when a state is received from the front-end."""
153 for name in self.keys:
153 for name in self.keys:
154 if name in sync_data:
154 if name in sync_data:
155 value = self._unpack_widgets(sync_data[name])
155 value = self._unpack_widgets(sync_data[name])
156 with self.property_lock(name, value):
156 with self.property_lock(name, value):
157 setattr(self, name, value)
157 setattr(self, name, value)
158
158
159
159
160 def _handle_custom_msg(self, content):
160 def _handle_custom_msg(self, content):
161 """Called when a custom msg is received."""
161 """Called when a custom msg is received."""
162 for handler in self._msg_callbacks:
162 for handler in self._msg_callbacks:
163 handler(self, content)
163 handler(self, content)
164
164
165
165
166 def _handle_property_changed(self, name, old, new):
166 def _handle_property_changed(self, name, old, new):
167 """Called when a property has been changed."""
167 """Called when a property has been changed."""
168 # Make sure this isn't information that the front-end just sent us.
168 # Make sure this isn't information that the front-end just sent us.
169 if self.should_send_property(name, new):
169 if self.should_send_property(name, new):
170 # Send new state to front-end
170 # Send new state to front-end
171 self.send_state(key=name)
171 self.send_state(key=name)
172
172
173 def _handle_displayed(self, **kwargs):
173 def _handle_displayed(self, **kwargs):
174 """Called when a view has been displayed for this widget instance"""
174 """Called when a view has been displayed for this widget instance"""
175 for handler in self._display_callbacks:
175 for handler in self._display_callbacks:
176 handler(self, **kwargs)
176 handler(self, **kwargs)
177
177
178 # Public methods
178 # Public methods
179 def send_state(self, key=None):
179 def send_state(self, key=None):
180 """Sends the widget state, or a piece of it, to the front-end.
180 """Sends the widget state, or a piece of it, to the front-end.
181
181
182 Parameters
182 Parameters
183 ----------
183 ----------
184 key : unicode (optional)
184 key : unicode (optional)
185 A single property's name to sync with the front-end.
185 A single property's name to sync with the front-end.
186 """
186 """
187 self._send({"method": "update",
187 self._send({"method": "update",
188 "state": self.get_state()})
188 "state": self.get_state()})
189
189
190 def get_state(self, key=None):
190 def get_state(self, key=None):
191 """Gets the widget state, or a piece of it.
191 """Gets the widget state, or a piece of it.
192
192
193 Parameters
193 Parameters
194 ----------
194 ----------
195 key : unicode (optional)
195 key : unicode (optional)
196 A single property's name to get.
196 A single property's name to get.
197 """
197 """
198 keys = self.keys if key is None else [key]
198 keys = self.keys if key is None else [key]
199 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
199 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
200
200
201
201
202 def _pack_widgets(self, values):
202 def _pack_widgets(self, values):
203 """This function recursively converts all widget instances to model id
203 """This function recursively converts all widget instances to model id
204 strings.
204 strings.
205
205
206 Children widgets will be stored and transmitted to the front-end by
206 Children widgets will be stored and transmitted to the front-end by
207 their model ids."""
207 their model ids."""
208 if isinstance(values, dict):
208 if isinstance(values, dict):
209 new_dict = {}
209 new_dict = {}
210 for key, value in values.items():
210 for key, value in values.items():
211 new_dict[key] = self._pack_widgets(value)
211 new_dict[key] = self._pack_widgets(value)
212 return new_dict
212 return new_dict
213 elif isinstance(values, list):
213 elif isinstance(values, list):
214 new_list = []
214 new_list = []
215 for value in values:
215 for value in values:
216 new_list.append(self._pack_widgets(value))
216 new_list.append(self._pack_widgets(value))
217 return new_list
217 return new_list
218 elif isinstance(values, Widget):
218 elif isinstance(values, Widget):
219 return values.model_id
219 return values.model_id
220 else:
220 else:
221 return values
221 return values
222
222
223
223
224 def _unpack_widgets(self, values):
224 def _unpack_widgets(self, values):
225 """This function recursively converts all model id strings to widget
225 """This function recursively converts all model id strings to widget
226 instances.
226 instances.
227
227
228 Children widgets will be stored and transmitted to the front-end by
228 Children widgets will be stored and transmitted to the front-end by
229 their model ids."""
229 their model ids."""
230 if isinstance(values, dict):
230 if isinstance(values, dict):
231 new_dict = {}
231 new_dict = {}
232 for key, values in values.items():
232 for key, values in values.items():
233 new_dict[key] = self._unpack_widgets(values[key])
233 new_dict[key] = self._unpack_widgets(values[key])
234 return new_dict
234 return new_dict
235 elif isinstance(values, list):
235 elif isinstance(values, list):
236 new_list = []
236 new_list = []
237 for value in values:
237 for value in values:
238 new_list.append(self._unpack_widgets(value))
238 new_list.append(self._unpack_widgets(value))
239 return new_list
239 return new_list
240 elif isinstance(values, string_types):
240 elif isinstance(values, string_types):
241 if widget.model_id in Widget.widgets:
241 if values in Widget.widgets:
242 return Widget.widgets[widget.model_id]
242 return Widget.widgets[values]
243 else:
243 else:
244 return values
244 return values
245 else:
245 else:
246 return values
246 return values
247
247
248
248
249 def send(self, content):
249 def send(self, content):
250 """Sends a custom msg to the widget model in the front-end.
250 """Sends a custom msg to the widget model in the front-end.
251
251
252 Parameters
252 Parameters
253 ----------
253 ----------
254 content : dict
254 content : dict
255 Content of the message to send.
255 Content of the message to send.
256 """
256 """
257 self._send({"method": "custom", "custom_content": content})
257 self._send({"method": "custom", "custom_content": content})
258
258
259
259
260 def on_msg(self, callback, remove=False):
260 def on_msg(self, callback, remove=False):
261 """Register or unregister a callback for when a custom msg is recieved
261 """Register or unregister a callback for when a custom msg is recieved
262 from the front-end.
262 from the front-end.
263
263
264 Parameters
264 Parameters
265 ----------
265 ----------
266 callback: method handler
266 callback: method handler
267 Can have a signature of:
267 Can have a signature of:
268 - callback(content)
268 - callback(content)
269 - callback(sender, content)
269 - callback(sender, content)
270 remove: bool
270 remove: bool
271 True if the callback should be unregistered."""
271 True if the callback should be unregistered."""
272 if remove and callback in self._msg_callbacks:
272 if remove and callback in self._msg_callbacks:
273 self._msg_callbacks.remove(callback)
273 self._msg_callbacks.remove(callback)
274 elif not remove and not callback in self._msg_callbacks:
274 elif not remove and not callback in self._msg_callbacks:
275 if callable(callback):
275 if callable(callback):
276 argspec = inspect.getargspec(callback)
276 argspec = inspect.getargspec(callback)
277 nargs = len(argspec[0])
277 nargs = len(argspec[0])
278
278
279 # Bound methods have an additional 'self' argument
279 # Bound methods have an additional 'self' argument
280 if isinstance(callback, types.MethodType):
280 if isinstance(callback, types.MethodType):
281 nargs -= 1
281 nargs -= 1
282
282
283 # Call the callback
283 # Call the callback
284 if nargs == 1:
284 if nargs == 1:
285 self._msg_callbacks.append(lambda sender, content: callback(content))
285 self._msg_callbacks.append(lambda sender, content: callback(content))
286 elif nargs == 2:
286 elif nargs == 2:
287 self._msg_callbacks.append(callback)
287 self._msg_callbacks.append(callback)
288 else:
288 else:
289 raise TypeError('Widget msg callback must ' \
289 raise TypeError('Widget msg callback must ' \
290 'accept 1 or 2 arguments, not %d.' % nargs)
290 'accept 1 or 2 arguments, not %d.' % nargs)
291 else:
291 else:
292 raise Exception('Callback must be callable.')
292 raise Exception('Callback must be callable.')
293
293
294
294
295 def on_displayed(self, callback, remove=False):
295 def on_displayed(self, callback, remove=False):
296 """Register or unregister a callback to be called when the widget has
296 """Register or unregister a callback to be called when the widget has
297 been displayed.
297 been displayed.
298
298
299 Parameters
299 Parameters
300 ----------
300 ----------
301 callback: method handler
301 callback: method handler
302 Can have a signature of:
302 Can have a signature of:
303 - callback(sender, **kwargs)
303 - callback(sender, **kwargs)
304 kwargs from display call passed through without modification.
304 kwargs from display call passed through without modification.
305 remove: bool
305 remove: bool
306 True if the callback should be unregistered."""
306 True if the callback should be unregistered."""
307 if remove and callback in self._display_callbacks:
307 if remove and callback in self._display_callbacks:
308 self._display_callbacks.remove(callback)
308 self._display_callbacks.remove(callback)
309 elif not remove and not callback in self._display_callbacks:
309 elif not remove and not callback in self._display_callbacks:
310 if callable(handler):
310 if callable(handler):
311 self._display_callbacks.append(callback)
311 self._display_callbacks.append(callback)
312 else:
312 else:
313 raise Exception('Callback must be callable.')
313 raise Exception('Callback must be callable.')
314
314
315
315
316 # Support methods
316 # Support methods
317 def _ipython_display_(self, **kwargs):
317 def _ipython_display_(self, **kwargs):
318 """Function that is called when `IPython.display.display` is called on
318 """Function that is called when `IPython.display.display` is called on
319 the widget."""
319 the widget."""
320
320
321 # Show view. By sending a display message, the comm is opened and the
321 # Show view. By sending a display message, the comm is opened and the
322 # initial state is sent.
322 # initial state is sent.
323 self._send({"method": "display"})
323 self._send({"method": "display"})
324 self._handle_displayed(**kwargs)
324 self._handle_displayed(**kwargs)
325
325
326
326
327 def _send(self, msg):
327 def _send(self, msg):
328 """Sends a message to the model in the front-end"""
328 """Sends a message to the model in the front-end"""
329 self.comm.send(msg)
329 self.comm.send(msg)
330
330
331
331
332 class DOMWidget(Widget):
332 class DOMWidget(Widget):
333 visible = Bool(True, help="Whether or not the widget is visible.", sync=True)
333 visible = Bool(True, help="Whether or not the widget is visible.", sync=True)
334
334
335 # Private/protected declarations
335 # Private/protected declarations
336 _css = Dict(sync=True) # Internal CSS property dict
336 _css = Dict(sync=True) # Internal CSS property dict
337
337
338 def get_css(self, key, selector=""):
338 def get_css(self, key, selector=""):
339 """Get a CSS property of the widget.
339 """Get a CSS property of the widget.
340
340
341 Note: This function does not actually request the CSS from the
341 Note: This function does not actually request the CSS from the
342 front-end; Only properties that have been set with set_css can be read.
342 front-end; Only properties that have been set with set_css can be read.
343
343
344 Parameters
344 Parameters
345 ----------
345 ----------
346 key: unicode
346 key: unicode
347 CSS key
347 CSS key
348 selector: unicode (optional)
348 selector: unicode (optional)
349 JQuery selector used when the CSS key/value was set.
349 JQuery selector used when the CSS key/value was set.
350 """
350 """
351 if selector in self._css and key in self._css[selector]:
351 if selector in self._css and key in self._css[selector]:
352 return self._css[selector][key]
352 return self._css[selector][key]
353 else:
353 else:
354 return None
354 return None
355
355
356 def set_css(self, *args, **kwargs):
356 def set_css(self, *args, **kwargs):
357 """Set one or more CSS properties of the widget.
357 """Set one or more CSS properties of the widget.
358
358
359 This function has two signatures:
359 This function has two signatures:
360 - set_css(css_dict, selector='')
360 - set_css(css_dict, selector='')
361 - set_css(key, value, selector='')
361 - set_css(key, value, selector='')
362
362
363 Parameters
363 Parameters
364 ----------
364 ----------
365 css_dict : dict
365 css_dict : dict
366 CSS key/value pairs to apply
366 CSS key/value pairs to apply
367 key: unicode
367 key: unicode
368 CSS key
368 CSS key
369 value
369 value
370 CSS value
370 CSS value
371 selector: unicode (optional)
371 selector: unicode (optional)
372 JQuery selector to use to apply the CSS key/value. If no selector
372 JQuery selector to use to apply the CSS key/value. If no selector
373 is provided, an empty selector is used. An empty selector makes the
373 is provided, an empty selector is used. An empty selector makes the
374 front-end try to apply the css to a default element. The default
374 front-end try to apply the css to a default element. The default
375 element is an attribute unique to each view, which is a DOM element
375 element is an attribute unique to each view, which is a DOM element
376 of the view that should be styled with common CSS (see
376 of the view that should be styled with common CSS (see
377 `$el_to_style` in the Javascript code).
377 `$el_to_style` in the Javascript code).
378 """
378 """
379 selector = kwargs.get('selector', '')
379 selector = kwargs.get('selector', '')
380
380
381 # Signature 1: set_css(css_dict, selector='')
381 # Signature 1: set_css(css_dict, selector='')
382 if len(args) == 1:
382 if len(args) == 1:
383 if isinstance(args[0], dict):
383 if isinstance(args[0], dict):
384 for (key, value) in args[0].items():
384 for (key, value) in args[0].items():
385 if not (key in self._css[selector] and value == self._css[selector][key]):
385 if not (key in self._css[selector] and value == self._css[selector][key]):
386 self._css[selector][key] = value
386 self._css[selector][key] = value
387 self.send_state('_css')
387 self.send_state('_css')
388 else:
388 else:
389 raise Exception('css_dict must be a dict.')
389 raise Exception('css_dict must be a dict.')
390
390
391 # Signature 2: set_css(key, value, selector='')
391 # Signature 2: set_css(key, value, selector='')
392 elif len(args) == 2 or len(args) == 3:
392 elif len(args) == 2 or len(args) == 3:
393
393
394 # Selector can be a positional arg if it's the 3rd value
394 # Selector can be a positional arg if it's the 3rd value
395 if len(args) == 3:
395 if len(args) == 3:
396 selector = args[2]
396 selector = args[2]
397 if selector not in self._css:
397 if selector not in self._css:
398 self._css[selector] = {}
398 self._css[selector] = {}
399
399
400 # Only update the property if it has changed.
400 # Only update the property if it has changed.
401 key = args[0]
401 key = args[0]
402 value = args[1]
402 value = args[1]
403 if not (key in self._css[selector] and value == self._css[selector][key]):
403 if not (key in self._css[selector] and value == self._css[selector][key]):
404 self._css[selector][key] = value
404 self._css[selector][key] = value
405 self.send_state('_css') # Send new state to client.
405 self.send_state('_css') # Send new state to client.
406 else:
406 else:
407 raise Exception('set_css only accepts 1-3 arguments')
407 raise Exception('set_css only accepts 1-3 arguments')
408
408
409
409
410 def add_class(self, class_names, selector=""):
410 def add_class(self, class_names, selector=""):
411 """Add class[es] to a DOM element
411 """Add class[es] to a DOM element
412
412
413 Parameters
413 Parameters
414 ----------
414 ----------
415 class_names: unicode or list
415 class_names: unicode or list
416 Class name(s) to add to the DOM element(s).
416 Class name(s) to add to the DOM element(s).
417 selector: unicode (optional)
417 selector: unicode (optional)
418 JQuery selector to select the DOM element(s) that the class(es) will
418 JQuery selector to select the DOM element(s) that the class(es) will
419 be added to.
419 be added to.
420 """
420 """
421 class_list = class_names
421 class_list = class_names
422 if isinstance(class_list, list):
422 if isinstance(class_list, list):
423 class_list = ' '.join(class_list)
423 class_list = ' '.join(class_list)
424
424
425 self.send({"msg_type": "add_class",
425 self.send({"msg_type": "add_class",
426 "class_list": class_list,
426 "class_list": class_list,
427 "selector": selector})
427 "selector": selector})
428
428
429
429
430 def remove_class(self, class_names, selector=""):
430 def remove_class(self, class_names, selector=""):
431 """Remove class[es] from a DOM element
431 """Remove class[es] from a DOM element
432
432
433 Parameters
433 Parameters
434 ----------
434 ----------
435 class_names: unicode or list
435 class_names: unicode or list
436 Class name(s) to remove from the DOM element(s).
436 Class name(s) to remove from the DOM element(s).
437 selector: unicode (optional)
437 selector: unicode (optional)
438 JQuery selector to select the DOM element(s) that the class(es) will
438 JQuery selector to select the DOM element(s) that the class(es) will
439 be removed from.
439 be removed from.
440 """
440 """
441 class_list = class_names
441 class_list = class_names
442 if isinstance(class_list, list):
442 if isinstance(class_list, list):
443 class_list = ' '.join(class_list)
443 class_list = ' '.join(class_list)
444
444
445 self.send({"msg_type": "remove_class",
445 self.send({"msg_type": "remove_class",
446 "class_list": class_list,
446 "class_list": class_list,
447 "selector": selector})
447 "selector": selector})
@@ -1,94 +1,94 b''
1 """StringWidget class.
1 """StringWidget class.
2
2
3 Represents a unicode string using a widget.
3 Represents a unicode string using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 import inspect
16 import inspect
17 import types
17 import types
18
18
19 from .widget import DOMWidget
19 from .widget import DOMWidget
20 from IPython.utils.traitlets import Unicode, Bool, List, Int
20 from IPython.utils.traitlets import Unicode, Bool, List, Int
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Classes
23 # Classes
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 class HTMLWidget(DOMWidget):
25 class HTMLWidget(DOMWidget):
26 view_name = Unicode('HTMLView', sync=True)
26 view_name = Unicode('HTMLView', sync=True)
27
27
28 # Keys
28 # Keys
29 value = Unicode(help="String value", sync=True)
29 value = Unicode(help="String value", sync=True)
30 disabled = Bool(False, help="Enable or disable user changes", sync=True)
30 disabled = Bool(False, help="Enable or disable user changes", sync=True)
31 description = Unicode(help="Description of the value this widget represents", sync=True)
31 description = Unicode(help="Description of the value this widget represents", sync=True)
32
32
33
33
34 class LatexWidget(HTMLWidget):
34 class LatexWidget(HTMLWidget):
35 view_name = Unicode('LatexView', sync=True)
35 view_name = Unicode('LatexView', sync=True)
36
36
37
37
38 class TextAreaWidget(HTMLWidget):
38 class TextAreaWidget(HTMLWidget):
39 view_name = Unicode('TextAreaView', sync=True)
39 view_name = Unicode('TextAreaView', sync=True)
40
40
41 def scroll_to_bottom(self):
41 def scroll_to_bottom(self):
42 self.send({"method": "scroll_to_bottom"})
42 self.send({"method": "scroll_to_bottom"})
43
43
44
44
45 class TextBoxWidget(HTMLWidget):
45 class TextBoxWidget(HTMLWidget):
46 view_name = Unicode('TextBoxView', sync=True)
46 view_name = Unicode('TextBoxView', sync=True)
47
47
48 def __init__(self, **kwargs):
48 def __init__(self, **kwargs):
49 super(StringWidget, self).__init__(**kwargs)
49 super(TextBoxWidget, self).__init__(**kwargs)
50 self._submission_callbacks = []
50 self._submission_callbacks = []
51 self.on_msg(self._handle_string_msg)
51 self.on_msg(self._handle_string_msg)
52
52
53 def _handle_string_msg(self, content):
53 def _handle_string_msg(self, content):
54 """Handle a msg from the front-end
54 """Handle a msg from the front-end
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 content: dict
58 content: dict
59 Content of the msg."""
59 Content of the msg."""
60 if 'event' in content and content['event'] == 'submit':
60 if 'event' in content and content['event'] == 'submit':
61 for handler in self._submission_callbacks:
61 for handler in self._submission_callbacks:
62 handler(self)
62 handler(self)
63
63
64 def on_submit(self, callback, remove=False):
64 def on_submit(self, callback, remove=False):
65 """Register a callback to handle text submission (triggered when the
65 """Register a callback to handle text submission (triggered when the
66 user clicks enter).
66 user clicks enter).
67
67
68 Parameters
68 Parameters
69 callback: Method handle
69 callback: Method handle
70 Function to be called when the text has been submitted. Function
70 Function to be called when the text has been submitted. Function
71 can have two possible signatures:
71 can have two possible signatures:
72 callback()
72 callback()
73 callback(sender)
73 callback(sender)
74 remove: bool (optional)
74 remove: bool (optional)
75 Whether or not to unregister the callback"""
75 Whether or not to unregister the callback"""
76 if remove and callback in self._submission_callbacks:
76 if remove and callback in self._submission_callbacks:
77 self._submission_callbacks.remove(callback)
77 self._submission_callbacks.remove(callback)
78 elif not remove and not callback in self._submission_callbacks:
78 elif not remove and not callback in self._submission_callbacks:
79 if callable(callback):
79 if callable(callback):
80 argspec = inspect.getargspec(callback)
80 argspec = inspect.getargspec(callback)
81 nargs = len(argspec[0])
81 nargs = len(argspec[0])
82
82
83 # Bound methods have an additional 'self' argument
83 # Bound methods have an additional 'self' argument
84 if isinstance(callback, types.MethodType):
84 if isinstance(callback, types.MethodType):
85 nargs -= 1
85 nargs -= 1
86
86
87 # Call the callback
87 # Call the callback
88 if nargs == 0:
88 if nargs == 0:
89 self._submission_callbacks.append(lambda sender: callback())
89 self._submission_callbacks.append(lambda sender: callback())
90 elif nargs == 1:
90 elif nargs == 1:
91 self._submission_callbacks.append(callback)
91 self._submission_callbacks.append(callback)
92 else:
92 else:
93 raise TypeError('StringWidget submit callback must ' \
93 raise TypeError('TextBoxWidget submit callback must ' \
94 'accept 0 or 1 arguments.')
94 'accept 0 or 1 arguments.')
General Comments 0
You need to be logged in to leave comments. Login now