Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPEP 23: Backbone.js Widgets #4374

Merged
merged 474 commits into from
Jan 28, 2014
Merged

IPEP 23: Backbone.js Widgets #4374

merged 474 commits into from
Jan 28, 2014

Conversation

jdfreder
Copy link
Member

Description

Adds backbone.js layer on top of comm layer to enable the creation of modular widgets.
See IPEP 23 for a detailed description.

Organization

  • Widget manager /ipython/IPython/html/static/js/widgetmanager.js
  • Javascript models and associated views /ipython/IPython/html/static/js/widgets/widget*.js
  • Python widgets /ipython/IPython/html/widgets/widget*.py
  • Unit tests /ipython/IPython/html/tests/casperjs/test_cases/widgets*.js
  • Example and tutorial notebooks /ipython/examples/widgets/*.ipynb

Examples

Examples and tutorials are included in this PR's contents (as seen above) direct link

Included Widgets

  • CheckboxWidget
  • ToggleButtonWidget
  • ButtonWidget
  • ContainerWidget
  • PopupWidget
  • FloatSliderWidget
  • BoundedFloatTextWidget
  • FloatProgressWidget
  • FloatTextWidget
  • ImageWidget
  • IntSliderWidget
  • BoundedIntTextWidget
  • IntProgressWidget
  • IntTextWidget
  • AccordionWidget
  • TabWidget
  • ToggleButtonsWidget
  • RadioButtonsWidget
  • DropdownWidget
  • SelectWidget
  • HTMLWidget
  • LatexWidget
  • TextareaWidget
  • TextWidget

@jdfreder
Copy link
Member Author

Uploaded some widgets to play with,
also updated example gist.

@jdfreder
Copy link
Member Author

PR description updated with some small examples and an index of the widget/view pairs was added.

@jdfreder
Copy link
Member Author

Made changes discussed in meeting and added ToggleButtonView for the SelectionWidget.

@jdfreder
Copy link
Member Author

NOTE: JS test will not pass until jupyter/ipython-components#10 is merged

@jdfreder
Copy link
Member Author

jdfreder commented Nov 1, 2013

Continuing a discussion from HipChat,

@cekees This PR provides an MVC-like architecture on top of the comm stuff. It's useful for widgets that can be expressed via a list of properties. This PR synchronizes a python traitlet model in the backend with a backbone model in the front. Some of the things it automatically handles are

  • delta compression (only sending the state information that has changed)
  • wiring the message callbacks to the correct cells automatically
  • inter-view synchronization (handled by backbone)
  • message throttling (to avoid flooding the kernel)
  • parent/child relationships between views (which one can override to specify custom parent/child relationships)
  • ability to manipulate the widget view's DOM from python using CSS, $().addClass, and $().removeClass methods

That being said, if you're interested in lower level control (for example, implementing a renderer that streams images to the browser using webm compression or something similar), you'll want to use comms to implement your widgets.

EDIT 11/20/2013: Support for custom messages was added a couple of days ago.

@jasongrout
Copy link
Member

Sorry; I haven't had time to look at the code in this pull request much, but I'm curious: how much does this depend on the architecture of the notebook? I'd like to see about adapting this to the Sage cell server, but we don't have the same concept of 'cells' as in the notebook. It seems like (in principle) this could be more general than depending on details of the IPython notebook front end.

If your answer is: "Read the code, tell us what you think", that's fine and understandable. I apologize for not having time (yet) to go through this carefully.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 3, 2013

Hi @jasongrout ,

I think I don't fully understand the context of where you want to implement this code. If you are planning on using it within a modified IPython environment where only one cell exists, that should work great (as long as the comm stuff is available). If you are planning on using it elsewhere, the coupling with IPython isn't horrible, but it will require a little bit of work. As of now it depends on

  • IPython traitlets
  • IPython Comm & CommManager
  • IPython.OutputArea (required to map outputs with a comm message)
  • JQuery, Require.js, bootstrap, underscore.js, and backbone.js

EDIT 11/4/13: Updated with new details

@jdfreder
Copy link
Member Author

jdfreder commented Nov 3, 2013

With regards to the msg_id mapping, see this JS function https://github.com/ipython/ipython/pull/4374/files#diff-309e41632e4bd9e130ed388abdd89575R294 . This is fairly recent code and not the only place where the mapping is (also the function that makes the comm callback dict). I think I'm going to try to do a better job of reducing the coupling with cell_index.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 4, 2013

@jasongrout I just made changes that decoupled the cell_index from the mapping code. Now everything that you would need to modify for custom mapping to use the code outside of IPython is contained in the _get_output_area(msg_id) and _get_widget_area_element(output_area, show) functions. 😄

EDIT: 11/4/13: Added _get_widget_area_element(...) to post

@jdfreder
Copy link
Member Author

jdfreder commented Nov 4, 2013

^ That last commit seems to have broken something... Looking

@jasongrout
Copy link
Member

Thanks. Encapsulating all the IPython notebook-specific things, like what the output DOM looks like, into a function or two that I could override is what I was hoping for.

If I understand things correctly, there is only one widget area per cell? That seems kind of limiting. I'd love to print some text, have a widget, print some more text, have another widget, etc. Is there any way to embed widgets in the normal output flow? I mean, I can embed images, audio, and all sorts of other things in the normal output region of a cell, inline with the other output---why not widgets too?

@jdfreder
Copy link
Member Author

jdfreder commented Nov 4, 2013

That seems kind of limiting.

I totally agree with you. Ideally you would be able to place widgets wherever you want. This is one of the design questions that we talked about in one of the first dev meetings regarding this subject. @ellisonbg and I re-discussed the issue when I started implementing this.

The problem is that we don't want the widgets to be cleared along with the output. In other words, we want the user to be able to make a widget and hookup the value change event to a function that clears the output and plots. If the output clear event cleared the entire output with the widgets included, the widget on the front end and corresponding com would get closed. Then the user would have to re-show the widget with the output. Even if the clear output selectively cleared the output, the placement of the widgets would be ruined.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 4, 2013

@jasongrout I fixed the bug, and updated my comments above with the changes.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 4, 2013

@jasongrout Also with the new changes, you could implement multiple widget areas by just overriding _get_widget_area_element so that it created new widget areas on the fly within the output_area. The problems I mentioned above would still exist- You would still have to override/fix clear_output so it didn't clear widget areas.

@jasongrout
Copy link
Member

Sorry I haven't kept up on the design discussions. In the case you mentioned, couldn't the widget just be hooked up to an "output widget" containing the plot, and send the clear message just to that output widget?

We've done similar things for Sage interacts, and it seems to work well (nested interacts, for example, like http://sagecell.sagemath.org/?q=zkjyys -- change the numbers of rows and columns, and the inner interact updates accordingly). It nicely encapsulates the effects of a widget too. In fact, I suppose you could implement the standard output of a cell as an "output" widget itself, and have turtles all the way down.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 5, 2013

In the case you mentioned, couldn't the widget just be hooked up to an "output widget" containing the plot, and send the clear message just to that output widget?

It could, but in that case why bother with interweaving the output area and widgets at all? The existing framework can behave like that already. There is already a LabelView (which lets you display arbitrary HTML in the widget area). The only thing that is missing is an ImageWidget or PlotWidget. Maybe that's something that should be included by default?

I think someone else is working on porting the matplot image data over a comm (unfortunately I don't know where that is being done). An image widget was on my todo list at one point, but @ellisonbg and I removed it.

@jasongrout
Copy link
Member

That was me working on porting matplotlib images over to the comm framework :). I had to leave it for the time being, but hope to pick it back up before or during the holiday season.

There's also a generic "output widget" missing, right? Suppose I want to execute a user function every time a button is clicked, and put whatever output the user did in that function in a box below the button (this is similar to how Sage interacts work right now).

But back to the other discussion---I'm just seeing if there is a way to combine the widget and general output regions. The point is not whether I can interleave widgets and output, but whether that should be the default. In the use-case you mentioned above, to me it seems more elegant to make the plot that would be cleared a specific region in the output, like this generic "output widget", that could be cleared independent of the rest of the output. Also, how would I actually clear/destroy the widget area?

@jdfreder
Copy link
Member Author

jdfreder commented Nov 5, 2013

That was me working on porting matplotlib images over to the comm framework :)

Awesome! Yeah I can't wait till that works. I think that would be much better than having a plot or image widget that tries to transmit all of that data through state.

There's also a generic "output widget" missing, right? Suppose I want to execute a user function every time a button is clicked, and put whatever output the user did in that function in a box below the button (this is similar to how Sage interacts work right now).

Maybe, I think redirecting output from the standard output area to a widget is not possible with the current callback system. I believe that would require the msg callbacks stored in the front-end (that direct output to the correct cell) to be modified in the middle of a cell execution in the back-end. I don't think there is a message that allows this to be done. @minrk would definitely know how to do this and if it could be done.

In the use-case you mentioned above, to me it seems more elegant to make the plot that would be cleared a specific region in the output

It may be more elegant, but I don't think it would be the simplest solution. Right now a user can create a widget and register a callback for it's value change. In that callback the user clears the output and draws a plot, just like he/she normally would for simple animations. The benefit is that the output area is predictable and behaves like it does without widgets.

Also, just to make sure we are on the same page, these widgets are not a form of interact. Interact can and will be built on top of these widgets, which is what @ellisonbg has shown and is still working on.

@cekees
Copy link

cekees commented Nov 5, 2013

I've running this branch without any problems on my local installation, even doing a little bit of UI development for some computational models using the IPython widgets, but I can't seem to get them to work when I run ipython notebook from a sage math cloud instances. No errors, but also no widgets. @jasongrout have you tried these on SMC? Perhaps interestingly, @minrk's low level comm example with the range widget works perfectly. The IPython commit ID is 2cc3475.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 5, 2013

@cekees is it because jupyter/ipython-components#10 isn't checked out on the sage server too?

@jdfreder
Copy link
Member Author

jdfreder commented Nov 5, 2013

The JavaScript console in your web browser should mention missing backbone.js and underscore.js

@cekees
Copy link

cekees commented Nov 5, 2013

ipython:
commit 2cc3475
Author: Jonathan Frederic jdfreder@calpoly.edu
Date: Mon Nov 4 19:16:34 2013 +0000

Fixed comment in widget.js

ipython/IPython/html/static/components:
commit 041a80ccbc2073eee9a2dceddf2cfcf9d9fa87a0
Author: Jonathan Frederic jdfreder@calpoly.edu
Date: Fri Oct 11 22:53:47 2013 +0000

Add backbone and underscore.

and:
~/ipython/IPython/html/static/components$ ls
backbone bower.json fabfile.py highlight.js jquery-ui marked
README.md underscore
bootstrap codemirror font-awesome jquery less.js
nonbower.json requirejs

But I still get the following in my js console:

  1. GET
    https://cloud.sagemath.com/static/notebook/js/widgets/int_range.js?_=1383690233765404
    (Not Found)
    jquery.min.js:6https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/components/jquery/jquery.min.js
    1. x.support.cors.e.crossDomain.sendjquery.min.js:6https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/components/jquery/jquery.min.js
    2. x.extend.ajaxjquery.min.js:6https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/components/jquery/jquery.min.js
    3. x.(anonymous
      function)jquery.min.js:6https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/components/jquery/jquery.min.js
    4. x.extend.getScriptjquery.min.js:6https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/components/jquery/jquery.min.js
    5. (anonymous function)
    6. OutputArea.append_javascriptoutputarea.js:478https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/notebook/js/outputarea.js
    7. OutputArea.append_mime_typeoutputarea.js:448https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/notebook/js/outputarea.js
    8. OutputArea.append_display_dataoutputarea.js:427https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/notebook/js/outputarea.js
    9. OutputArea.append_outputoutputarea.js:305https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/notebook/js/outputarea.js
    10. OutputArea.handle_outputoutputarea.js:254https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/notebook/js/outputarea.js
    11. x.isFunction.ijquery.min.js:4https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/components/jquery/jquery.min.js
    12. Kernel._handle_output_messagekernel.js:544https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/services/kernels/js/kernel.js
    13. x.isFunction.ijquery.min.js:4https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/components/jquery/jquery.min.js
    14. Kernel._handle_iopub_messagekernel.js:555https://cloud.sagemath.com/6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/static/services/kernels/js/kernel.js
    15. x.isFunction.i

On Tue, Nov 5, 2013 at 4:10 PM, Jonathan Frederic
notifications@github.comwrote:

The JavaScript console in your web browser should mention missing
backbone.js and underscore.js


Reply to this email directly or view it on GitHubhttps://github.com//pull/4374#issuecomment-27818012
.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 5, 2013

Ah! It looks like the path to the widget stuff is missing the /6dbbd03e-0125-46e8-acfd-335888c9464b/port/25043/ portion of the URL. This probably is because I load the files from an absolute path, I'll try changing that so it's relative. I think I just need to change where the widget js is loaded from using display. The require.js imports should work. I'll post back in a second with a fix attempt...

@jdfreder
Copy link
Member Author

jdfreder commented Nov 5, 2013

@cekees can you try the last commit? c6936ae

@cekees
Copy link

cekees commented Nov 5, 2013

Works!

On Tue, Nov 5, 2013 at 4:40 PM, Jonathan Frederic
notifications@github.comwrote:

@cekees https://github.com/cekees can you try the last commit?


Reply to this email directly or view it on GitHubhttps://github.com//pull/4374#issuecomment-27820872
.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 6, 2013

@cekees Could you do me a favor and pull the latest and test again when you get a chance (making sure to clear the browser cache)? I changed the JavaScript references yet again, but I am using baseProjectUrl now.

@jdfreder
Copy link
Member Author

jdfreder commented Nov 6, 2013

I uploaded the basic widget tutorial notebooks, parts 1-5. They are listed in the PR description and actually stored in this PR (ipython/examples/widgets).

@cekees
Copy link

cekees commented Nov 6, 2013

Works for me, except Part 5 in the PR is giving me a 404 and on Part 4: Styles, In[5] I get the following error (there are also errors down further in the worksheet that may or may not be related.


TypeError Traceback (most recent call last)
in ()
6 # set_css used to set multiple CSS attributes.
7 container.set_css({'padding': '6px', # Add padding to the container
----> 8 'background': 'yellow'}) # Fill the container yellow
9
10 label = widgets.StringWidget(default_view_name="LabelView", parent=container)

TypeError: set_css() takes at least 3 arguments (2 given)

@jdfreder
Copy link
Member Author

jdfreder commented Nov 6, 2013

TypeError: set_css() takes at least 3 arguments (2 given)

Looks like you don't have the latest widgets.py? Commit 42c6fc8 changes set_css so it can take between 1-3 arguments.

@jdfreder
Copy link
Member Author

I think we should remove the inspection and only support one callback signature in each case

I think you are right. I merged your review PR, thanks for all the good input! Question: I noticed that, in the example notebooks, you returned after each period in the markdown cells. Is that something I should practice too?

@minrk
Copy link
Member

minrk commented Jan 28, 2014

Is that something I should practice too?

No, don't worry about it, it's just my habit.

@jdfreder
Copy link
Member Author

@minrk and @ellisonbg this should be ready to merge after d661486 pleases our good friend Travis C. I.

@ellisonbg
Copy link
Member

Merging. Great work @jdfreder !!! and thanks everyone for the review help, especially @minrk and @jasongrout @cekees

ellisonbg added a commit that referenced this pull request Jan 28, 2014
IPEP 23: Backbone.js Widgets
@ellisonbg ellisonbg merged commit 7eac82e into ipython:master Jan 28, 2014
@jasongrout
Copy link
Member

Awesome! Congrats, guys!

// elsewhere.
define(["underscore",
"backbone",
], function (Underscore, Backbone) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be function(_, Backbone), since we are actually using underscore via _ below. This might also be a problem in other files.

@jasongrout
Copy link
Member

Also, since we changed view_name to be private, we probably also ought to change model_name to be private (i.e., _model_name or something---I even prefer the shorter _model and _view)

closed = Bool(False)

keys = List()
def _keys_default(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So just to double-check this design decision---widget.keys is still the authoritative source for the syncing attributes, and setting the sync=True parameter just provides a convenient default? But we could just set widget.keys directly to change this behavior? (I had thought we were totally moving to the sync=True framework, but I do like the flexibility of manually overriding that architecture if I need to.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They keys attribute should always be constructed automatically by looking at the sync trait metadata. The problem with allowing the setting of keys manually is that it messes up subclassing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, should keys be a property, rather than a traitlet?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still set it manually, any classes that subclass that class won't be able to rely on sync = True.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that breaking subclassing is always wrong, so we should make it a read only property? Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minrk are you okay with using a readonly property for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, property is fine. Just make sure it's not evaluated on every request. I don't actually see any benefit to property, since there is really no such thing as read-only in Python.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually see any benefit to property, since there is really no such thing as read-only in Python.

I agree. I'm indifferent to the property or traitlet approach.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a traitlet implies that it makes sense to set it - a property suggests that you shouldn't try that, even though it's still technically possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you have a readonly traitlet? Basically, the validator just rejects any attempts to set it?

@jdfreder
Copy link
Member Author

Thanks for pointing the naming issues out @jasongrout , I opened a PR to resolve them - #4933

@ChakriCherukuri
Copy link

Hi,
I just got the latest code from master and I am trying to create my custom widgets. One thing I noticed is that List() traitlet is no longer being mapped to an array on JS side. It is now mapped
into an object something like {0: v1, 1: v2, 2: v3, ...} as opposed to an array like [v1, v2, v3, ...]. It would be great if someone can look into this issue.

Thanks a lot,
Chakri

PS: I am a first time poster so please pardon my lack of sophistication in my comments. I just started playing with GIT, IPython widgets, Backbone etc.

@jasongrout
Copy link
Member

It looks like the javascript unpacker should be checking to see if the value is an instanceof Array before checking to see if it is an object here: https://github.com/ipython/ipython/blob/master/IPython/html/static/notebook/js/widgets/widget.js#L225 and

@jasongrout
Copy link
Member

(perhaps using jquery isArray is safer than trying to deal with the javascript type system in lots of different browsers: http://api.jquery.com/jquery.isarray/)

@jdfreder
Copy link
Member Author

Thanks for pointing this out guys, I will be gone tomorrow. I'll try to open a PR to fix this over the weekend.

@jasongrout
Copy link
Member

@jdfreder: not to get impatient, but I'm curious if there is progress on this. I know you guys have been doing a ton of work lately, and we really appreciate it!

@ellisonbg
Copy link
Member

Jason, can you open a new issue for us to track this one on? Thanks.

@jasongrout
Copy link
Member

Done: #5020

@jdfreder jdfreder deleted the widget-msg branch March 10, 2014 18:44
mattvonrocketstein pushed a commit to mattvonrocketstein/ipython that referenced this pull request Nov 3, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants