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.
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
andlen(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.