You are currently viewing the new Anvil Editor Docs.
Switch to the Classic Editor Docs
You are currently viewing the Classic Editor Docs.
Switch to the new Anvil Editor Docs

Accessing JavaScript

Interaction with JavaScript objects in Anvil is primarily done through the anvil.js module. This reference provides some details about working with JavaScript objects from Python code.

Check out the Quickstart guide for an introduction applied to an external library.

HTML Forms also have a unique way to interact with their HTML Template JavaScript functions described here.

Accessing the window

You can access any JavaScript function or variable by importing it from the window object. The window object can be thought of as the JavaScript global namespace in the browser where your app runs.

from anvil.js.window import Foo
# Foo is a JavaScript object defined in the JavaScript namespace

Accessing a DOM Node

You can access the DOM node for any Anvil Component in Python:

anvil.js.get_dom_node(component)
# access any anvil component's DOM node

You can also access any HTML elements from Python code using the dom_nodes API. As long as an element has an anvil-name attribute, you can accesss it:

<div anvil-name="my-component"></div>
my_element = self.dom_nodes['my-component']

These objects are real JavaScript HTMLElement objects.

Accessing an External Library

If you’ve found a JavaScript library that you want to use within Anvil, always check the documentation and look for a browser option. Not all JavaScript runs in the browser, and often library authors will provide a JavaScript option specifically for the browser.

When a JavaScript library has a browser version the code will look something like this:

<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>

In the above example, a CDN provider hosts the JavaScript library’s source code. By adding the script tags to our Native Libraries the JavaScript source code will add objects as attributes to the window. We can then import the objects into Python using anvil.js.window. For an example of this approach, see how we load the Quill library in the Quickstart guide.

Almost all JavaScript libraries are available at npmjs. Often you’ll find browser versions of those libraries hosted by CDN providers such as jsdelivr, unpkg or cdnjs. Be aware that finding a JavaScript library on jsdelivr, unpkg or cdnjs does not guarantee its suitability for the browser.

You can avoid using a CDN provider by pasting the JavaScript source code into a JavaScript file in your assets. Then replacing the CDN provided URL with a relative URL.

<script src="_/theme/quill.js"></script>

A script tag with a src attribute is the traditional way to access JavaScript files in the browser.

JavaScript Modules

A more modern approach for working with JavaScript in the browser is to use a JavaScript module. The main difference is that a JavaScript module does not add objects to the window. Instead, you explicitly import objects from the module. The library’s sample code will look something like this:

<script type="module">
  import { v4 as uuid4 } from 'https://jspm.dev/uuid';
  console.log(uuid4());
  //1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed

</script>

With JavaScript modules we don’t need to paste the sample code into our Native Libraries. Instead, we can use anvil.js.import_from(URL) to access the JavaScript objects directly in Python.

import anvil.js
uuid_module = anvil.js.import_from('https://jspm.dev/uuid')
uuid4 = uuid_module.v4
print(uuid4()) # 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed

In the JavaScript code above, v4 is a named import (aliased as uuid4). We access a named import in Python using . notation.

JavaScript modules may also have a default import and you may see sample code like this:

<script type="module">
  import confetti from 'https://cdn.skypack.dev/canvas-confetti';
  confetti(); // 🎉

</script>

Since confetti is not inside curly braces it is the default import (rather than a named import). In Python, the above sample code would become the following:

import anvil.js
confetti_module = anvil.js.import_from('https://cdn.skypack.dev/canvas-confetti')
confetti = confetti_module.default
confetti() # 🎉

anvil.js.import_from() always returns a JavaScript Module object. You can then access the JavaScript objects, as we’ve done above, with . notation.

Tip: skypack.dev is a CDN provider that only provides JavaScript modules for the browser. If you can’t find a browser supported version of your preferred JavaScript library you may find it at skypack.dev.

Return values from anvil.js

When using anvil.js, JavaScript objects will be returned as Python objects. JavaScript strings, numbers, arrays, booleans, null, and undefined are mapped to their Python equivalents.

All other JavaScript objects (including dictionary-like objects) will be returned as proxyobjects. Proxyobjects are Python objects that wrap a JavaScript object and allow you to interact with the underlying JavaScript object from Python code.

from anvil.js.window import Foo
print(type(Foo)) # <class 'Proxy'>

