Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bluesmoon/59f1046fc6f2669dc80a to your computer and use it in GitHub Desktop.
Save bluesmoon/59f1046fc6f2669dc80a to your computer and use it in GitHub Desktop.
Creating custom IJulia widgets
Display the source blob
Display the rendered blob
Raw
{"nbformat_minor": 0, "cells": [{"source": "# Creating Custom IJulia Widgest - a Minimal Example", "cell_type": "markdown", "metadata": {}}, {"source": "The objective of this notebook it to show how we can build custom interactive widgets for an IJulia notebook. We will keep the example simple. Our goal will be to present the user with a button and then set up communication between the button and our Julia code. In particular, when the user clicks the button, we will show her the symbols that are currently defined in the Julia module ```Main```.\n\nUltimately, to build an interactive widget, we will need a communication loop that looks like this\n\n User => JavaScript => Julia => JavaScript => User\n\nWe will build up this loop step by step.\n\nTo build a widget we need some basic HTML and JavaScript. There are many free resources for learning about these. I found [Eloquent Javascript](http://eloquentjavascript.net/) particularly useful.", "cell_type": "markdown", "metadata": {}}, {"source": "## User => JavaScript => User, the User Interface", "cell_type": "markdown", "metadata": {}}, {"source": "The first thing we will need is a way to interface with the user, we do this by outputting HTML that will be rendered and diplayed to the user.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 1, "cell_type": "code", "source": "type OurWidget\nend\n\nimport Base: writemime\n\nfunction writemime(io, ::MIME\"text/html\", widget::OurWidget)\n widget_html = \"\"\"\n <button> I don't do anything yet </button>\n <div> This is where the output will go </div>\n \"\"\"\n write(io, widget_html)\nend;", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 2, "cell_type": "code", "source": "OurWidget()", "outputs": [{"execution_count": 2, "output_type": "execute_result", "data": {"text/plain": "OurWidget()", "text/html": " <button> I don't do anything yet </button>\n <div> This is where the output will go </div>\n"}, "metadata": {}}], "metadata": {"scrolled": true, "collapsed": false, "trusted": true}}, {"source": "Ok, so we now the user can communicate using the button, but there is not communication going back to the user. To make the button do something we will add some JavaScript code to the HTML we output.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 3, "cell_type": "code", "source": "function writemime(io, ::MIME\"text/html\", widget::OurWidget)\n widget_html = \"\"\"\n <button id=\"button1\"> Now I do something </button>\n <div id=\"output-area1\"> This is where the output will go </div>\n <script>\n \\$('#button1').click( function() {\n \\$('#output-area1').text(\"Someone pressed the button!\")\n });\n </script>\"\"\"\n write(io, widget_html)\nend;", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 4, "cell_type": "code", "source": "OurWidget()", "outputs": [{"execution_count": 4, "output_type": "execute_result", "data": {"text/plain": "OurWidget()", "text/html": "<button id=\"button1\"> Now I do something </button>\n<div id=\"output-area1\"> This is where the output will go </div>\n<script>\n $('#button1').click( function() {\n $('#output-area1').text(\"Someone pressed the button!\")\n });\n</script>"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "We now have two way communication between the user and the button, but not with the Julia backend. In the next couple of sections we will explore how to set up communication between JavaScript and Julia using the ```IJulia.comm_manager``` module.", "cell_type": "markdown", "metadata": {}}, {"source": "## JavaScript => Julia => JavaScript, the IJulia ```comm_manager```\n\nThe [```IJulia.CommManager```](https://github.com/JuliaLang/IJulia.jl/blob/master/src/comm_manager.jl) module implements the IPython messaging protocol:\n\nhttps://github.com/ipython/ipython/wiki/IPEP-21:-Widget-Messages\n\nThis protocol specifies how messages should be passed to and from a backend (Julia in our case) and frontend (JavaScript in our case).", "cell_type": "markdown", "metadata": {}}, {"source": "### JavaScript => Julia", "cell_type": "markdown", "metadata": {}}, {"source": "To open communication between JavaScript and Julia, we construct a ```Comm``` object from the ```IJulia.CommManager``` module. Then, we associate a function with this object by setting its ```on_msg``` field to be the function we want to be called when a message is received from the JavaScript frontend. ", "cell_type": "markdown", "metadata": {}}, {"source": "First though, we set up some JavaScript to send a message to Julia when our button gets clicked. We use the ```display``` function to run our JavaScript for us.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 5, "cell_type": "code", "source": "setup_html = \"\"\"\n<script>\n var comm_manager = IPython.notebook.kernel.comm_manager;\n comm_manager.register_target(\n \"OurWidget\", \n function(comm){\n \\$('#button2')\n .click( function () {\n comm.send({\"hello\": \"from JavaScript \"});\n });\n } \n );\n</script>\"\"\"\ndisplay(\"text/html\", setup_html)", "outputs": [{"output_type": "display_data", "data": {"text/html": "<script>\n var comm_manager = IPython.notebook.kernel.comm_manager;\n comm_manager.register_target(\n \"OurWidget\", \n function(comm){\n $('#button2')\n .click( function () {\n comm.send({\"hello\": \"from JavaScript \"});\n });\n } \n );\n</script>"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 6, "cell_type": "code", "source": "using IJulia.CommManager\n\nfunction writemime(io, ::MIME\"text/html\", widget::OurWidget) \n widget_html = \"\"\"\n <button id=\"button2\"> Click me to send a message to Julia </button>\n <div id=\"output-area2\"> I'm just some text </div>\n \"\"\"\n\n write(io, widget_html)\n \n comm = Comm(:OurWidget);\n\n comm.on_msg = msg -> println(IJulia.orig_STDOUT,\n \"just received a message on the Julia side: \\n\",\n msg.content[\"data\"]\n );\nend;", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 7, "cell_type": "code", "source": "OurWidget()", "outputs": [{"execution_count": 7, "output_type": "execute_result", "data": {"text/plain": "OurWidget()", "text/html": "<button id=\"button2\"> Click me to send a message to Julia </button>\n<div id=\"output-area2\"> I'm just some text </div>\n"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "When you click the button output from the previous cell, it should now cause a message to print in the ***terminal*** where IJulia was launched from.", "cell_type": "markdown", "metadata": {}}, {"source": "### Julia => JavaScript", "cell_type": "markdown", "metadata": {}}, {"source": "The missing piece is communication from Julia to JavaScript. We set this up in a similar way to the ```JavaScript => Julia``` communication. We set up a function to be called on the JavaScript side whenever communication is received from the Julia side.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 8, "cell_type": "code", "source": "setup_html = \"\"\"\n<script>\n var comm_manager = IPython.notebook.kernel.comm_manager;\n comm_manager.register_target(\n \"OurWidget\",\n function (comm) {\n \\$('#button3')\n .click( function () {\n comm.send({\"request\": \"names\"});\n });\n\n comm.on_msg(function (msg) {\n var names = msg.content.data[\"names\"];\n var names_string = names.reduce(\n function(s1, s2) { \n return s1 + \", \" + s2;\n });\n\n \\$('#output-area3').text(\n \"The symbols defined in module Main in Julia are: \\\\n\" + \n names_string\n );\n }); \n }\n );\n</script>\"\"\"\ndisplay(\"text/html\", setup_html)", "outputs": [{"output_type": "display_data", "data": {"text/html": "<script>\n var comm_manager = IPython.notebook.kernel.comm_manager;\n comm_manager.register_target(\n \"OurWidget\",\n function (comm) {\n $('#button3')\n .click( function () {\n comm.send({\"request\": \"names\"});\n });\n\n comm.on_msg(function (msg) {\n var names = msg.content.data[\"names\"];\n var names_string = names.reduce(\n function(s1, s2) { \n return s1 + \", \" + s2;\n });\n\n $('#output-area3').text(\n \"The symbols defined in module Main in Julia are: \\n\" + \n names_string\n );\n }); \n }\n );\n</script>"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 9, "cell_type": "code", "source": "using IJulia.CommManager\n\nfunction writemime(io, ::MIME\"text/html\", widget::OurWidget)\n widget_html = \"\"\"\n <button id=\"button3\"> \n Show symbols defined in module Main in Julia\n </button>\n <div id=\"output-area3\"> \n This is where the output will go \n </div>\n\n \"\"\"\n\n write(io, widget_html)\n \n comm = Comm(:OurWidget)\n\n comm.on_msg = function(msg)\n if msg.content[\"data\"][\"request\"] == \"names\"\n response = Dict{Any, Any}(\"names\" => names(Main))\n # In Julia 0.3.0 I needed to replace the above with:\n # response = {\"names\" => names(Main)}\n send_comm(comm, response)\n end\n end\nend;", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 10, "cell_type": "code", "source": "OurWidget()", "outputs": [{"execution_count": 10, "output_type": "execute_result", "data": {"text/plain": "OurWidget()", "text/html": "<button id=\"button3\"> \n Show symbols defined in module Main in Julia\n</button>\n<div id=\"output-area3\"> \n This is where the output will go \n</div>\n\n"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Conclusion", "cell_type": "markdown", "metadata": {}}, {"source": "We have now completed the loop of communication \n\nUser => JavaScript => Julia => JavaScript => User\n\nTo recap, we can create an interactive widget by\n\n1. adding JavaScript code to set the behaviour of a widget when it is created\n2. adding JavaScript code to communicate with Julia\n3. outputing an HTML representation of the widget for the user\n4. adding Julia code to communicate back to the JavaScript frontend.\n\nA great way I found to learn more about interactive widgets in IJulia, is by reading the code for the [Interact.jl](https://github.com/JuliaLang/Interact.jl) package.", "cell_type": "markdown", "metadata": {}}], "nbformat": 4, "metadata": {"kernelspec": {"display_name": "Julia 0.4.0-dev", "name": "julia 0.4", "language": "julia"}, "language_info": {"version": "0.4.0", "name": "julia"}}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment