Custom Elements

Anatomy Of An Element

Elements in awe are composed of two parts.

  • The server side - The element definition in Python.
  • The client side - The element definition in JavaScript.

Each element has several properties that make it what it is.

  • element_type - The name of the element type. It is derived from the element type class definition.
  • id - Every element added to the page has an id. This is id is usually generated by can be explicitly set by passing an id argument to the new_XXX when creating the element.
  • parent - Except for root elements, all elements are children of other elements.
  • root_id - Every element added to the page is always part of some root. Most elements are part of the root root which is the default root for all elements created under the Page instance. However, whenever a new prop is created with the new_prop method, a new root is created for it. All children under that prop will have the root_id of the new prop element.
  • index - For each root, every element added to it, gets an index that is incremented by one every time. This index is used internally to add elements in the correct order, but it may be used for other purposes if you see fit.
  • children - Each element may have 0 or more children elements.
  • allow_children - By default, elements always allow children to be created under them. However, an element definition may specify in its class definition allow_children = False, so awe will verify that no new_XXX methods are invoked on it.
  • props - Every elements has a dict of props. They are usually passed as is to the underlying React component but they may be used to hold arbitrary data as well.
  • data - In addition to props, every element can have arbitrary data attached to it in the data dict.
  • key - The key is the element id that is automatically injected to the element props. Internally, React uses the key prop.
  • style - The element style may be passed to the new_XXX method as an argument. This is just syntactic sugar for adding a style prop explicitly to the element props.

The above is exposed to elements in Python and JavaScript with these names:

Name Python Javascript
element_type element_type elementType
id id id
parent parent parentId
root_id root_id rootId
index index index
children children children
allow_children allow_children N/A
props props props
data data data
key props['key'] props.key
style props['style'] props.style

Basic Element

Custom elements in python are classes that extend awe.CustomElement and implement the _js() classmethod.

To use the custom element in a page use the new method.

from awe import Page, CustomElement


class MyElement(CustomElement):

    @classmethod
    def _js(cls):
        return '''
            register(
                (element) => {
                    return (
                        <div {...element.props}>
                            My Element!
                        </div>
                    );
                }
            )
        '''


def main():
    page = Page()
    page.new(MyElement)
    page.start(block=True)


if __name__ == '__main__':
    main()

_init Method

The init method serves as the constructor for the element.

The new method calls the _init with the arguments passed to it on the newly created element. (excluding id, props and style which are handled by new directly)

from awe import Page, CustomElement


class MyElement(CustomElement):

    def _init(self, argument1, argument2='default value'):
        self.update_props({'argument1': argument1})
        self.update_data({'argument2': argument2})

    @classmethod
    def _js(cls):
        return '''
            register((e) => <div key={e.props.key}>
                argument1: {e.props.argument1}, argument2: {e.data.argument2}
            </div>)
        '''


def main():
    page = Page()
    page.new(MyElement, argument1='value 1')
    page.new(MyElement, argument1='value 2', argument2='not the default value')
    page.start(block=True)


if __name__ == '__main__':
    main()

Accepting Additional props During Element Creation

from awe import Page, CustomElement


class MyElement(CustomElement):

    @classmethod
    def _js(cls):
        return '''
            register((e) => <div key={e.props.key} style={e.props.style}>
                user supplied: {e.props.userSuppliedProp}
            </div>)
        '''


def main():
    page = Page()
    page.new(MyElement,
             props={'userSuppliedProp': 'user supplied value'},
             style={'color': '#ff0000'})
    page.start(block=True)


if __name__ == '__main__':
    main()

Updating An Element After Creation

import time

from awe import Page, CustomElement


class MyElement(CustomElement):

    def _init(self):
        self.update_props({
            'counter1': 0,
            'nested': {'counter2': 0}
        })
        self.update_data({'counter3': 0})

    def increment(self):
        self.update_props({'counter1': self.props['counter1'] + 1})
        self.update_prop(['nested', 'counter2'], self.props['nested']['counter2'] + 1)
        self.update_data({'counter3': self.data['counter3'] + 1})

    @classmethod
    def _js(cls):
        return '''
            register((e) => <div key={e.props.key}>
                counter1: {e.props.counter1},
                counter2: {e.props.nested.counter2},
                counter3: {e.data.counter3}
            </div>)
        '''

def main():
    page = Page()
    element = page.new(MyElement)
    page.start()
    while True:
        element.increment()
        time.sleep(1)


if __name__ == '__main__':
    main()

Advanced Element Updates

import time
from collections import deque

from awe import Page, CustomElement


class MyElement(CustomElement):

    def _init(self):
        self.update_props({'list1': []})
        self.update_data({
            'deque1': deque(),
            'nested': {'list2': []}
        })

    def update_things(self):
        now = int(time.time())
        list2_data = [now, now + 1]
        self.props['list1'].append(now)
        self.data['deque1'].appendleft(now)
        self.data['nested']['list2'].extend(list2_data)
        self.update_element(['props', 'list1'], action='append', data=now)
        self.update_element(['data', 'deque1'], action='prepend', data=now)
        self.update_element(['data', 'nested', 'list2'], action='extend', data=list2_data)

    @classmethod
    def _js(cls):
        return '''
            register((e) => <div key={e.props.key}>
                {e.props.list1.map((item, index) => (<span key={index.toString()}>list1: {item}</span>))}
                <br /><br />
                {e.data.deque1.map((item, index) => (<span key={index.toString()}>deque1: {item}</span>))}
                <br /><br />
                {e.data.nested.list2.map((item, index) => (<span key={index.toString()}>list2: {item}</span>))}
            </div>)
        '''


def main():
    page = Page()
    element = page.new(MyElement)
    page.start()
    while True:
        element.update_things()
        time.sleep(1)


if __name__ == '__main__':
    main()

Available Window Globals

from awe import Page, CustomElement


class Popover(CustomElement):

    def _init(self, title):
        self.update_props({'title': title})

    @classmethod
    def _js(cls):
        return '''
            class Popover extends React.Component {
                render() {
                    const element = this.props.element;
                    return (
                        <antd.Popover {...element.props}>
                            {element.children}
                        </antd.Popover>
                    );
                }
            }
            register((e) => <Popover element={e} />)
        '''


def main():
    page = Page()
    popover = page.new(Popover, title='Some Title')
    popover.new_button(lambda: None, 'Hover Me!')
    content = popover.new_prop('content')
    content.new_text('line 1')
    content.new_text('line 2')
    page.start(block=True)


if __name__ == '__main__':
    main()

Variables And Functions

TODO: document these

  • _new_variable
  • element.variables (.value)
  • Awe.updateVariable
  • _register
  • Awe.call

External Scripts And Stylesheets

TODO: document

Custom Update Element Action

TODO: document

Custom Serializers

TODO: document

Note

I stopped documenting the above sections because I am assuming very few people even use the most basic functionally of awe, let alone implement custom elements.

If I am mistaken and you happen to be reading this and wishing the above would include useful information, open an issue on GitHub and I’ll be happy to complete it, knowing someone finds it useful.

Additional Reference

See the Python side implementation of the builtin elements in awe/view.py.

See the JavaScript side implementation of the builtin elements in components/index.js.

They all use the same API used by custom elements.

See the examples/custom_element.py example for a simple full example.