Return values from anvil.js.get_dom_node are also proxyobjects.

dom_node = anvil.js.get_dom_node(self.content_panel)
print(dom_node) # <HTMLDivElement proxyobject>

As is the window object!

print(anvil.js.window) # <Window proxyobject>

Catching Exceptions

When a JavaScript Error is thrown within JavaScript code, it is re-raised in Python as an ExternalError. You can catch JavaScript errors in Python with anvil.js.ExternalError using a try/except block. If you need to access the original JavaScript error, use the property .original_error.

import anvil.js
from anvil.js.window import Foo

try:
    Foo()
except anvil.js.ExternalError as err:
    print(err) # this includes the string output of the original error
    js_error = err.original_error
    print(js_error.message) # most JavaScript Errors have a message property
    print(js_error.name) # most Javsacript Errors have a name property

Working with proxyobjects

Attributes

You can access the attributes on the underlying JavaScript object in Python as you would in JavaScript.

Foo.bar     # use . notation
Foo['bar']  # use [] subscript notation

JavaScript attributes will be returned to Python in the same way as described above i.e. JavaScript strings, numbers, arrays, booleans, null, and undefined will be returned as their Python equivalents. All other JavaScript objects will be returned as proxyobjects.

Calling dir() on a proxyobject, e.g. dir(Foo), will give an indication of the available attributes accessible in Python.

If you are using JavaScript objects from an external library, it is always a good idea to check the documentation.

All proxyobjects have a keys() method e.g. Foo.keys(). Calling keys() returns a list of strings, which represents the underlying JavaScript object’s own property names. It is the equivalent in JavaScript of doing Object.keys(js_object)

All proxyobjects have a get() method e.g. Foo.get('bar', None). The default value will be returned if the attribute is not found. If no default value is provided then None will be used as the default value.

Iteration

If an object is iterable in JavaScript then it will be iterable in Python.
A JavaScript object is only iterable if it has a Symbol.iterator symbol.

from anvil.js.window import jQuery

for element in jQuery('div'):
    # jQuery objects are iterable in JavaScript
    # and therefore iterable in Python
    print(element)

Typically proxyobjects are not iterable - but you can iterate over the keys.

for key in proxy_obj.keys():
    val = proxy_obj[key]

__class__

Accessing .__class__ of a proxyobject will return the constructor of the underlying JavaScript object.

x = Foo()
print(x)           # <Foo proxyobject>
print(x.__class__) # <proxyclass 'Foo'> (the JavaScript constructor)
print(type(x))     # <class 'Proxy'>

True or False

All proxyobjects are considered ‘Truthy’ (i.e. bool(proxy_obj) will be True) unless:

  • the underlying JavaScript object is empty: {}
  • the proxyobject can be called with len and len(proxy_obj) == 0

len()

You can call len() on a proxyobject if the underlying JavaScript object is not a function and has a .length property.

from anvil.js.window import jQuery

print(len(jQuery('div')))
# jQuery objects have .length property and can be called with len()

Converting to Python

A proxyobject, which is a dictionary-like, is easily converted into a Python dictionary.

print(proxy_obj) # proxyobject({'a': 1, 'b': 2})
dict(proxy_obj)  # {'a': 1, 'b': 2}
{**proxy_obj}    # {'a': 1, 'b': 2} 

The above code works for all proxyobjects regardless of the underlying JavaScript object. However, only the keys returned from the .keys() method will be included in the dictionary object. Thus it is unlikely to be suitable for most proxyobjects.

You can determine if the proxyobject is dictionary-like by inspecting it with print().

print(proxy_obj) # proxyobject({'a': 1, 'b': 2}) (dictionary-like)
print(Foo())     # <Foo proxyobject>      (not dictionary-like)

More specifically, a proxyobject is considered dictionary-like when the underlying JavaScript object is an object literal.

Proxyobjects that are dictionary-like can be sent to the server. When a dictionary-like proxyobject is sent to the server, it will arrive as a plain Python dict. Other proxyobjects would need to be mapped to another portable type before being sent to the server.

Calling proxyobjects

If the underlying JavaScript object is callable, then the proxyobject is callable in Python.
Kwargs will not work when calling proxyobjects since kwargs are not supported in JavaScript.

