# 5. Seamless interoperation with the DOM

## 5.1. Practical example: a simple, responsive website using no HTML or CSS at all

To many programmers, using ‘static’ HTML and CSS feels like being locked up in a closet. As an alternative, responsiveness can simply be programmed using Transcrypt, as can be seen on this site written in Transcrypt. Note that it features sourcemaps. The site adapts to diverse screen formats, device types and landscape vs. portrait mode.

## 5.2. SVG example: Turtle graphics

Turtle graphics are a way to teach computer programming to children, invented by Seymour Papert in the 1960’s. Lines are drawn as ‘track’ of a walking turtle, that can move ahead and turn. Children use turtle graphics intuitively by imagining what they would do if they were the turtle. This leads to a recipe of motion, indeed an algorithm, which is the basis of imperative (as opposed to e.g. declarative) programming.

SVG or Scalable Vector Graphics are a way to display high quality graphs, e.g. in the browser. SVG, as opposed to e.g. the HTML Canvas, bypasses the pixel paradigm and works with floating point coordinates directly. As a consequence, SVG plots can be zoomed without becoming ragged or ‘pixelated’.

When looking under the hood of SVG, there’s an amazing correspondence between the primitives in an SVG path and the primitives of turtle graphics. So both from an aestethical and from a conceptual point of view, turtle graphics and SVG form a happy mariage.

Turtle graphics in Transcrypt do not require the use of any graphics libraries. Below are two turtle graphics examples and the source code of Transcrypt’s turtle module, which is quite compact. As can be seen from the code integration between Transcrypt and JavaScript is trivial.

Drawing a alternatingly floodfilled star

    # Free after the example from the Python 3.5 manual

    from turtle import *

    up ()
    goto (-250, -21)
    startPos = pos ()

    down ()
    color ('red', 'yellow')
    begin_fill ()
    while True:
        forward (500)
        right (170)
        if distance (startPos) < 1:
            break
    end_fill ()
    done ()

Click here to view the resulting zoomable star.

Drawing the contours of a snowflake

    from turtle import *

    josh = Turtle ()

    def draw (length):
        if length > 9:
            draw (length / 3)
            josh.left (60)
            draw (length / 3)
            josh.right (120)
            draw (length / 3)
            josh.left (60)
            draw (length / 3)
        else:
            josh.forward (length)

    length = 150
    josh.up ()
    josh.forward (length / 2)
    josh.left (90)
    josh.forward (length / 4)
    josh.right (90)
    josh.down ()

    for i in range (3):
        josh.right (120)
        draw (length)

    josh.done ()

Click here to view the resulting zoomable snowflake.

Transcrypt’s turtle graphics module sits directly on top of SVG, no libraries needed, so a very compact download

    __pragma__ ('skip')
    document = Math = 0
    __pragma__ ('noskip')

    _debug = False

    def abs (vec2D):
        return Math.sqrt (vec2D [0] * vec2D [0] + vec2D [1] * vec2D [1])

    _ns = 'http://www.w3.org/2000/svg'
    _svg = document.createElementNS (_ns, 'svg')

    _defaultElement = document.body
    _defaultElement.appendChild (_svg)

    _width = None
    _height = None
    _offset = None

    def _rightSize (self):
        nonlocal _width
        nonlocal _height
        nonlocal _offset

        _width = _defaultElement.offsetWidth
        _height = _defaultElement.offsetHeight
        _offset = [_width // 2, _height // 2]

        _svg.setAttribute ('width', _width)
        _svg.setAttribute ('height', _height)

    window.onresize = _rightSize

    _rightSize ()

    def bgcolor (color):
        nonlocal _defaultElement

        _bgcolor = color
        _defaultElement.style.backgroundColor = _bgcolor

    bgcolor ('white')

    def setDefaultElement (element):
        nonlocal _defaultElement

        _defaultElement.removeChild (_svg)
        _defaultElement = element
        element.appendChild (_svg)

        _rightSize ()
        bgcolor ('white')

    _allTurtles = []

    class Turtle:
        def __init__ (self):
            _allTurtles.append (self)
            self._paths = []
            self.reset ()

        def reset (self):
            self._heading = Math.PI / 2
            self.pensize (1)
            self.color ('black', 'black')
            self.down ()
            self._track = []    # Need to make track explicitly because:
            self.home ()        #   Makes a position but needs a track to put in in
            self.clear ()       #   Makes a track but needs a position to initialize it with

        def clear (self):
            for path in self._paths:
                _svg.removeChild (path)
            self._paths = []

            self._track = []
            self._moveto (self._position)

        def _flush (self):
            if _debug:
                print ('Flush:', self._track)

            if len (self._track) > 1:
                path = document.createElementNS (_ns, 'path')
                path.setAttribute ('d', ' '.join (self._track))
                path.setAttribute ('stroke', self._pencolor if self._pencolor != None else 'none')
                path.setAttribute ('stroke-width', self._pensize)
                path.setAttribute ('fill', self._fillcolor if self._fill and self._fillcolor != None else 'none')
                path.setAttribute ('fill-rule', 'evenodd')
                _svg.appendChild (path)
                self._paths.append (path)

                self._track = []
                self._moveto (self._position)   # _track should start with a move command

        def done (self):
            self._flush ()

        def pensize (self, width):
            self._flush ()
            if width == None:
                return self._pensize
            else:
                self._pensize = width

        def color (self, pencolor, fillcolor = None):
            self._flush ()
            self._pencolor = pencolor

            if fillcolor != None:
                self._fillcolor = fillcolor

        def goto (self, x, y = None):
            if y == None:
                self._position = x
            else:
                self._position = [x, y]

            self._track.append ('{} {} {}'.format (
                'L' if self._down else 'M',
                self._position [0] + _offset [0],
                self._position [1] + _offset [1])
            )

        def _moveto (self, x, y = None):
            wasdown = self.isdown ()
            self.up ()
            self.goto (x, y)
            if wasdown:
                self.down ()

        def home (self):
            self._moveto (0, 0)

        def position (self):
            return self._position [:]

        def pos (self):
            return self.position ()

        def distance (self, x, y = None):
            if y == None:
                other = x
            else:
                other = [x, y]

            dX = other [0] - self._position [0]
            dY = other [1] - self._position [1]

            return Math.sqrt (dX * dX + dY * dY)

        def up (self):
            self._down = False

        def down (self):
            self._down = True

        def isdown (self):
            return self._down

        def _predict (self, length):
            delta = [Math.sin (self._heading), Math.cos (self._heading)]
            return [self._position [0] + length * delta [0], self._position [1] + length * delta [1]]

        def forward (self, length):
            self._position = self._predict (length)

            self._track.append ('{} {} {}'.format (
                'L' if self._down else 'M',
                self._position [0] + _offset [0],
                self._position [1] + _offset [1])
            )

        def back (self, length):
            self.forward (-length)

        def circle (self, radius):
            self.left (90)
            opposite = self._predict (2 * (radius + 1) + 1)
            self.right (90)

            self._track.append ('{} {} {} {} {} {} {} {}'.format (
                'A',
                radius,
                radius,
                0,
                1,
                0,
                opposite [0] + _offset [0],
                opposite [1] + _offset [1]
            ))

            self._track.append ('{} {} {} {} {} {} {} {}'.format (
                'A',
                radius,
                radius,
                0,
                1,
                0,
                self._position [0] + _offset [0],
                self._position [1] + _offset [1]
            ))

        def left (self, angle):
            self._heading = (self._heading + Math.PI * angle / 180) % (2 * Math.PI)

        def right (self, angle):
            self.left (-angle)

        def begin_fill (self):
            self._flush ()
            self._fill = True

        def end_fill (self):
            self._flush ()
            self._fill = False

    _defaultTurtle = Turtle ()

    def reset ():
        nonlocal _allTurtles
        bgcolor ('white')
        for turtle in _allTurtles:
            turtle.reset ()
            turtle.done ()

    def clear ():
        nonlocal _allTurtles
        for turtle in _allTurtles:
            turtle.clear ()

    def done ():                            _defaultTurtle.done ()
    def pensize (width):                    _defaultTurtle.pensize (width)
    def color (pencolor, fillcolor = None): _defaultTurtle.color (pencolor, fillcolor)
    def home ():                            _defaultTurtle.home ()
    def goto (x, y = None):                 _defaultTurtle.goto (x, y)
    def position ():                        return _defaultTurtle.position ()
    def pos ():                             return _defaultTurtle.pos ()
    def distance (x, y = None):             return _defaultTurtle.distance (x, y)
    def up ():                              _defaultTurtle.up ()
    def down ():                            _defaultTurtle.down ()
    def forward (length):                   _defaultTurtle.forward (length)
    def back (length):                      _defaultTurtle.back (length)
    def circle (radius):                    _defaultTurtle.circle (radius)
    def left (angle):                       _defaultTurtle.left (angle)
    def right (angle):                      _defaultTurtle.right (angle)
    def begin_fill ():                      _defaultTurtle.begin_fill ()
    def end_fill ():                        _defaultTurtle.end_fill ()

Remark: In a later stage animation may be added. As a further step, for complicated fractals, transparent server side compilation of a relatively simple algorithm would allow on-line editing combined with fast client side rendering of high-resolution graphics.

# 6. Integration with JavaScript libraries

## 6.1. Three ways of integration

There are three ways to integrate Transcrypt applications with existing JavaScript libraries.

1. The simplest way is to use the library as is, without any encapsulation. In this way all symbols of that library will be in the global namespace. While many JavaScript programmers don’t seem to mind that, many Python programmers do.
2. Another way is to encapsulate the JavaScript library as a whole in a Transcrypt module. In the distibution this is done for the fabric module, that encapsulates fabric.js. In this way the global namespace stays clean.
3. The third way is to write a complete Pythonic API for the JavaScript library. This is overkill in most cases and makes it harder to keep up with new versions of the library. Note that Transcrypt was desiged to make seamless cooperation between Transcrypt and JavaScript libraries possible without

In the Pong example below, approach 2 is choosen to encapsulate the fabric.js graphics library. In most cases this approach strikes a good balance between effort and yield. As can be seen below, the effort involved is minimal.

The encapsulation layer for fabric.js

    __pragma__ ('noanno')

    fabric = __pragma__ ('js',
        '''
    (function () {{
        var exports = {{}};
        {}  // Puts fabric in exports and in global window
        delete window.fabric;
        return exports;
    }}) () .fabric;
        ''',
        __include__ ('com/fabricjs/__javascript__/fabric.js')
    )

Note that __pragma__ (‘js’, <skeletoncode>, includes = [<file1>, <file2>, ..]) is used to achieve the encapsulation. It replaces the {} by the respective contents of the files. The fabric module is part of the download. Note that not all facilities were included in customizing fabric.js. You can drop-in replaces the fabric.js in the __javascript__ subdirectory by another customized version without changing anything. Preferably download a development version, since that enables easy debugging. Transcryp will minify it for you on the fly.

## 6.2. Integration example: Pong

In using the fabric.js JavaScript library this example, the only thing differing from plain JavaScipt is that new <constructor> is replaced by __new__ (<constructor>).

pong.py
    __pragma__ ('skip')
    window = Math = Date = 0    # Prevent complaints by optional static checker
    __pragma__ ('noskip')

    from com.fabricjs import fabric

    orthoWidth = 1000
    orthoHeight = 750
    fieldHeight = 650

    enter, esc, space = 13, 27, 32

    window.onkeydown = lambda event: event.keyCode != space # Prevent scrolldown on spacebar press

    class Attribute:    # Attribute in the gaming sense of the word, rather than of an object
        def __init__ (self, game):
            self.game = game                    # Attribute knows game it's part of
            self.game.attributes.append (self)  # Game knows all its attributes
            self.install ()                     # Put in place graphical representation of attribute
            self.reset ()                       # Reset attribute to start position

        def reset (self):       # Restore starting positions or score, then commit to fabric
            self.commit ()      # Nothing to restore for the Attribute base class

        def predict (self):
            pass

        def interact (self):
            pass

        def commit (self):
            pass

    class Sprite (Attribute):   # Here, a sprite is an attribute that can move
        def __init__ (self, game, width, height):
            self.width = width
            self.height = height
            Attribute.__init__ (self, game)

        def install (self):     # The sprite holds an image that fabric can display
            self.image = __new__ (fabric.Rect ({
                'width': self.game.scaleX (self.width), 'height': self.game.scaleY (self.height),
                'originX': 'center', 'originY': 'center', 'fill': 'white'
            }))

        __pragma__ ('kwargs')
        def reset (self, vX = 0, vY = 0, x = 0, y = 0):
            self.vX = vX        # Speed
            self.vY = vY

            self.x = x          # Predicted position, can be commit, no bouncing initially
            self.y = y

            Attribute.reset (self)
        __pragma__ ('nokwargs')

        def predict (self):     # Predict position, do not yet commit, bouncing may alter it
            self.x += self.vX * self.game.deltaT
            self.y += self.vY * self.game.deltaT

        def commit (self):      # Update fabric image for asynch draw
            self.image.left = self.game.orthoX (self.x)
            self.image.top = self.game.orthoY (self.y)

        def draw (self):
            self.game.canvas.add (self.image)

    class Paddle (Sprite):
        margin = 30 # Distance of paddles from walls
        width = 10
        height = 100
        speed = 400 # / s

        def __init__ (self, game, index):
            self.index = index  # Paddle knows its player index, 0 == left, 1 == right
            Sprite.__init__ (self, game, self.width, self.height)

        def reset (self):       # Put paddle in rest position, dependent on player index
            Sprite.reset (
                self,
                x = orthoWidth // 2 - self.margin if self.index else -orthoWidth // 2 + self.margin,
                y = 0
            )

        def predict (self): # Let paddle react on keys
            self.vY = 0

            if self.index:                          # Right player
                if self.game.keyCode == ord ('K'):  # Letter K pressed
                    print (111)
                    self.vY = self.speed
                elif self.game.keyCode == ord ('M'):
                    self.vY = -self.speed
            else:                                   # Left player
                if self.game.keyCode == ord ('A'):
                    self.vY = self.speed
                elif self.game.keyCode == ord ('Z'):
                    self.vY = -self.speed

            Sprite.predict (self)                   # Do not yet commit, paddle may bounce with walls

        def interact (self):    # Paddles and ball assumed infinitely thin
            # Paddle touches wall
            self.y = Math.max (self.height // 2 - fieldHeight // 2, Math.min (self.y, fieldHeight // 2 - self.height // 2))

            # Paddle hits ball
            if (
                (self.y - self.height // 2) < self.game.ball.y < (self.y + self.height // 2)
                and (
                    (self.index == 0 and self.game.ball.x < self.x) # On or behind left paddle
                    or
                    (self.index == 1 and self.game.ball.x > self.x) # On or behind right paddle
                )
            ):
                self.game.ball.x = self.x               # Ball may have gone too far already
                self.game.ball.vX = -self.game.ball.vX  # Bounce on paddle
                self.game.ball.speedUp (self)

    class Ball (Sprite):
        side = 8
        speed = 300 # / s

        def __init__ (self, game):
            Sprite.__init__ (self, game, self.side, self.side)

        def reset (self):   # Launch according to service direction with random angle offset from horizontal
            angle =  (
                self.game.serviceIndex * Math.PI    # Service direction
                +
                (1 if Math.random () > 0.5 else -1) * Math.random () * Math.atan (fieldHeight / orthoWidth)
            )

            Sprite.reset (
                self,
                vX = self.speed * Math.cos (angle),
                vY = self.speed * Math.sin (angle)
            )

        def predict (self):
            Sprite.predict (self)           # Integrate velocity to position

            if self.x < -orthoWidth // 2:   # If out on left side
                self.game.scored (1)        #   Right player scored
            elif self.x > orthoWidth // 2:
                self.game.scored (0)

            if self.y > fieldHeight // 2:   # If it hits top wall
                self.y = fieldHeight // 2   #   It may have gone too far already
                self.vY = -self.vY          #   Bounce
            elif self.y < -fieldHeight // 2:
                self.y = -fieldHeight // 2
                self.vY = -self.vY

        def speedUp (self, bat):
            factor = 1 + 0.15 * (1 - Math.abs (self.y - bat.y) / (bat.height // 2)) ** 2    # Speed will increase more if paddle hit near centre

            if Math.abs (self.vX) < 3 * self.speed:
                self.vX *= factor
                self.vY *= factor

    class Scoreboard (Attribute):
        nameShift = 75
        hintShift = 25

        def install (self): # Graphical representation of scoreboard are four labels and a separator line
            self.playerLabels = [__new__ (fabric.Text ('Player {}'.format (name), {
                    'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30',
                    'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.nameShift)
            })) for name, position in (('AZ keys:', -7/16), ('KM keys:', 1/16))]

            self.hintLabel = __new__ (fabric.Text ('[spacebar] starts game, [enter] resets score', {
                    'fill': 'white', 'fontFamily': 'arial', 'fontSize': '12',
                    'left': self.game.orthoX (-7/16 * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.hintShift)
            }))

            self.image = __new__ (fabric.Line ([
                    self.game.orthoX (-orthoWidth // 2), self.game.orthoY (fieldHeight // 2),
                    self.game.orthoX (orthoWidth // 2), self.game.orthoY (fieldHeight // 2)
                ],
                {'stroke': 'white'}
            ))

        def increment (self, playerIndex):
            self.scores [playerIndex] += 1

        def reset (self):
            self.scores = [0, 0]
            Attribute.reset (self)  # Only does a commit here

        def commit (self):          # Committing labels is adapting their texts
            self.scoreLabels = [__new__ (fabric.Text ('{}'.format (score), {
                    'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30',
                    'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (fieldHeight // 2 + self.nameShift)
            })) for score, position in zip (self.scores, (-2/16, 6/16))]

        def draw (self):
            for playerLabel, scoreLabel in zip (self.playerLabels, self.scoreLabels):
                self.game.canvas.add (playerLabel)
                self.game.canvas.add (scoreLabel)
                self.game.canvas.add (self.hintLabel)
            self.game.canvas.add (self.image)

    class Game:
        def __init__ (self):
            self.serviceIndex = 1 if Math.random () > 0.5 else 0    # Index of player that has initial service
            self.pause = True                           # Start game in paused state
            self.keyCode = None

            self.canvas = __new__ (fabric.Canvas ('canvas', {'backgroundColor': 'black', 'originX': 'center', 'originY': 'center'}))
            self.canvas.onWindowResize = self.resize    # Install draw callback, will be called asynch
            self.canvas.onWindowDraw = self.draw        # Install resize callback, will be called if resized
            self.canvas.lineWidth = 2
            self.canvas.clear ()

            self.attributes = []                        # All attributes will insert themselves here
            self.paddles = [Paddle (self, index) for index in range (2)]    # Pass game as parameter self
            self.ball = Ball (self)
            self.scoreboard = Scoreboard (self)

            window.setInterval (self.update, 10)    # Install update callback, time in ms
            window.setInterval (self.draw, 20)      # Install draw callback, time in ms
            window.addEventListener ('keydown', self.keydown)
            window.addEventListener ('keyup', self.keyup)

            for key in ('A', 'Z', 'K', 'M', 'space', 'enter'):
                element = document.getElementById (key)
                element.addEventListener ('mousedown', (lambda aKey: lambda: self.mouseOrTouch (aKey, True)) (key))
                element.addEventListener ('touchstart', (lambda aKey: lambda: self.mouseOrTouch (aKey, True)) (key))
                element.addEventListener ('mouseup', (lambda aKey: lambda: self.mouseOrTouch (aKey, False)) (key))
                element.addEventListener ('touchend', (lambda aKey: lambda: self.mouseOrTouch (aKey, False)) (key))
                element.style.cursor = 'pointer'

            self.time = + __new__ (Date)

        def mouseOrTouch (self, key, down):
            if down:
                if key == 'space':
                    self.keyCode = space
                elif key == 'enter':
                    self.keyCode = enter
                else:
                    self.keyCode = ord (key)
            else:
                self.keyCode = None

        def update (self):                          # Note that update and draw are not synchronized
            oldTime = self.time
            self.time = + __new__ (Date)
            self.deltaT = (self.time - oldTime) / 1000.

            if self.pause:                          # If in paused state
                if self.keyCode == space:           #   If spacebar hit
                    self.pause = False              #         Start playing
                elif self.keyCode == enter:         #   Else if enter hit
                    self.scoreboard.reset ()        #         Reset score
            else:                                   # Else, so if in active state
                for attribute in self.attributes:   #   Compute predicted values
                    attribute.predict ()

                for attribute in self.attributes:   #   Correct values for bouncing and scoring
                    attribute.interact ()

                for attribute in self.attributes:   #   Commit them to pyglet for display
                    attribute.commit ()

        def scored (self, playerIndex):             # Player has scored
            self.scoreboard.increment (playerIndex) # Increment player's points
            self.serviceIndex = 1 - playerIndex     # Grant service to the unlucky player

            for paddle in self.paddles:             # Put paddles in rest position
                paddle.reset ()

            self.ball.reset ()                      # Put ball in rest position
            self.pause = True                       # Wait for next round

        def draw (self):
            self.canvas.clear ()
            for attribute in self.attributes:
                attribute.draw ()

        def resize (self, width, height):
            pass

        def scaleX (self, x):
            return x * (self.canvas.width / orthoWidth)

        def scaleY (self, y):
            return y * (self.canvas.height / orthoHeight)

        def orthoX (self, x):
            return self.scaleX (x + orthoWidth // 2)

        def orthoY (self, y):
            return self.scaleY (orthoHeight - fieldHeight // 2 - y)

        def keydown (self, event):
            self.keyCode = event.keyCode

        def keyup (self, event):
            self.keyCode = None

    game = Game ()  # Create and run game
| pong.mod.js
        (function () {
            var fabric = __init__ (__world__.com.fabricjs).fabric;
            var orthoWidth = 1000;
            var orthoHeight = 750;
            var fieldHeight = 650;
            var __left0__ = tuple ([13, 27, 32]);
            var enter = __left0__ [0];
            var esc = __left0__ [1];
            var space = __left0__ [2];
            window.onkeydown = (function __lambda__ (event) {
                return event.keyCode != space;});
            var Attribute = __class__ ('Attribute', [object], {
                get __init__ () {return __get__ (this, function (self, game) {
                    self.game = game;
                    self.game.attributes.append (self);
                    self.install ();
                    self.reset ();
                });},
                get reset () {return __get__ (this, function (self) {
                    self.commit ();
                });},
                get predict () {return __get__ (this, function (self) {
                    // pass;
                });},
                get interact () {return __get__ (this, function (self) {
                    // pass;
                });},
                get commit () {return __get__ (this, function (self) {
                    // pass;
                });}
            });
            var Sprite = __class__ ('Sprite', [Attribute], {
                get __init__ () {return __get__ (this, function (self, game, width, height) {
                    self.width = width;
                    self.height = height;
                    Attribute.__init__ (self, game);
                });},
                get install () {return __get__ (this, function (self) {
                    self.image = new fabric.Rect (dict ({'width': self.game.scaleX (self.width), 'height': self.game.scaleY (self.height), 'originX': 'center', 'originY': 'center', 'fill': 'white'}));
                });},
                get reset () {return __get__ (this, function (self, vX, vY, x, y) {
                    if (typeof vX == 'undefined' || (vX != null && vX .__class__ == __kwargdict__)) {;
                        var vX = 0;
                    };
                    if (typeof vY == 'undefined' || (vY != null && vY .__class__ == __kwargdict__)) {;
                        var vY = 0;
                    };
                    if (typeof x == 'undefined' || (x != null && x .__class__ == __kwargdict__)) {;
                        var x = 0;
                    };
                    if (typeof y == 'undefined' || (y != null && y .__class__ == __kwargdict__)) {;
                        var y = 0;
                    };
                    if (arguments.length) {
                        var __ilastarg0__ = arguments.length - 1;
                        if (arguments [__ilastarg0__] && arguments [__ilastarg0__].__class__ == __kwargdict__) {
                            var __allkwargs0__ = arguments [__ilastarg0__--];
                            for (var __attrib0__ in __allkwargs0__) {
                                switch (__attrib0__) {
                                    case 'self': var self = __allkwargs0__ [__attrib0__]; break;
                                    case 'vX': var vX = __allkwargs0__ [__attrib0__]; break;
                                    case 'vY': var vY = __allkwargs0__ [__attrib0__]; break;
                                    case 'x': var x = __allkwargs0__ [__attrib0__]; break;
                                    case 'y': var y = __allkwargs0__ [__attrib0__]; break;
                                }
                            }
                        }
                    }
                    self.vX = vX;
                    self.vY = vY;
                    self.x = x;
                    self.y = y;
                    Attribute.reset (self);
                });},
                get predict () {return __get__ (this, function (self) {
                    self.x += self.vX * self.game.deltaT;
                    self.y += self.vY * self.game.deltaT;
                });},
                get commit () {return __get__ (this, function (self) {
                    self.image.left = self.game.orthoX (self.x);
                    self.image.top = self.game.orthoY (self.y);
                });},
                get draw () {return __get__ (this, function (self) {
                    self.game.canvas.add (self.image);
                });}
            });
            var Paddle = __class__ ('Paddle', [Sprite], {
                get __init__ () {return __get__ (this, function (self, game, index) {
                    self.index = index;
                    Sprite.__init__ (self, game, self.width, self.height);
                });},
                get reset () {return __get__ (this, function (self) {
                    Sprite.reset (self, __kwargdict__ ({x: (self.index ? Math.floor (orthoWidth / 2) - self.margin : Math.floor (-(orthoWidth) / 2) + self.margin), y: 0}));
                });},
                get predict () {return __get__ (this, function (self) {
                    self.vY = 0;
                    if (self.index) {
                        if (self.game.keyCode == ord ('K')) {
                            print (111);
                            self.vY = self.speed;
                        }
                        else {
                            if (self.game.keyCode == ord ('M')) {
                                self.vY = -(self.speed);
                            }
                        }
                    }
                    else {
                        if (self.game.keyCode == ord ('A')) {
                            self.vY = self.speed;
                        }
                        else {
                            if (self.game.keyCode == ord ('Z')) {
                                self.vY = -(self.speed);
                            }
                        }
                    }
                    Sprite.predict (self);
                });},
                get interact () {return __get__ (this, function (self) {
                    self.y = Math.max (Math.floor (self.height / 2) - Math.floor (fieldHeight / 2), Math.min (self.y, Math.floor (fieldHeight / 2) - Math.floor (self.height / 2)));
                    if ((self.y - Math.floor (self.height / 2) < self.game.ball.y && self.game.ball.y < self.y + Math.floor (self.height / 2)) && (self.index == 0 && self.game.ball.x < self.x || self.index == 1 && self.game.ball.x > self.x)) {
                        self.game.ball.x = self.x;
                        self.game.ball.vX = -(self.game.ball.vX);
                        self.game.ball.speedUp (self);
                    }
                });}
            });
            Paddle.margin = 30;
            Paddle.width = 10;
            Paddle.height = 100;
            Paddle.speed = 400;
            var Ball = __class__ ('Ball', [Sprite], {
                get __init__ () {return __get__ (this, function (self, game) {
                    Sprite.__init__ (self, game, self.side, self.side);
                });},
                get reset () {return __get__ (this, function (self) {
                    var angle = self.game.serviceIndex * Math.PI + ((Math.random () > 0.5 ? 1 : -(1)) * Math.random ()) * Math.atan (fieldHeight / orthoWidth);
                    Sprite.reset (self, __kwargdict__ ({vX: self.speed * Math.cos (angle), vY: self.speed * Math.sin (angle)}));
                });},
                get predict () {return __get__ (this, function (self) {
                    Sprite.predict (self);
                    if (self.x < Math.floor (-(orthoWidth) / 2)) {
                        self.game.scored (1);
                    }
                    else {
                        if (self.x > Math.floor (orthoWidth / 2)) {
                            self.game.scored (0);
                        }
                    }
                    if (self.y > Math.floor (fieldHeight / 2)) {
                        self.y = Math.floor (fieldHeight / 2);
                        self.vY = -(self.vY);
                    }
                    else {
                        if (self.y < Math.floor (-(fieldHeight) / 2)) {
                            self.y = Math.floor (-(fieldHeight) / 2);
                            self.vY = -(self.vY);
                        }
                    }
                });},
                get speedUp () {return __get__ (this, function (self, bat) {
                    var factor = 1 + 0.15 * Math.pow (1 - Math.abs (self.y - bat.y) / (Math.floor (bat.height / 2)), 2);
                    if (Math.abs (self.vX) < 3 * self.speed) {
                        self.vX *= factor;
                        self.vY *= factor;
                    }
                });}
            });
            Ball.side = 8;
            Ball.speed = 300;
            var Scoreboard = __class__ ('Scoreboard', [Attribute], {
                get install () {return __get__ (this, function (self) {
                    self.playerLabels = function () {
                        var __accu0__ = [];
                        var __iter0__ = tuple ([tuple (['AZ keys:', -(7) / 16]), tuple (['KM keys:', 1 / 16])]);
                        for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                            var __left0__ = __iter0__ [__index0__];
                            var py_name = __left0__ [0];
                            var position = __left0__ [1];
                            __accu0__.append (new fabric.Text ('Player {}'.format (py_name), dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30', 'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight / 2) + self.nameShift)})));
                        }
                        return __accu0__;
                    } ();
                    self.hintLabel = new fabric.Text ('[spacebar] starts game, [enter] resets score', dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '12', 'left': self.game.orthoX ((-(7) / 16) * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight / 2) + self.hintShift)}));
                    self.image = new fabric.Line (list ([self.game.orthoX (Math.floor (-(orthoWidth) / 2)), self.game.orthoY (Math.floor (fieldHeight / 2)), self.game.orthoX (Math.floor (orthoWidth / 2)), self.game.orthoY (Math.floor (fieldHeight / 2))]), dict ({'stroke': 'white'}));
                });},
                get increment () {return __get__ (this, function (self, playerIndex) {
                    self.scores [playerIndex]++;
                });},
                get reset () {return __get__ (this, function (self) {
                    self.scores = list ([0, 0]);
                    Attribute.reset (self);
                });},
                get commit () {return __get__ (this, function (self) {
                    self.scoreLabels = function () {
                        var __accu0__ = [];
                        var __iter0__ = zip (self.scores, tuple ([-(2) / 16, 6 / 16]));
                        for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                            var __left0__ = __iter0__ [__index0__];
                            var score = __left0__ [0];
                            var position = __left0__ [1];
                            __accu0__.append (new fabric.Text ('{}'.format (score), dict ({'fill': 'white', 'fontFamily': 'arial', 'fontSize': '30', 'left': self.game.orthoX (position * orthoWidth), 'top': self.game.orthoY (Math.floor (fieldHeight / 2) + self.nameShift)})));
                        }
                        return __accu0__;
                    } ();
                });},
                get draw () {return __get__ (this, function (self) {
                    var __iter0__ = zip (self.playerLabels, self.scoreLabels);
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var __left0__ = __iter0__ [__index0__];
                        var playerLabel = __left0__ [0];
                        var scoreLabel = __left0__ [1];
                        self.game.canvas.add (playerLabel);
                        self.game.canvas.add (scoreLabel);
                        self.game.canvas.add (self.hintLabel);
                    }
                    self.game.canvas.add (self.image);
                });}
            });
            Scoreboard.nameShift = 75;
            Scoreboard.hintShift = 25;
            var Game = __class__ ('Game', [object], {
                get __init__ () {return __get__ (this, function (self) {
                    self.serviceIndex = (Math.random () > 0.5 ? 1 : 0);
                    self.pause = true;
                    self.keyCode = null;
                    self.canvas = new fabric.Canvas ('canvas', dict ({'backgroundColor': 'black', 'originX': 'center', 'originY': 'center'}));
                    self.canvas.onWindowResize = self.resize;
                    self.canvas.onWindowDraw = self.draw;
                    self.canvas.lineWidth = 2;
                    self.canvas.clear ();
                    self.attributes = list ([]);
                    self.paddles = function () {
                        var __accu0__ = [];
                        for (var index = 0; index < 2; index++) {
                            __accu0__.append (Paddle (self, index));
                        }
                        return __accu0__;
                    } ();
                    self.ball = Ball (self);
                    self.scoreboard = Scoreboard (self);
                    window.setInterval (self.update, 10);
                    window.setInterval (self.draw, 20);
                    window.addEventListener ('keydown', self.keydown);
                    window.addEventListener ('keyup', self.keyup);
                    var __iter0__ = tuple (['A', 'Z', 'K', 'M', 'space', 'enter']);
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var key = __iter0__ [__index0__];
                        var element = document.getElementById (key);
                        element.addEventListener ('mousedown', (function __lambda__ (aKey) {
                            return (function __lambda__ () {
                                return self.mouseOrTouch (aKey, true);});}) (key));
                        element.addEventListener ('touchstart', (function __lambda__ (aKey) {
                            return (function __lambda__ () {
                                return self.mouseOrTouch (aKey, true);});}) (key));
                        element.addEventListener ('mouseup', (function __lambda__ (aKey) {
                            return (function __lambda__ () {
                                return self.mouseOrTouch (aKey, false);});}) (key));
                        element.addEventListener ('touchend', (function __lambda__ (aKey) {
                            return (function __lambda__ () {
                                return self.mouseOrTouch (aKey, false);});}) (key));
                        element.style.cursor = 'pointer';
                    }
                    self.time = +(new Date);
                });},
                get mouseOrTouch () {return __get__ (this, function (self, key, down) {
                    if (down) {
                        if (key == 'space') {
                            self.keyCode = space;
                        }
                        else {
                            if (key == 'enter') {
                                self.keyCode = enter;
                            }
                            else {
                                self.keyCode = ord (key);
                            }
                        }
                    }
                    else {
                        self.keyCode = null;
                    }
                });},
                get update () {return __get__ (this, function (self) {
                    var oldTime = self.time;
                    self.time = +(new Date);
                    self.deltaT = (self.time - oldTime) / 1000.0;
                    if (self.pause) {
                        if (self.keyCode == space) {
                            self.pause = false;
                        }
                        else {
                            if (self.keyCode == enter) {
                                self.scoreboard.reset ();
                            }
                        }
                    }
                    else {
                        var __iter0__ = self.attributes;
                        for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                            var attribute = __iter0__ [__index0__];
                            attribute.predict ();
                        }
                        var __iter0__ = self.attributes;
                        for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                            var attribute = __iter0__ [__index0__];
                            attribute.interact ();
                        }
                        var __iter0__ = self.attributes;
                        for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                            var attribute = __iter0__ [__index0__];
                            attribute.commit ();
                        }
                    }
                });},
                get scored () {return __get__ (this, function (self, playerIndex) {
                    self.scoreboard.increment (playerIndex);
                    self.serviceIndex = 1 - playerIndex;
                    var __iter0__ = self.paddles;
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var paddle = __iter0__ [__index0__];
                        paddle.reset ();
                    }
                    self.ball.reset ();
                    self.pause = true;
                });},
                get draw () {return __get__ (this, function (self) {
                    self.canvas.clear ();
                    var __iter0__ = self.attributes;
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var attribute = __iter0__ [__index0__];
                        attribute.draw ();
                    }
                });},
                get resize () {return __get__ (this, function (self, width, height) {
                    // pass;
                });},
                get scaleX () {return __get__ (this, function (self, x) {
                    return x * (self.canvas.width / orthoWidth);
                });},
                get scaleY () {return __get__ (this, function (self, y) {
                    return y * (self.canvas.height / orthoHeight);
                });},
                get orthoX () {return __get__ (this, function (self, x) {
                    return self.scaleX (x + Math.floor (orthoWidth / 2));
                });},
                get orthoY () {return __get__ (this, function (self, y) {
                    return self.scaleY ((orthoHeight - Math.floor (fieldHeight / 2)) - y);
                });},
                get keydown () {return __get__ (this, function (self, event) {
                    self.keyCode = event.keyCode;
                });},
                get keyup () {return __get__ (this, function (self, event) {
                    self.keyCode = null;
                });}
            });
            var game = Game ();
            __pragma__ ('<use>' +
                'com.fabricjs' +
            '</use>')
            __pragma__ ('<all>')
                __all__.Attribute = Attribute;
                __all__.Ball = Ball;
                __all__.Game = Game;
                __all__.Paddle = Paddle;
                __all__.Scoreboard = Scoreboard;
                __all__.Sprite = Sprite;
                __all__.enter = enter;
                __all__.esc = esc;
                __all__.fieldHeight = fieldHeight;
                __all__.game = game;
                __all__.orthoHeight = orthoHeight;
                __all__.orthoWidth = orthoWidth;
                __all__.space = space;
            __pragma__ ('</all>')
        }) ();

## 6.3. Joined minification

Minification is currently performed by the Google closure compiler, that’s also part of the distribution. Rather than separately minifying libraries, the application is minified as a whole. In principle this enables a smaller total download size. Currently closures ADVANCED_OPTIMIZATIONS switch breaks the working strict code, however, so the SIMPLE_OPTIMIZATIONS switch is used by default.

As can be seen from the listings, pong.mod.js without libraries is only slightly longer than pong.py without libraries. The difference mainly comes from the expensive keyword arguments mechanism that is activated for the reset function, using __pragma__ (‘kargs’) and __pragma__ (‘nokwargs’). The minified version is about half this size. The Transcrypt runtime itself in minified form is about 9kB. So the bulk of the total size of the minified file, 148kB comes from fabric.js. From this example it becomes clear that Transcrypt is extremely lightweight.

## 6.4. Integration example: jQuery

In contrast to the use of the fabric.js library in the Pong example, jQuery hasn’t been encapsulated at all. It’s just downloaded on the fly from a content delivery network and used as-is. Instead of the $ (that is not a valid Python identifier), an S is used as alias. This might have been any character sequence.

jquery_demo.py
    __pragma__ ('alias', 'S', '$')

    def start ():
        def changeColors ():
            for div in S__divs:
                S (div) .css ({
                    'color': 'rgb({},{},{})'.format (* [int (256 * Math.random ()) for i in range (3)]),
                })

        S__divs = S ('div')
        changeColors ()
        window.setInterval (changeColors, 500)
