Advanced Example
An advanced example of Enaml templates.
This example shows how Enaml templates can be used to automatically generate the body of a form. Template specialization is used to select the proper control for a model attribute at runtime. Template recursion is then used to unroll a list of these controls into the body of a form.
Screenshot

Example Enaml Code
#------------------------------------------------------------------------------
# Copyright (c) 2013-2024, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
#------------------------------------------------------------------------------
""" An advanced example of Enaml templates.
This example shows how Enaml templates can be used to automatically
generate the body of a form. Template specialization is used to select
the proper control for a model attribute at runtime. Template recursion
is then used to unroll a list of these controls into the body of a form.
<< autodoc-me >>
"""
from __future__ import print_function
from atom.api import Atom, Bool, Enum, Event, Float, Int, Str
from enaml.core.api import DynamicTemplate
from enaml.stdlib.fields import FloatField
from enaml.widgets.api import (
CheckBox, Container, Field, Form, GroupBox, Label, ObjectCombo, PushButton,
SpinBox, Window,
)
#------------------------------------------------------------------------------
# "Libaray" Definitions
#------------------------------------------------------------------------------
# The templates and enamldefs defined in this section are ones which can
# be written once and then used as a library. They are more-or-less fully
# generic and will work for a large swath of models.
template FormControl(Attr, MemberType):
""" A template which generates a control for an AutoForm.
This default specialization displays read-only text for the value.
Parameters
----------
Attr : str
The name of the attribute on 'model' being accessed.
MemberType : type
The type of the member being accessed.
"""
Field:
read_only = True
text << str(getattr(model, Attr))
template FormControl(Attr, MemberType: Int):
""" A form control template specialization for Int members.
This control uses a spin box to represent the value.
"""
SpinBox:
value := getattr(model, Attr)
template FormControl(Attr, MemberType: Str):
""" A form control template specialization for Str members.
This control uses a Field to represent the value.
"""
Field:
text := getattr(model, Attr)
template FormControl(Attr, MemberType: Float):
""" A form control template specialization for Float members.
This control uses a FloatField to represent the value.
"""
FloatField:
value := getattr(model, Attr)
template FormControl(Attr, MemberType: Bool):
""" A form control template specialization for Bool members.
This control uses a CheckBox to represent the value.
"""
CheckBox:
checked := getattr(model, Attr)
template FormControl(Attr, MemberType: Event):
""" A form control template specialization for Event members.
This control uses a PushButton to represent the value.
"""
const ButtonText = Attr[0].upper() + Attr[1:].lower()
PushButton:
text = ButtonText
clicked :: getattr(model, Attr)()
def enum_labels(model, attr):
""" Return the list of enum labels for the given model and attr.
"""
items = getattr(type(model), attr).items
return sorted(items)
template FormControl(Attr, MemberType: Enum):
""" A form control template specialization for Enum members.
This control uses an ObjectCombo to represent the value.
"""
ObjectCombo:
items = enum_labels(model, Attr)
selected := getattr(model, Attr)
template FormItem(Attr, MemberType):
""" A template which generates a pair of items for an AutoForm.
Parameters
----------
Attr : str
The name of the attribute on 'model' being accessed.
MemberType : type
The type of the member being accessed.
"""
const LabelText = Attr[0].upper() + Attr[1:].lower()
Label:
text = LabelText
FormControl(Attr, MemberType):
pass
def form_spec(obtype):
""" Generate a form specification for an atom type.
Parameters
----------
obtype : type
The Atom subclass of interest.
Returns
-------
result : tuple
A tuple of 2-tuples of (attr, member_type) for all non-private
members of the class.
"""
items = []
for name, member in obtype.members().items():
if not name.startswith('_'):
items.append((name, type(member)))
items.sort()
return tuple(items)
template ForEach(Spec, Item):
""" A templated loop which maps a template over a sequence.
Parameters
----------
Spec : tuple
A tuple of tuples which are the values to map over the item.
Item : template
A template which accepts *values from inner tuples of the spec.
"""
ForEach(Spec[:-1], Item):
pass
Item(*Spec[-1]):
pass
template ForEach(Spec: (), Item):
""" The terminating condition for the templated loop.
"""
pass
template AutoFormBody(ModelType):
""" A template which builds the body for an AutoForm.
Parameters
----------
ModelType : type
The type of the model. This should be an Atom subclass.
"""
const Spec: tuple = form_spec(ModelType)
ForEach(Spec, FormItem):
pass
template AutoFormBody(ModelType: type(None)):
""" A template specialization for null models.
"""
pass
enamldef AutoForm(Form):
""" A Form which automatically generates its body from a model.
"""
attr model: Atom
DynamicTemplate:
base = AutoFormBody
args = (type(model),)
#------------------------------------------------------------------------------
# Main Models and Views
#------------------------------------------------------------------------------
class FooModel(Atom):
spam = Int(34)
ham = Int(42)
first = Str('first')
last = Str('last')
owner = Str('owner')
time = Float(42.56)
click = Bool()
clack = Bool()
class BarModel(Atom):
name = Str('name')
trigger = Event()
choices = Enum('first', 'second', 'third')
def _observe_trigger(self, change):
print('I was triggered')
enamldef Main(Window):
title = 'Advanced Templates'
attr foo_model = FooModel()
attr bar_model = BarModel()
Container:
GroupBox:
title = 'Foo Model'
flat = True
AutoForm:
padding = 0
model = foo_model
GroupBox:
title = 'Bar Model'
flat = True
AutoForm:
padding = 0
model = bar_model