When calling a proxyobject:

  • the arguments will be converted to JavaScript,
  • the underlying JavaScript function will be called with those arguments
  • the return value will be converted to Python.

If you send a Python function to a proxyobject as a callable, the reverse is true. See the Quickstart guide for an example.

You can know if a proxyobject is callable by calling print() on the proxyobject or by using callable().

from anvil.js.window import Foo

print(Foo)
#   <proxyclass 'Foo'>, or
#   <proxyfunction 'Foo'>, or
#   <bound proxymethod 'Foo' of <Window proxyobject>>
print(callable(Foo)) 
# True

Calling with new

Some libraries will include constructors that require the use of the new operator.

When calling a proxyobject, Python will infer whether new should be used based on the context and the underlying JavaScript object being called.

If you need to control when the new operator is used, you can use anvil.js.new or anvil.js.call.

from anvil.js.window import Foo
import anvil.js

anvil.js.new(Foo, 1, 'a')  # Foo will be called with the new keyword
anvil.js.call(Foo, 1, 'a') # Foo will be called without the new keyword
anvil.js.call('Foo')
anvil.js.call(anvil.js.window['Foo'])
anvil.js.window.Foo()
Are three ways of calling a JavaScript function from Python

Calling asynchronous JavaScript APIs

Some JavaScript libraries will use asyncronous Promises. If your JavaScript library includes an api that returns a Promise then you can write this code as syncronouse Python.

Here is an example from Vega (a JavaScript graphing library).

var spec = "https://raw.githubusercontent.com/vega/vega/master/docs/examples/bar-chart.vg.json";

vegaEmbed('#vis', spec).then(function(result) {
    var vega_object = result;
    console.log(vega_object);
}).catch(console.error);

According to the documentation vegaEmbed returns a Promise, which means we can’t access the vega_object until the Promise has resolved. Here’s how you might convert the above code into syncronous Python code in Anvil.

from anvil.js.window import vegaEmbed
dom_node = anvil.js.get_dom_node(self.vega_component) # a component you've added to your design view

spec = "https://raw.githubusercontent.com/vega/vega/master/docs/examples/bar-chart.vg.json"

vega_object = vegaEmbed(dom_node, spec) # replace '#vis' with a dom node
print(vega_object)
# We can wrap this in a try except 
# or let anvil's error handler deal with it

When an asynchronous JavaScript function returns a Promise, Python blocks the execution until the Promise resolves before continuing. If the Promise rejects an exception is raised. The same mechanism described here is also used for anvil.server.call().

If you obtain a Promise object by some other route (for example, as the attribute of an object, or by constructing it directly), you can block until it resolves by calling anvil.js.await_promise(js_promise). This will return the resolved value of the Promise, or raise an exception if it rejects.

Using Python functions as callbacks

You can use Python functions as callbacks, or event handlers, for JavaScript libraries. If you pass a callable Python object, Anvil wraps it as a JavaScript function.

Blocking functions

When a Python function is called from JavaScript, the function returns (or throws an exception) synchronously, like any JavaScript function – unless it blocks. If the function blocks, it will return a Promise that resolves (or rejects) when the Python function returns (or raises an exception).

NB: A Python function blocks if the execution must wait for a process to complete before continuing. If you use sleep() or anvil.server.call() then you have written blocking code.

If you’re using a Python function as a callback, check the documentation of your JavaScript library for the expected return value. Often callbacks, like the one used in our Quickstart, will work as expected regardless of whether the Python function blocks or not.

Note the above behaviour is different when a Python function is called from JavaScript using anvil.call where the return value is always a Promise.

Capturing exceptions in callbacks

Sometimes, the JavaScript library you are using will handle exceptions itself. This can make it frustrating to debug your code, as it prevents Anvil from displaying information about the exception in the Output panel or the App Logs.

You can avoid this problem by adding the @anvil.js.report_exceptions decorator to your Python callback. This decorator will catch any exceptions thrown by that function and handle them via Anvil’s standard error-reporting mechanisms. (The callback will then return or resolve undefined to JavaScript.)

Alternatively, set Anvil’s exception reporting as the default behaviour by calling anvil.js.report_all_exceptions(True).


Do you still have questions?

Our Community Forum is full of helpful information and Anvil experts.