| jquery_demo.mod.js
        (function () {
            var start = function () {
                var changeColors = function () {
                    var __iter0__ = $divs;
                    for (var __index0__ = 0; __index0__ < __iter0__.length; __index0__++) {
                        var div = __iter0__ [__index0__];
                        $ (div).css (dict ({'color': 'rgb({},{},{})'.format.apply (null, function () {
                            var __accu0__ = [];
                            for (var i = 0; i < 3; i++) {
                                __accu0__.append (int (256 * Math.random ()));
                            }
                            return __accu0__;
                        } ())}));
                    }
                };
                var $divs = $ ('div');
                changeColors ();
                window.setInterval (changeColors, 500);
            };
            __pragma__ ('<all>')
                __all__.start = start;
            __pragma__ ('</all>')
        }) ();

## 6.5. Integration example: D3.js

The D3.js graphics library offers animation by data driven DOM manipulation. It combines well with class based object oriented programming as supported by Trancrypt, leading to applications that are easy to understand and maintain.

d3js_demo.py
    class Spawn:
        def __init__ (self, width, height):
            self.width, self.height, self.spacing = self.fill = width, height, 100, d3.scale.category20 ()

            self.svg = d3.select ('body'
            ) .append ('svg'
            ) .attr ('width', self.width
            ) .attr ('height', self.height
            ) .on ('mousemove', self.mousemove
            ) .on ('mousedown', self.mousedown)

            self.svg.append ('rect'
            ) .attr ('width', self.width
            ) .attr ('height', self.height)

            self.cursor = self.svg.append ('circle'
            ) .attr ('r', self.spacing
            ) .attr ('transform', 'translate ({}, {})' .format (self.width / 2, self.height / 2)
            ) .attr ('class', 'cursor')

            self.force = d3.layout.force (
            ) .size ([self.width, self.height]
            ) .nodes ([{}]
            ) .linkDistance (self.spacing
            ) .charge (-1000
            ) .on ('tick', self.tick)

            self.nodes, self.links, self.node, self.link = self.force.nodes (), self.force.links (), self.svg.selectAll ('.node'), self.svg.selectAll ('.link')

            self.restart ()

        def mousemove (self):
            self.cursor.attr ('transform', 'translate (' + d3.mouse (self.svg.node ()) + ')')

        def mousedown (self):
            def pushLink (target):
                x, y = target.x - node.x, target.y - node.y
                if Math.sqrt (x * x + y * y) < self.spacing:
                    spawn.links.push ({'source': node, 'target': target})

            point = d3.mouse (self.svg.node ())
            node = {'x': point [0], 'y': point [1]}
            self.nodes.push (node)
            self.nodes.forEach (pushLink)
            self.restart ()

        def tick (self):
            self.link.attr ('x1', lambda d: d.source.x
            ) .attr ('y1', lambda d: d.source.y
            ) .attr ('x2', lambda d: d.target.x
            ) .attr ('y2', lambda d: d.target.y)

            self.node.attr ('cx', lambda d: d.x
            ) .attr ('cy', lambda d: d.y)

        def restart (self):
            self.link = self.link.data (self.links)

            self.link.enter (
            ) .insert ('line', '.node'
            ) .attr('class', 'link')

            self.node = self.node.data (self.nodes)

            self.node.enter (
            ) .insert ('circle', '.cursor'
            ) .attr ('class', 'node'
            ) .attr ('r', 7
            ) .call (self.force.drag)

            self.force.start ()

    spawn = Spawn (window.innerWidth, window.innerHeight)
