Skip to content

Instantly share code, notes, and snippets.

@pbugnion
Last active April 23, 2020 23:00
Show Gist options
  • Save pbugnion/63cf43b41ec0eed2d0b7e7426d1c67d2 to your computer and use it in GitHub Desktop.
Save pbugnion/63cf43b41ec0eed2d0b7e7426d1c67d2 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@Ricyteach
Copy link

Ricyteach commented Jun 11, 2019

Thanks so much for this- it has been driving me crazy for days. A few questions:

  1. I tried this same fix after discovering the unpack_models function yesterday, but it was broken because I was missing the first empty Object argument to widgets.DOMWidgetModel.extend(). Why is that argument needed? What is it doing?

  2. Since Test is a DOMWidget, shouldn't it be widgets.DOMWidgetModel.serializers and not widgets.WidgetModel.serializers? Or does that make no difference?

Additionally: do you know if this part of the ipywidgets API documented anywhere other than in the code? Seems like it should be added to the low level widget tutorial... a search for unpack_models turns up no hits, currently.

@pbugnion
Copy link
Author

pbugnion commented Jun 11, 2019

I tried this same fix after discovering the unpack_models function yesterday, but it was broken because I was missing the first empty Object argument to widgets.DOMWidgetModel.extend(). Why is that argument needed? What is it doing?

The first argument extends the class prototype, the second argument extends static properties of the class. For instance, in ipyleaflet:

var LeafletLayerModel = widgets.WidgetModel.extend({
    defaults: _.extend({}, widgets.WidgetModel.prototype.defaults, {
        _view_name: 'LeafletLayerView',
        _model_name: 'LeafletLayerModel',
        _view_module: 'jupyter-leaflet',
        _model_module: 'jupyter-leaflet',
        opacity: 1.0,
        bottom: false,
        options: [],
        name: '',
        base: false,
        popup: null,
        popup_min_width: 50,
        popup_max_width: 300,
        popup_max_height: null,
    })
}, {
    serializers: _.extend({
        popup: { deserialize: widgets.unpack_models }
    }, widgets.WidgetModel.serializers)
});

... instances of LeafletLayerModel will have the defaults() method as specified here. The class LeafletLayerModel will have the serializers static attribute.

The way this would be translated into 'modern' Javascript is:

class LeafletLayerModel extends widgets.WidgetModel {
  defaults() {
    return {
        ...super.defaults(),
        _view_name: 'LeafletLayerView',
        _model_name: 'LeafletLayerModel',
        /* other attributes omitted for brevity */
    }
  }

  static serializers = {
    ...widgets.DOMWidgetModel.serializers,
        popup: {deserialize: widgets.unpack_models},
  }
}

The peculiar syntax with the two arguments to widgets.DOMWidgetModel.extend is a quirk of Backbone classes. See the API reference: Backbone.Model.extend(properties, [classProperties]) .

Since Test is a DOMWidget, shouldn't it be widgets.DOMWidgetModel.serializers and not widgets.WidgetModel.serializers? Or does that make no difference?

Yes, it definitely should -- I'll update the notebook.

According to the source, by inheriting from the wrong class, I'm stopping the user from passing a layout and style traitlet from the kernel.

@pbugnion
Copy link
Author

Additionally: do you know if this part of the ipywidgets API documented anywhere other than in the code? Seems like it should be added to the low level widget tutorial... a search for unpack_models turns up no hits, currently.

Yes, it definitely should! The interface for creating custom widgets is very under-documented. If I have time, I'd love to work on that eventually.

@Ricyteach
Copy link

Excellent, very clear-- thank you. Will that "modern" version run in most browsers?

I'm a very skilled Python guy, but I have written very little javascript, and what little I know is mostly the old syntax, I have never written, for example, typescript or used the newer ECMA 5, 6, 7, 8, 9 stuff (I assume that is what you have written there?).

I might try submitting a pull request to update the docs with this small tidbit. If I can feel confident enough I know what I am doing now (I believe I do).

@pbugnion
Copy link
Author

pbugnion commented Jun 12, 2019

Will that "modern" version run in most browsers?

No -- to get sufficient browser support, it needs to be transpiled to 'old' [*] JavaScript using a tool like Babel or typescript, and bundled using something like webpack.

A good heuristic is:

  • if you have a very simple frontend, then keeping it in 'old' JS is probably safest. If you have no experience of Babel and webpack, I'd expect it to take you a day or two just to get something working (based on my personal experience as a Python developer who had to learn JS that way).
  • if you are going to develop a rich frontend library, then the investment is worth it. 'New' transpiled JS is just nicer to write.

[*] here, 'old' JavaScript means JavaScript that can be executed by all the browsers you want to support. It's useful to note that this isn't well-defined: the latest version of Chrome will accept lots of modern syntax, while IE 11 won't. For instance, pasting the following in my chrome console works just fine:

class Hello {
    static prop = {
        a: 22
    }
}

Hello.prop
# {a: 22}

I might try submitting a pull request to update the docs with this small tidbit

Yes! That would be great! Jupyter widgets are purely maintained by the community -- there is no commercial company behind them, so we're very heavily reliant on contributions like these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment