2026, Jan 13 07:00

Vue 2 + Brython: Fix self-based method errors with javascript.this and get your Watch Later list working

Why Vue 2 methods in Brython fail with self and how to fix missing argument errors using a shared instance or javascript.this to make Watch Later work.

When you stitch together Vue 2 and Brython to build a lightweight “watch later” list for movies, it’s tempting to model Vue methods like regular Python methods with a self parameter. That subtle choice is exactly what breaks the interaction: clicking Add never updates the list, and DevTools shows a missing argument error. Here’s what is actually happening and how to fix it properly.

Problem statement, quickly

The UI has three movies. Each Add submits a Vue method that should toggle the title in a Watch Later section. Items in that section have a Remove button to pop them out. The code wires everything through Brython. The Add buttons don’t work; the Remove buttons never get a chance because the list stays empty.

Problematic example

The following keeps the same behavior but illustrates the issue with renamed identifiers.

<html>
<head>
  <meta charset="UTF-8" />
  <script src="https://unpkg.com/vue@2" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/brython@3/brython.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/brython@3/brython_stdlib.js"></script>
</head>
<body onload="brython()">
  <div id="app">
    <form @submit.prevent="flipLater('Mickey 17')">
      <label>Mickey 17</label>
      <input type="submit" value="Add">
    </form>
    <form @submit.prevent="flipLater('Oppenheimer')">
      <label>Oppenheimer</label>
      <input type="submit" value="Add">
    </form>
    <form @submit.prevent="flipLater('Dead Poets Society')">
      <label>Dead Poets Society</label>
      <input type="submit" value="Add">
    </form>
    <div id="later">
      <h2>Watch Later</h2>
      <ul>
        <li v-for="(film, idx) in laterQueue" :key="idx">
          <h4>{{ film }}
            <button @click="purgeLater(idx)">Remove</button>
          </h4>
        </li>
      </ul>
    </div>
  </div>
  <script type="text/python">
from browser import window
# This signature is the root cause
# Vue won't pass an object you can treat as `self` here
def flipLater(self, title, *_):
    if title in self.laterQueue:
        self.laterQueue.remove(title)
    else:
        self.laterQueue.append(title)
def purgeLater(self, idx):
    self.laterQueue.pop(idx)
vm2 = window.Vue.new({
    'el': '#app',
    'data': {
        'laterQueue': []
    },
    'methods': {
        'flipLater': flipLater,
        'purgeLater': purgeLater
    }
})
  </script>
</body>
</html>

What actually goes wrong

The crux is the method signatures. In Vue 2, methods are invoked with this bound to the component instance. Brython doesn’t translate that into a Python-style self parameter automatically. As a result, the function defined with self, title expects two positional arguments, but the template calls flipLater('Mickey 17') with a single argument. DevTools surfaces this as TypeError: toggleWatchLater() missing 1 required positional argument: 'movie_title'. That means the first positional slot was consumed by what you intended to be the string, leaving the expected movie title missing. The underlying data never updates, so the list never renders. When you debug this setup, remember you can check output in the JavaScript console and use console.log in addition to print.

Two straightforward fixes

There are two ways to align Brython with Vue’s calling convention using only what is shown here. The first is to avoid self entirely and access the Vue instance by a shared variable. The second uses Brython’s javascript.this() helper to retrieve the current this.

Fix 1: Reference the Vue instance directly

This version removes the self parameter and updates the reactive array through the Vue instance variable.

<html>
<head>
  <meta charset="UTF-8" />
  <script src="https://unpkg.com/vue@2" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/brython@3/brython.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/brython@3/brython_stdlib.js"></script>
</head>
<body onload="brython()">
  <div id="app">
    <form @submit.prevent="flipLater('Mickey 17')">
      <label>Mickey 17</label>
      <input type="submit" value="Add">
    </form>
    <form @submit.prevent="flipLater('Oppenheimer')">
      <label>Oppenheimer</label>
      <input type="submit" value="Add">
    </form>
    <form @submit.prevent="flipLater('Dead Poets Society')">
      <label>Dead Poets Society</label>
      <input type="submit" value="Add">
    </form>
    <div id="later">
      <h2>Watch Later</h2>
      <ul>
        <li v-for="(film, idx) in laterQueue" :key="idx">
          <h4>{{ film }}
            <button @click="purgeLater(idx)">Remove</button>
          </h4>
        </li>
      </ul>
    </div>
  </div>
  <script type="text/python">
from browser import window
# Note: no `self` parameter
def flipLater(title, *_):
    if title in ui.laterQueue:
        ui.laterQueue.remove(title)
    else:
        ui.laterQueue.append(title)
def purgeLater(idx):
    ui.laterQueue.pop(idx)
ui = window.Vue.new({
    'el': '#app',
    'data': {
        'laterQueue': []
    },
    'methods': {
        'flipLater': flipLater,
        'purgeLater': purgeLater
    }
})
  </script>
</body>
</html>

Fix 2: Use javascript.this() from Brython

This approach pulls the current Vue instance via Brython’s javascript module, then updates state on that object. It mirrors how this works in plain JavaScript.

<script type="text/python">
import javascript
def flipLater(title, *_):
    that = javascript.this()
    if title in that.laterQueue:
        that.laterQueue.remove(title)
    else:
        that.laterQueue.append(title)
def purgeLater(idx):
    that = javascript.this()
    that.laterQueue.pop(idx)
</script>

The module reference can be found in Brython’s documentation for the javascript module. This is not plain JavaScript code, but it is conceptually similar to using this in JavaScript.

Why this matters

Vue’s method binding semantics and Python’s method conventions are not interchangeable. When you mix a JS framework with a Python runtime like Brython, assuming that self gets auto-bound the way it would in a class method is enough to derail event handling. Understanding how Vue invokes methods, how arguments are passed from templates, and where to read the component instance in Brython prevents silent failures and speeds up debugging. The DevTools error about a missing positional argument is the first hint something is off with the function signature. From there, validating calls and printing to the JavaScript console helps zero in on the mismatch quickly.

Conclusion

Keep Vue methods in Brython free of self unless you deliberately supply it. Either work with a shared Vue instance variable or retrieve the current instance using javascript.this(). Both paths align the call signature with what Vue actually sends, and the Watch Later list will start behaving as intended: Add toggles entries, and Remove pops them out. When in doubt, watch DevTools for TypeError messages and use console.log to confirm what arguments your handlers receive.