| d3js_demo.mod.js
        (function () {
            var Spawn = __class__ ('Spawn', [object], {
                get __init__ () {return __get__ (this, function (self, width, height) {
                    var __left0__ = tuple ([width, height, 100, d3.scale.category20 ()]);
                    self.width = __left0__ [0];
                    self.height = __left0__ [1];
                    self.spacing = __left0__ [2];
                    self.fill = __left0__;
                    self.svg = d3.select ('body').append ('svg').attr ('width', self.width).attr ('height', self.height).on ('mousemove', self.mousemove).on ('mousedown', self.mousedown);
                    self.svg.append ('rect').attr ('width', self.width).attr ('height', self.height);
                    self.cursor = self.svg.append ('circle').attr ('r', self.spacing).attr ('transform', 'translate ({}, {})'.format (self.width / 2, self.height / 2)).attr ('class', 'cursor');
                    self.force = d3.layout.force ().size (list ([self.width, self.height])).nodes (list ([dict ({})])).linkDistance (self.spacing).charge (-(1000)).on ('tick', self.tick);
                    var __left0__ = tuple ([self.force.nodes (), self.force.links (), self.svg.selectAll ('.node'), self.svg.selectAll ('.link')]);
                    self.nodes = __left0__ [0];
                    self.links = __left0__ [1];
                    self.node = __left0__ [2];
                    self.link = __left0__ [3];
                    self.restart ();
                });},
                get mousemove () {return __get__ (this, function (self) {
                    self.cursor.attr ('transform', ('translate (' + d3.mouse (self.svg.node ())) + ')');
                });},
                get mousedown () {return __get__ (this, function (self) {
                    var pushLink = function (target) {
                        var __left0__ = tuple ([target.x - node.x, target.y - node.y]);
                        var x = __left0__ [0];
                        var y = __left0__ [1];
                        if (Math.sqrt (x * x + y * y) < self.spacing) {
                            spawn.links.push (dict ({'source': node, 'target': target}));
                        }
                    };
                    var point = d3.mouse (self.svg.node ());
                    var node = dict ({'x': point [0], 'y': point [1]});
                    self.nodes.push (node);
                    self.nodes.forEach (pushLink);
                    self.restart ();
                });},
                get tick () {return __get__ (this, function (self) {
                    self.link.attr ('x1', (function __lambda__ (d) {
                        return d.source.x;})).attr ('y1', (function __lambda__ (d) {
                        return d.source.y;})).attr ('x2', (function __lambda__ (d) {
                        return d.target.x;})).attr ('y2', (function __lambda__ (d) {
                        return d.target.y;}));
                    self.node.attr ('cx', (function __lambda__ (d) {
                        return d.x;})).attr ('cy', (function __lambda__ (d) {
                        return d.y;}));
                });},
                get restart () {return __get__ (this, function (self) {
                    self.link = self.link.data (self.links);
                    self.link.enter ().insert ('line', '.node').attr ('class', 'link');
                    self.node = self.node.data (self.nodes);
                    self.node.enter ().insert ('circle', '.cursor').attr ('class', 'node').attr ('r', 7).call (self.force.drag);
                    self.force.start ();
                });}
            });
            var spawn = Spawn (window.innerWidth, window.innerHeight);
            __pragma__ ('<all>')
                __all__.Spawn = Spawn;
                __all__.spawn = spawn;
            __pragma__ ('</all>')
        }) ();