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.
.. code-block:: python
from awe import Page, CustomElement
class MyElement(CustomElement):
@classmethod
def _js(cls):
return '''
register(
(element) => {
return (
My Element!
);
}
)
'''
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)
.. code-block:: python
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) =>
argument1: {e.props.argument1}, argument2: {e.data.argument2}
)
'''
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
------------------------------------------------------
.. code-block:: python
from awe import Page, CustomElement
class MyElement(CustomElement):
@classmethod
def _js(cls):
return '''
register((e) =>
user supplied: {e.props.userSuppliedProp}
)
'''
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
----------------------------------
.. code-block:: python
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) =>
counter1: {e.props.counter1},
counter2: {e.props.nested.counter2},
counter3: {e.data.counter3}
)
'''
def main():
page = Page()
element = page.new(MyElement)
page.start()
while True:
element.increment()
time.sleep(1)
if __name__ == '__main__':
main()
Advanced Element Updates
------------------------
.. code-block:: python
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) =>
{e.props.list1.map((item, index) => (list1: {item}))}
{e.data.deque1.map((item, index) => (deque1: {item}))}
{e.data.nested.list2.map((item, index) => (list2: {item}))}
)
'''
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
------------------------
.. code-block:: python
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 (
{element.children}
);
}
}
register((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.