# 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.

Tip

To see this example in action, download it from `advanced` and run:

    $ enaml-run advanced.enaml

## Screenshot

## Example Enaml Code

    #------------------------------------------------------------------------------
    # Copyright (c) 2013-2025, 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