Wednesday, November 13, 2013

Two-way data binding in brython: knockout.js

I spent the last few days trying to learn AngularJS, and was resigned to the thought that I was going to have to suck it up and write my current project in straight javascript. I was getting frustrated with all the baggage that comes with AngularJS and its awful documentation (I still don't have a proper understanding of directives!) when I stumbled on KnockoutJS. It focuses on doing one thing very well, and doesn't force a particular flavor of MVC on you. Its documentation is awesome and has an approach to tutorials that, in my humble opinion, are more natural than the ones at Khan Academy.

And there's an interesting pattern in how it wraps observables.

To get around problems with IE, you access and modify KnockoutJS variables via functions. This has a handy side effect in that everything it touches is sanitized. In other words, you can have observable brython objects that are modified and returned directly without any javascript baggage! You don't even have to wrap the ko library in a JSObject... just use it directly in your python script. The only thing you have to remember is which objects are observable and which ones aren't - but you'd have to do that in javascript, too.

There is an oddity, however; KnockoutJS doesn't call brython class functions for events properly (of course, it's expecting javascript). You can get around it, though, by wrapping the function with lambda.

Here's an example:

index.html:
 <!doctype html>  
 <html>  
   
 <head>  
   <script type="application/javascript" , src="brython.js"></script>  
   <script type="application/javascript", src="knockout-3.0.0.js"></script>  
   <script type="text/python", src="exvg.py"></script>  
 </head>  
   
 <body onload="brython()">  
   <table>  
     <thead>  
       <tr>  
         <td>x</td>  
         <td>y</td>  
       </tr>  
     </thead>  
     <tbody>  
     <tr>  
       <td><input data-bind="value: x" /></td>  
       <td><input data-bind="value: y" /></td>  
       </tr>  
     </tbody>  
   </table>  
   <svg style="width:500px;height:200px;border:1px solid">  
     <circle r="10" data-bind="attr: {cx: x, cy: y}, event: { mousedown: mouseDown }"/>  
   </svg>  
 </body>  
   
 </html>  

exvg.py:
 class Point:  
   def __init__(self):  
     self.x = ko.observable(100)  
     self.y = ko.observable(50)  
       
     # This is a little funky:  
     self.mouseDown = lambda data, event : self._mouseDown(event)  
       
   def _mouseDown(self, event):  
     # I want to drag relative to the parent SVG element  
     containerRect = event.target.parent.getBoundingClientRect()  
     startX = containerRect.left  
     startY = containerRect.top  
       
     def mouseMove(event):  
       self.x(event.clientX-startX)  
       self.y(event.clientY-startY)  
       
     def mouseUp(event):  
       # In theory I should unbind the specific function, but brython  
       # chokes when I try to unbind mouseUp from mouseup  
       doc.unbind('mouseup')  
       doc.unbind('mousemove')  
       
     # I override other events until we're done dragging  
     event.preventDefault()  
     doc.bind('mousemove',mouseMove)  
     doc.bind('mouseup',mouseUp)  
   
 ko.applyBindings(Point())  

The result:

5 comments:

  1. Very interesting article . Thanks for trying brython + knockout js ... I guess that , in the long term , brython will converge towards a more declarative MVVM style . Any improvements you might want to suggest for better integration between both frameworks , please share them via brython@googlegroups.com . We'd really like to know ...

    ReplyDelete
    Replies
    1. Thanks! With regard to MVVM, I'm usually against anything that makes you conform to a particular structure in order to get things like two-way data binding (viz. why I prefer knockout.js to angular.js). Honestly, I'm already a little uncomfortable with some of the liberties brython takes with the simple purity of python (e.g. the weird doc <= syntax), and I'd be completely against formally incorporating any two-way binding beyond something buried in an optional library. My feeling is that if brython wants to incorporate this kind of thing, the best way would be to determine appropriate patterns from a python perspective first, rather than have it result from a strange hybrid between the javascript way of doing things. This blog post is a hack, and I would actually frown on serious attempts to make these two work together. These views probably make me a bit of an outsider in the brython community... :-) Even if I had more time to devote to it, I don't know if my opinions would be that useful.

      Delete
  2. Hi,
    Firstly, this is a very interesting article, thanks for posting.

    I have been looking at Brython for a project I'm working on and the idea of coupling it with Knockout.JS opens many possibilities. I am very intrigued by your comment regarding frowning on serious attempts to work with the two together. Is this because it is a hack? Are you able to share any further insights as to why this might be a 'frownable' practice? It would be good to get more information before I start down this road so your thoughts are much appreciated.

    Many Thanks!

    ReplyDelete
    Replies
    1. Thanks for reading, and thanks for the comment!

      I only meant "frownable" with regard to the idea of incorporating some MVVM or MVC structure as a standard component of brython. As practical advice, though, I'd still caution against combining these two too much - my demo here is to just show that it's possible, not that it's necessarily a good idea. I initially tried making a slightly more complicated system, similar in spirit to my demo, and quickly found the combination to cause more pain than it saved. Brython and knockout together make you keep track of THREE classes of objects (javascript, knockout, and brython) instead of two if you were to just use one or the other. Also, debugging brython and knockout by themselves can be non-trivial, but together, they were nightmarish. If your project is simple enough, this may not be a problem, but if it's going to get complex, things will probably start breaking down quickly. With more experience with both libraries, I possibly could have pulled it off, but if you're new to one or the other, I would recommend first learning one by itself.

      Delete
  3. Ah thanks for the tips! I have to admit I'm knew to all three (javascript, knockout, and brython) so I think I'll take your advice and get more familiar with each individually before trying to combine them.

    You've given me a lot to consider before proceeding, as the project could get quite complex if I'm not careful.

    ReplyDelete