mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-27 04:20:22 +08:00
Many checks off the todo list, test fixes
This commit is contained in:
parent
f6de685d9f
commit
d103c682c5
@ -95,7 +95,7 @@ function(widget_manager, underscore, backbone){
|
||||
var value = state[key];
|
||||
this.key_value_lock = [key, value];
|
||||
try {
|
||||
this.set(key, state[key]);
|
||||
this.set(key, this._unpack_models(value));
|
||||
} finally {
|
||||
this.key_value_lock = null;
|
||||
}
|
||||
@ -137,32 +137,26 @@ function(widget_manager, underscore, backbone){
|
||||
// The throttle has been exceeded, buffer the current msg so
|
||||
// it can be sent once the kernel has finished processing
|
||||
// some of the existing messages.
|
||||
if (method=='patch') {
|
||||
if (this.msg_buffer === null) {
|
||||
this.msg_buffer = $.extend({}, model_json); // Copy
|
||||
}
|
||||
for (attr in options.attrs) {
|
||||
var value = options.attrs[attr];
|
||||
if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
|
||||
this.msg_buffer[attr] = value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.msg_buffer === null) {
|
||||
this.msg_buffer = $.extend({}, model_json); // Copy
|
||||
}
|
||||
for (attr in options.attrs) {
|
||||
var value = this._pack_models(options.attrs[attr]);
|
||||
if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
|
||||
this.msg_buffer[attr] = value;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// We haven't exceeded the throttle, send the message like
|
||||
// normal. If this is a patch operation, just send the
|
||||
// changes.
|
||||
var send_json = model_json;
|
||||
if (method =='patch') {
|
||||
send_json = {};
|
||||
for (attr in options.attrs) {
|
||||
var value = options.attrs[attr];
|
||||
if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
|
||||
send_json[attr] = value;
|
||||
}
|
||||
send_json = {};
|
||||
for (attr in options.attrs) {
|
||||
var value = this._pack_models(options.attrs[attr]);
|
||||
if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
|
||||
send_json[attr] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,6 +171,37 @@ function(widget_manager, underscore, backbone){
|
||||
return model_json;
|
||||
},
|
||||
|
||||
_pack_models: function(value) {
|
||||
if (value instanceof Backbone.Model) {
|
||||
return value.id;
|
||||
} else if (value instanceof Object) {
|
||||
var packed = {};
|
||||
for (var key in value) {
|
||||
packed[key] = this._pack_models(value[key]);
|
||||
}
|
||||
return packed;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
_unpack_models: function(value) {
|
||||
if (value instanceof Object) {
|
||||
var unpacked = {};
|
||||
for (var key in value) {
|
||||
unpacked[key] = this._unpack_models(value[key]);
|
||||
}
|
||||
return unpacked;
|
||||
} else {
|
||||
var model = this.widget_manager.get_model(value);
|
||||
if (model !== null) {
|
||||
return model;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
@ -196,24 +221,23 @@ function(widget_manager, underscore, backbone){
|
||||
// triggered on model change
|
||||
},
|
||||
|
||||
child_view: function(model_id, options) {
|
||||
// create and return a child view, given a model id for a model and (optionally) a view name
|
||||
child_view: function(child_model, options) {
|
||||
// create and return a child view, given a model and (optionally) a view name
|
||||
// if the view name is not given, it defaults to the model's default view attribute
|
||||
var child_model = this.widget_manager.get_model(model_id);
|
||||
var child_view = this.widget_manager.create_view(child_model, options);
|
||||
this.child_views[model_id] = child_view;
|
||||
var child_view = this.model.widget_manager.create_view(child_model, options);
|
||||
this.child_views[child_model.id] = child_view;
|
||||
return child_view;
|
||||
},
|
||||
|
||||
update_child_views: function(old_list, new_list) {
|
||||
// this function takes an old list and new list of model ids
|
||||
// this function takes an old list and new list of models
|
||||
// views in child_views that correspond to deleted ids are deleted
|
||||
// views corresponding to added ids are added child_views
|
||||
|
||||
// delete old views
|
||||
_.each(_.difference(old_list, new_list), function(element, index, list) {
|
||||
var view = this.child_views[element];
|
||||
delete this.child_views[element];
|
||||
var view = this.child_views[element.id];
|
||||
delete this.child_views[element.id];
|
||||
view.remove();
|
||||
}, this);
|
||||
|
||||
@ -247,10 +271,10 @@ function(widget_manager, underscore, backbone){
|
||||
_.each(_.difference(new_list, old_list), function(item, index, list) {
|
||||
added_callback(item);
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
|
||||
callbacks: function(){
|
||||
return this.widget_manager.callbacks(this);
|
||||
return this.model.widget_manager.callbacks(this);
|
||||
},
|
||||
|
||||
render: function(){
|
||||
@ -271,7 +295,7 @@ function(widget_manager, underscore, backbone){
|
||||
// TODO: make changes more granular (e.g., trigger on visible:change)
|
||||
this.model.on('change', this.update, this);
|
||||
this.model.on('msg:custom', this.on_msg, this);
|
||||
WidgetView.initialize.apply(this, arguments);
|
||||
DOMWidgetView.__super__.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
on_msg: function(msg) {
|
||||
|
@ -64,7 +64,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$label.show();
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return CheckboxView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
});
|
||||
@ -88,13 +88,13 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
//
|
||||
// Called when the model is changed. The model may have been
|
||||
// changed by another view or by a state update from the back-end.
|
||||
if (options === undefined || options.updated_view != this) {
|
||||
if (this.model.get('value')) {
|
||||
this.$el.addClass('active');
|
||||
} else {
|
||||
this.$el.removeClass('active');
|
||||
}
|
||||
if (this.model.get('value')) {
|
||||
this.$el.addClass('active');
|
||||
} else {
|
||||
this.$el.removeClass('active');
|
||||
}
|
||||
|
||||
if (options === undefined || options.updated_view != this) {
|
||||
var disabled = this.model.get('disabled');
|
||||
this.$el.prop('disabled', disabled);
|
||||
|
||||
@ -105,17 +105,17 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$el.html(description);
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return ToggleButtonView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
events: {"click button" : "handleClick"},
|
||||
events: {"click" : "handleClick"},
|
||||
|
||||
// Handles and validates user input.
|
||||
handleClick: function(e) {
|
||||
|
||||
// Calling model.set will trigger all of the other views of the
|
||||
// model to update.
|
||||
this.model.set('value', ! $(e.target).hasClass('active'), {updated_view: this});
|
||||
this.model.set('value', ! $(this.$el).hasClass('active'), {updated_view: this});
|
||||
this.touch();
|
||||
},
|
||||
});
|
||||
|
@ -49,7 +49,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$el.removeAttr('disabled');
|
||||
}
|
||||
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return ButtonView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
events: {
|
||||
|
@ -64,7 +64,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager) {
|
||||
this.$el.empty();
|
||||
this.update_child_views(old_list, new_list);
|
||||
_.each(new_list, function(element, index, list) {
|
||||
this.$el.append(this.child_views[element].$el);
|
||||
this.$el.append(this.child_views[element.id].$el);
|
||||
}, this)
|
||||
},
|
||||
|
||||
@ -74,7 +74,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager) {
|
||||
// Called when the model is changed. The model may have been
|
||||
// changed by another view or by a state update from the back-end.
|
||||
set_flex_properties(this, this.$el);
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return ContainerView.__super__.update.apply(this);
|
||||
},
|
||||
});
|
||||
|
||||
@ -258,7 +258,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager) {
|
||||
this.show();
|
||||
}
|
||||
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return ModalView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
_get_selector_element: function(selector) {
|
||||
@ -277,7 +277,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager) {
|
||||
return this.$window.find(selector.substring(6));
|
||||
}
|
||||
} else {
|
||||
return IPython.DOMWidgetView._get_selector_element.apply(this, [selector]);
|
||||
return ModalView.__super__._get_selector_element.apply(this, [selector]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -47,7 +47,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
} else {
|
||||
this.$el.removeAttr('height');
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return ImageView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
});
|
||||
|
@ -101,7 +101,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$label.show();
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return DropdownView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
// Handle when a value is clicked.
|
||||
@ -193,7 +193,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$label.show();
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return RadioButtonsView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
// Handle when a value is clicked.
|
||||
@ -280,7 +280,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$label.show();
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return ToggleButtonsView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
// Handle when a value is clicked.
|
||||
@ -364,7 +364,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$label.show();
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return ListBoxView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
// Handle when a value is clicked.
|
||||
|
@ -77,7 +77,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
}
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return AccordionView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
add_child_view: function(view) {
|
||||
@ -130,7 +130,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
|
||||
initialize: function() {
|
||||
this.containers = [];
|
||||
IPython.DOMWidgetView.initialize.apply(this, arguments);
|
||||
TabView.__super__.initialize.apply(this, arguments);
|
||||
},
|
||||
|
||||
render: function(){
|
||||
@ -181,7 +181,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.select_page(selected_index);
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return TabView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
add_child_view: function(view) {
|
||||
|
@ -31,7 +31,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
// Called when the model is changed. The model may have been
|
||||
// changed by another view or by a state update from the back-end.
|
||||
this.$el.html(this.model.get('value'));
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return HTMLView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
});
|
||||
@ -54,7 +54,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$el.html(this.model.get('value'));
|
||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$el.get(0)]);
|
||||
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return LatexView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
});
|
||||
@ -114,7 +114,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$label.show();
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return TextAreaView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
events: {"keyup textarea": "handleChanging",
|
||||
@ -173,7 +173,7 @@ define(["notebook/js/widgets/widget"], function(widget_manager){
|
||||
this.$label.show();
|
||||
}
|
||||
}
|
||||
return IPython.DOMWidgetView.update.apply(this);
|
||||
return TextBoxView.__super__.update.apply(this);
|
||||
},
|
||||
|
||||
events: {"keyup input": "handleChanging",
|
||||
|
@ -27,7 +27,7 @@ casper.notebook_test(function () {
|
||||
|
||||
index = this.append_cell(
|
||||
'names = [name for name in dir(widgets)' +
|
||||
' if name.endswith("Widget") and name!= "Widget"]\n' +
|
||||
' if name.endswith("Widget") and name!= "Widget" and name!= "DOMWidget"]\n' +
|
||||
'for name in names:\n' +
|
||||
' print(name)\n');
|
||||
this.execute_cell_then(index, function(index){
|
||||
@ -37,7 +37,7 @@ casper.notebook_test(function () {
|
||||
// suffixed).
|
||||
var javascript_names = this.evaluate(function () {
|
||||
names = [];
|
||||
for (var name in IPython.widget_manager.widget_model_types) {
|
||||
for (var name in IPython.widget_manager._model_types) {
|
||||
names.push(name.replace('Model',''));
|
||||
}
|
||||
return names;
|
||||
|
@ -7,9 +7,15 @@ casper.notebook_test(function () {
|
||||
this.execute_cell_then(index);
|
||||
|
||||
var bool_index = this.append_cell(
|
||||
'bool_widget = widgets.BoolWidget(description="Title", value=True)\n' +
|
||||
'display(bool_widget)\n'+
|
||||
'display(bool_widget, view_name="ToggleButtonView")\n' +
|
||||
'bool_widgets = [widgets.BoolWidget(description="Title", value=True) for i in range(2)]\n' +
|
||||
'display(bool_widgets[0])\n' +
|
||||
'bool_widgets[1].view_name = "ToggleButtonView"\n' +
|
||||
'display(bool_widgets[1])\n' +
|
||||
'for widget in bool_widgets:\n' +
|
||||
' def handle_change(name,old,new):\n' +
|
||||
' for other_widget in bool_widgets:\n' +
|
||||
' other_widget.value = new\n' +
|
||||
' widget.on_trait_change(handle_change, "value")\n' +
|
||||
'print("Success")');
|
||||
this.execute_cell_then(bool_index, function(index){
|
||||
|
||||
@ -37,21 +43,21 @@ casper.notebook_test(function () {
|
||||
'Checkbox labeled correctly.');
|
||||
|
||||
this.test.assert(this.cell_element_exists(index,
|
||||
'.widget-area .widget-subarea div button'),
|
||||
'.widget-area .widget-subarea button'),
|
||||
'Toggle button exists.');
|
||||
|
||||
this.test.assert(this.cell_element_function(index,
|
||||
'.widget-area .widget-subarea div button', 'html')=="Title",
|
||||
'.widget-area .widget-subarea button', 'html')=="Title",
|
||||
'Toggle button labeled correctly.');
|
||||
|
||||
this.test.assert(this.cell_element_function(index,
|
||||
'.widget-area .widget-subarea div button', 'hasClass', ['active']),
|
||||
'.widget-area .widget-subarea button', 'hasClass', ['active']),
|
||||
'Toggle button is toggled.');
|
||||
|
||||
});
|
||||
|
||||
index = this.append_cell(
|
||||
'bool_widget.value = False\n' +
|
||||
'bool_widgets[0].value = False\n' +
|
||||
'print("Success")');
|
||||
this.execute_cell_then(index, function(index){
|
||||
|
||||
@ -63,18 +69,18 @@ casper.notebook_test(function () {
|
||||
'Checkbox is not checked. (1)');
|
||||
|
||||
this.test.assert(! this.cell_element_function(bool_index,
|
||||
'.widget-area .widget-subarea div button', 'hasClass', ['active']),
|
||||
'.widget-area .widget-subarea button', 'hasClass', ['active']),
|
||||
'Toggle button is not toggled. (1)');
|
||||
|
||||
// Try toggling the bool by clicking on the toggle button.
|
||||
this.cell_element_function(bool_index, '.widget-area .widget-subarea div button', 'click');
|
||||
this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
|
||||
|
||||
this.test.assert(this.cell_element_function(bool_index,
|
||||
'.widget-area .widget-subarea .widget-hbox-single input', 'prop', ['checked']),
|
||||
'Checkbox is checked. (2)');
|
||||
|
||||
this.test.assert(this.cell_element_function(bool_index,
|
||||
'.widget-area .widget-subarea div button', 'hasClass', ['active']),
|
||||
'.widget-area .widget-subarea button', 'hasClass', ['active']),
|
||||
'Toggle button is toggled. (2)');
|
||||
|
||||
// Try toggling the bool by clicking on the checkbox.
|
||||
@ -85,7 +91,7 @@ casper.notebook_test(function () {
|
||||
'Checkbox is not checked. (3)');
|
||||
|
||||
this.test.assert(! this.cell_element_function(bool_index,
|
||||
'.widget-area .widget-subarea div button', 'hasClass', ['active']),
|
||||
'.widget-area .widget-subarea button', 'hasClass', ['active']),
|
||||
'Toggle button is not toggled. (3)');
|
||||
|
||||
});
|
||||
|
@ -42,11 +42,16 @@ casper.notebook_test(function () {
|
||||
}
|
||||
|
||||
selection_index = this.append_cell(
|
||||
'selection = widgets.SelectionWidget(values=["' + selection_values + '"[i] for i in range(4)])\n' +
|
||||
'display(selection)\n' +
|
||||
'display(selection, view_name="ToggleButtonsView")\n' +
|
||||
'display(selection, view_name="RadioButtonsView")\n' +
|
||||
'display(selection, view_name="ListBoxView")\n' +
|
||||
'selection = [widgets.SelectionWidget(values=["' + selection_values + '"[i] for i in range(4)]) for j in range(4)]\n' +
|
||||
'selection[1].view_name="ToggleButtonsView"\n' +
|
||||
'selection[2].view_name="RadioButtonsView"\n' +
|
||||
'selection[3].view_name="ListBoxView"\n' +
|
||||
'[display(selection[i]) for i in range(4)]\n' +
|
||||
'for widget in selection:\n' +
|
||||
' def handle_change(name,old,new):\n' +
|
||||
' for other_widget in selection:\n' +
|
||||
' other_widget.value = new\n' +
|
||||
' widget.on_trait_change(handle_change, "value")\n' +
|
||||
'print("Success")\n');
|
||||
this.execute_cell_then(selection_index, function(index){
|
||||
this.test.assert(this.get_output_cell(index).text == 'Success\n',
|
||||
@ -73,7 +78,8 @@ casper.notebook_test(function () {
|
||||
});
|
||||
|
||||
index = this.append_cell(
|
||||
'selection.value = "a"\n' +
|
||||
'for widget in selection:\n' +
|
||||
' widget.value = "a"\n' +
|
||||
'print("Success")\n');
|
||||
this.execute_cell_then(index, function(index){
|
||||
this.test.assert(this.get_output_cell(index).text == 'Success\n',
|
||||
|
@ -7,12 +7,15 @@ casper.notebook_test(function () {
|
||||
this.execute_cell_then(index);
|
||||
|
||||
var string_index = this.append_cell(
|
||||
'string_widget = widgets.StringWidget()\n' +
|
||||
'display(string_widget)\n'+
|
||||
'display(string_widget, view_name="TextAreaView")\n' +
|
||||
'display(string_widget, view_name="HTMLView")\n' +
|
||||
'display(string_widget, view_name="LatexView")\n' +
|
||||
'string_widget.value = "xyz"\n' +
|
||||
'string_widget = [widgets.StringWidget(), widgets.StringWidget(), widgets.StringWidget(), widgets.StringWidget()]\n' +
|
||||
'string_widget[0].value = "xyz"\n' +
|
||||
'string_widget[1].view_name = "TextAreaView"\n' +
|
||||
'string_widget[1].value = "xyz"\n' +
|
||||
'string_widget[2].view_name = "HTMLView"\n' +
|
||||
'string_widget[2].value = "xyz"\n' +
|
||||
'string_widget[3].view_name = "LatexView"\n' +
|
||||
'string_widget[3].value = "$\\\\LaTeX{}$"\n' +
|
||||
'[display(widget) for widget in string_widget]\n'+
|
||||
'print("Success")');
|
||||
this.execute_cell_then(string_index, function(index){
|
||||
|
||||
@ -39,29 +42,12 @@ casper.notebook_test(function () {
|
||||
'.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val')=='xyz',
|
||||
'Python set textbox value.');
|
||||
|
||||
this.cell_element_function(index,
|
||||
'.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val', [''])
|
||||
this.sendKeys('.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'abc');
|
||||
|
||||
this.test.assert(this.cell_element_function(index,
|
||||
'.widget-area .widget-subarea .widget-hbox textarea', 'val')=='abc',
|
||||
'Textarea updated to textbox contents.');
|
||||
|
||||
this.cell_element_function(index,
|
||||
'.widget-area .widget-subarea .widget-hbox textarea', 'val', ['']);
|
||||
this.sendKeys('.widget-area .widget-subarea .widget-hbox textarea', '$\\LaTeX{}$');
|
||||
|
||||
this.test.assert(this.cell_element_function(index,
|
||||
'.widget-area .widget-subarea .widget-hbox-single input[type=text]', 'val')=='$\\LaTeX{}$',
|
||||
'Textbox updated to textarea contents.');
|
||||
});
|
||||
|
||||
this.wait(500); // Wait for change to execute in kernel
|
||||
|
||||
index = this.append_cell('print(string_widget.value)');
|
||||
this.execute_cell_then(index, function(index){
|
||||
this.test.assert(this.get_output_cell(index).text == '$\\LaTeX{}$\n',
|
||||
'Python updated with correct string widget value.');
|
||||
|
||||
this.test.assert(this.cell_element_exists(string_index,
|
||||
'.widget-area .widget-subarea div span.MathJax_Preview'),
|
||||
|
@ -34,11 +34,13 @@ from IPython.utils.py3compat import string_types
|
||||
@contextmanager
|
||||
def PropertyLock(instance, key, value):
|
||||
instance._property_lock = (key, value)
|
||||
yield
|
||||
del instance._property_lock
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del instance._property_lock
|
||||
|
||||
def should_send_property(instance, key, value):
|
||||
return not hasattr(instance, _property_lock) or \
|
||||
return not hasattr(instance, '_property_lock') or \
|
||||
key != instance._property_lock[0] or \
|
||||
value != instance._property_lock[1]
|
||||
|
||||
@ -47,8 +49,9 @@ class Widget(LoggingConfigurable):
|
||||
|
||||
# Shared declarations (Class level)
|
||||
widget_construction_callback = None
|
||||
widgets = []
|
||||
|
||||
keys = ['view_name']
|
||||
keys = ['view_name'] # TODO: Sync = True
|
||||
|
||||
def on_widget_constructed(callback):
|
||||
"""Class method, registers a callback to be called when a widget is
|
||||
@ -66,6 +69,7 @@ class Widget(LoggingConfigurable):
|
||||
# Public declarations (Instance level)
|
||||
target_name = Unicode('widget', help="""Name of the backbone model
|
||||
registered in the frontend to create and sync this widget with.""")
|
||||
# model_name
|
||||
view_name = Unicode(help="""Default view registered in the frontend
|
||||
to use to represent the widget.""")
|
||||
|
||||
@ -75,23 +79,27 @@ class Widget(LoggingConfigurable):
|
||||
def __init__(self, **kwargs):
|
||||
"""Public constructor
|
||||
"""
|
||||
self.closed = False
|
||||
self._display_callbacks = []
|
||||
self._msg_callbacks = []
|
||||
super(Widget, self).__init__(**kwargs)
|
||||
|
||||
self.on_trait_change(self._handle_property_changed, self.keys)
|
||||
Widget.widgets.append(self)
|
||||
Widget._call_widget_constructed(self)
|
||||
|
||||
def __del__(self):
|
||||
"""Object disposal"""
|
||||
self.close()
|
||||
|
||||
|
||||
def close(self):
|
||||
"""Close method. Closes the widget which closes the underlying comm.
|
||||
When the comm is closed, all of the widget views are automatically
|
||||
removed from the frontend."""
|
||||
self._close_communication()
|
||||
if not self.closed:
|
||||
self.closed = True
|
||||
self._close_communication()
|
||||
Widget.widgets.remove(self)
|
||||
|
||||
@property
|
||||
def comm(self):
|
||||
@ -109,6 +117,8 @@ class Widget(LoggingConfigurable):
|
||||
data = msg['content']['data']
|
||||
method = data['method']
|
||||
|
||||
# TODO: Log unrecog.
|
||||
|
||||
# Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
|
||||
if method == 'backbone' and 'sync_data' in data:
|
||||
sync_data = data['sync_data']
|
||||
@ -124,7 +134,7 @@ class Widget(LoggingConfigurable):
|
||||
"""Called when a state is recieved from the frontend."""
|
||||
for name in self.keys:
|
||||
if name in sync_data:
|
||||
value = sync_data[name]
|
||||
value = self._unpack_widgets(sync_data[name])
|
||||
with PropertyLock(self, name, value):
|
||||
setattr(self, name, value)
|
||||
|
||||
@ -204,22 +214,57 @@ class Widget(LoggingConfigurable):
|
||||
else:
|
||||
keys = [key]
|
||||
for k in keys:
|
||||
value = getattr(self, k)
|
||||
|
||||
# a more elegant solution to encoding Widgets would be
|
||||
# to tap into the JSON encoder and teach it how to deal
|
||||
# with Widget objects, or maybe just teach the JSON
|
||||
# encoder to look for a _repr_json property before giving
|
||||
# up encoding
|
||||
if isinstance(value, Widget):
|
||||
value = value.model_id
|
||||
elif isinstance(value, list) and len(value)>0 and isinstance(value[0], Widget):
|
||||
# assume all elements of the list are widgets
|
||||
value = [i.model_id for i in value]
|
||||
state[k] = value
|
||||
state[k] = self._pack_widgets(getattr(self, k))
|
||||
return state
|
||||
|
||||
|
||||
def _pack_widgets(self, values):
|
||||
"""This function recursively converts all widget instances to model id
|
||||
strings.
|
||||
|
||||
Children widgets will be stored and transmitted to the front-end by
|
||||
their model ids."""
|
||||
if isinstance(values, dict):
|
||||
new_dict = {}
|
||||
for key in values.keys():
|
||||
new_dict[key] = self._pack_widgets(values[key])
|
||||
return new_dict
|
||||
elif isinstance(values, list):
|
||||
new_list = []
|
||||
for value in values:
|
||||
new_list.append(self._pack_widgets(value))
|
||||
return new_list
|
||||
elif isinstance(values, Widget):
|
||||
return values.model_id
|
||||
else:
|
||||
return values
|
||||
|
||||
|
||||
def _unpack_widgets(self, values):
|
||||
"""This function recursively converts all model id strings to widget
|
||||
instances.
|
||||
|
||||
Children widgets will be stored and transmitted to the front-end by
|
||||
their model ids."""
|
||||
if isinstance(values, dict):
|
||||
new_dict = {}
|
||||
for key in values.keys():
|
||||
new_dict[key] = self._unpack_widgets(values[key])
|
||||
return new_dict
|
||||
elif isinstance(values, list):
|
||||
new_list = []
|
||||
for value in values:
|
||||
new_list.append(self._unpack_widgets(value))
|
||||
return new_list
|
||||
elif isinstance(values, string_types):
|
||||
for widget in Widget.widgets:
|
||||
if widget.model_id == values:
|
||||
return widget
|
||||
return values
|
||||
else:
|
||||
return values
|
||||
|
||||
|
||||
def send(self, content):
|
||||
"""Sends a custom msg to the widget model in the front-end.
|
||||
|
||||
@ -232,8 +277,9 @@ class Widget(LoggingConfigurable):
|
||||
"custom_content": content})
|
||||
|
||||
|
||||
def on_msg(self, callback, remove=False):
|
||||
"""Register a callback for when a custom msg is recieved from the front-end
|
||||
def on_msg(self, callback, remove=False): # TODO: Use lambdas and inspect here
|
||||
"""Register or unregister a callback for when a custom msg is recieved
|
||||
from the front-end.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -250,7 +296,8 @@ class Widget(LoggingConfigurable):
|
||||
|
||||
|
||||
def on_displayed(self, callback, remove=False):
|
||||
"""Register a callback to be called when the widget has been displayed
|
||||
"""Register or unregister a callback to be called when the widget has
|
||||
been displayed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -282,10 +329,9 @@ class Widget(LoggingConfigurable):
|
||||
def _open_communication(self):
|
||||
"""Opens a communication with the front-end."""
|
||||
# Create a comm.
|
||||
if self._comm is None:
|
||||
self._comm = Comm(target_name=self.target_name)
|
||||
self._comm.on_msg(self._handle_msg)
|
||||
self._comm.on_close(self._close_communication)
|
||||
self._comm = Comm(target_name=self.target_name)
|
||||
self._comm.on_msg(self._handle_msg)
|
||||
self._comm.on_close(self._close_communication)
|
||||
|
||||
# first update
|
||||
self.send_state()
|
||||
@ -295,7 +341,7 @@ class Widget(LoggingConfigurable):
|
||||
"""Closes a communication with the front-end."""
|
||||
if self._comm is not None:
|
||||
try:
|
||||
self._comm.close()
|
||||
self._comm.close() # TODO: Check
|
||||
finally:
|
||||
self._comm = None
|
||||
|
||||
@ -361,7 +407,7 @@ class DOMWidget(Widget):
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], dict):
|
||||
for (key, value) in args[0].items():
|
||||
if not (key in self._css[selector] and value in self._css[selector][key]):
|
||||
if not (key in self._css[selector] and value == self._css[selector][key]):
|
||||
self._css[selector][key] = value
|
||||
self.send_state('_css')
|
||||
else:
|
||||
@ -379,7 +425,7 @@ class DOMWidget(Widget):
|
||||
# Only update the property if it has changed.
|
||||
key = args[0]
|
||||
value = args[1]
|
||||
if not (key in self._css[selector] and value in self._css[selector][key]):
|
||||
if not (key in self._css[selector] and value == self._css[selector][key]):
|
||||
self._css[selector][key] = value
|
||||
self.send_state('_css') # Send new state to client.
|
||||
else:
|
||||
@ -398,7 +444,7 @@ class DOMWidget(Widget):
|
||||
be added to.
|
||||
"""
|
||||
class_list = class_names
|
||||
if isinstance(list, class_list):
|
||||
if isinstance(class_list, list):
|
||||
class_list = ' '.join(class_list)
|
||||
|
||||
self.send({"msg_type": "add_class",
|
||||
@ -418,7 +464,7 @@ class DOMWidget(Widget):
|
||||
be removed from.
|
||||
"""
|
||||
class_list = class_names
|
||||
if isinstance(list, class_list):
|
||||
if isinstance(class_list, list):
|
||||
class_list = ' '.join(class_list)
|
||||
|
||||
self.send({"msg_type": "remove_class",
|
||||
|
Loading…
Reference in New Issue
Block a user