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 anid. This is id is usually generated by can be explicitly set by passing anidargument to thenew_XXXwhen 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 therootroot which is the default root for all elements created under thePageinstance. However, whenever a new prop is created with thenew_propmethod, a new root is created for it. All children under that prop will have theroot_idof 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 definitionallow_children = False, soawewill verify that nonew_XXXmethods 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 toprops, every element can have arbitrary data attached to it in thedatadict.key- The key is the elementidthat is automatically injected to the elementprops. Internally, React uses thekeyprop.style- The element style may be passed to thenew_XXXmethod as an argument. This is just syntactic sugar for adding astyleprop explicitly to the elementprops.
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.