========================
Attribute Value Adapters
========================

In advanced, highly customized projects it is often the case that a property
wants to be overridden for a particular customer in a particular case. A prime
example is the label of a widget. Until this implementation of a form
framework was written, widgets only could get their label from the field they
were representing. Thus, wanting to change the label of a widget meant
implementing a custom schema and re-registering the form in question for the
custom schema. It is needless to say that this was very annoying.

For this form framework, we are providing multiple levels of customization. 
The user has the choice to change the value of an attribute through attribute 
assignment or adapter lookup. The chronological order of an attribute value 
assignment is as follows:

1. During initialization or right thereafter, the attribute value can be set
   by direct attribute assignment, i.e. ``obj.attr = value``

2. While updating the object, an adapter is looked up for the attribute. If an
   adapter is found, the attribute value will be overridden. Of course, if the
   object does not have an ``update()`` method, one can choose another
   location to do the adapter lookup.

3. After updating, the developer again has the choice to override the attribute
   allowing granularity above and beyond the adapter.

The purpose of this module is to implement the availability of an attribute
value using an adapter.

  >>> from z3c.form import value

The module provides helper functions and classes, to create those adapters
with as little code as possible.


Static Value Adapter
--------------------

To demonstrate the static value adapter, let's go back to our widget label
example. Let's create a couple of simple widgets and forms first:

  >>> class TextWidget(object):
  ...    label = u'Text'
  >>> tw = TextWidget()

  >>> class CheckboxWidget(object):
  ...    label = u'Checkbox'
  >>> cbw = CheckboxWidget()

  >>> class Form1(object):
  ...    pass
  >>> form1 = Form1()

  >>> class Form2(object):
  ...    pass
  >>> form2 = Form2()

We can now create a generic widget property adapter:

  >>> WidgetAttribute = value.StaticValueCreator(
  ...     discriminators = ('widget', 'view')
  ...     )

Creating the widget attribute object, using the helper function above, allows
us to define the discriminators (or the granulatrity) that can be used to
control a widget attribute by an adapter. In our case this is the widget
itself and the form/view in which the widget is displayed. In other words, it
will be possible to register a widget attribute value specifically for a
particular widget, a particular form, or a combination thereof.

Let's now create a label attribute adapter for the text widget, since our
customer does not like the default label:

  >>> TextLabel = WidgetAttribute(u'My Text', widget=TextWidget)

The first argument of any static attribute value is the value itself, in our
case the string "My Text". The following keyword arguments are the
discriminators specified in the property factory. Since we only specify the
widget, the label will be available to all widgets. But first we have to
register the adapter:

  >>> import zope.component
  >>> zope.component.provideAdapter(TextLabel, name='label')

The name of the adapter is the attribute name of the widget. Let's now see how
we can get the label:

  >>> from z3c.form import interfaces
  >>> staticValue = zope.component.getMultiAdapter(
  ...     (tw, form1), interfaces.IValue, name='label')
  >>> staticValue
  <StaticValue u'My Text'>

The resulting value object has one public method ``get()``, which returns the
actual value:

  >>> staticValue.get()
  u'My Text'

As we said before, the value should be available to all forms, ...

  >>> zope.component.getMultiAdapter(
  ...     (tw, form2), interfaces.IValue, name='label')
  <StaticValue u'My Text'>

... but only to the ``TextWidget``:

  >>> zope.component.getMultiAdapter(
  ...     (cbw, form2), interfaces.IValue, name='label')
  Traceback (most recent call last):
  ...
  ComponentLookupError: ((<CheckboxWidget...>, <Form2...>),
                          <InterfaceClass ...IValue>, 'label')

By the way, the attribute adapter factory notices, if you specify a
discriminator that was not specified:

  >>> WidgetAttribute(u'My Text', form=Form2)
  Traceback (most recent call last):
  ...
  ValueError: One or more keyword arguments did not match the discriminators.

  >>> WidgetAttribute.discriminators
  ('widget', 'view')


Computed Value Adapter
----------------------

A second implementation of the value adapter in the evaluated value, where one
can specify a function that computes the value to be returned. The only
argument to the function is the value adapter instance itself, which then
contains all the discriminators as specified when creating the generic widget
attribute factory. Let's take the same use case as before, but generating the
value as follows:

  >>> def getLabelValue(adapter):
  ...     return adapter.widget.label + ' (1)'

Now we create the value adapter for it:

  >>> WidgetAttribute = value.ComputedValueCreator(
  ...     discriminators = ('widget', 'view')
  ...     )

  >>> TextLabel = WidgetAttribute(getLabelValue, widget=TextWidget)

After registering the adapter, ...

  >>> zope.component.provideAdapter(TextLabel, name='label')

we now get the answers:

  >>> from z3c.form import interfaces
  >>> zope.component.getMultiAdapter(
  ...     (tw, form1), interfaces.IValue, name='label')
  <ComputedValue u'Text (1)'>


__Note__: The two implementations of the attribute value adapters are not
          meant to be canonical features that must always be used. The API is
          kept simple to allow you to quickly implement your own value
          adapter.


Automatic Interface Assignment
------------------------------

Oftentimes it is desirable to register an attribute value adapter for an
instance. A good example is a field, so let's create a small schema:

  >>> import zope.interface
  >>> import zope.schema
  >>> class IPerson(zope.interface.Interface):
  ...     firstName = zope.schema.TextLine(title=u'First Name')
  ...     lastName = zope.schema.TextLine(title=u'Last Name')

The customer now requires that the title -- which is the basis of the widget
label for field widgets -- of the last name should be "Surname". Until now the
option was to write a new schema changing the title. With this attribute value
module, as introduced thus far, we would need to provide a special interface
for the last name field, since registering a label adapter for all text fields
would also change the first name.

Before demonstrating the solution to this problem, let's first create a field
attribute value:

  >>> FieldAttribute = value.StaticValueCreator(
  ...     discriminators = ('field',)
  ...     )

We can now create the last name title, changing only the title of the
``lastName`` field. Instead of passing in an interface of class as the field
discriminator, we pass in the field instance:

  >>> LastNameTitle = FieldAttribute(u'Surname', field=IPerson['lastName'])

The attribute value factory will automatically detect instances, create an
interface on the fly, directly provide it on the field and makes it the
discriminator interface for the adapter registratioon.

So after registering the adapter, ...

  >>> zope.component.provideAdapter(LastNameTitle, name='title')

the adapter is only available to the last name field and not the first name:

  >>> zope.component.queryMultiAdapter(
  ...     (IPerson['lastName'],), interfaces.IValue, name='title')
  <StaticValue u'Surname'>

  >>> zope.component.queryMultiAdapter(
  ...     (IPerson['firstName'],), interfaces.IValue, name='title')
