================================================
zeam.form.ztk object widget used in a collection
================================================

We need an interface that defines a list of objects, and an
implementation::

  >>> import datetime
  >>> from zope import schema, interface
  >>> from zope.component import IFactory, provideUtility

  >>> class IProgram(interface.Interface):
  ...     name = schema.TextLine(title=u"Name")
  ...     location = schema.TextLine(title=u"Where")
  ...     date = schema.Date(title=u"When", required=False)

  >>> class IProgramSelection(interface.Interface):
  ...     programs = schema.List(title=u"Program selection",
  ...                            value_type=schema.Object(schema=IProgram),
  ...                            required=True)

  >>> class Program(object):
  ...      interface.implements(IProgram)
  ...      def __init__(self, name=None, location=None, date=None):
  ...           self.name = name
  ...           self.location = location
  ...           self.date = date

  >>> class Selection(object):
  ...      interface.implements(IProgramSelection)
  ...      def __init__(self, programs=[]):
  ...           self.programs = list(programs)

  >>> content = Selection([Program(u'TV', u'at my place', datetime.date(2001, 6, 23))])

  >>> provideUtility(Program, IFactory, name=IProgram.__identifier__)


Field
-----

You can create a field::

  >>> from zeam.form.ztk import Fields

  >>> fields = Fields(IProgramSelection)
  >>> field = fields['programs']
  >>> field
  <ListSchemaField Program selection>

  >>> field.required
  True

  >>> value_field = field.valueField
  >>> value_field
  <ObjectSchemaField >


Validation
~~~~~~~~~~

The field can only validate a list of correctly build objects::

  >>> from zeam.form.base.markers import NO_VALUE
  >>> field.validate(NO_VALUE, None)
  u'Missing required value.'
  >>> field.validate([], None)
  u'Missing required value.'
  >>> field.validate([1, 2, 3], None)
  u'Wrong contained type'

  >>> field.validate([Program(u'TV', u'at my place'),
  ...                 Program(u'Rock', u'in the street'),
  ...                 Program(u'Manga', u'at his place')], None)


Widget
------

You can get a dedicated widget for your list of programs::

  >>> from zeam.form.ztk import Widgets, FormData
  >>> from zope.publisher.browser import TestRequest
  >>> from zeam.form.base import interfaces
  >>> from zope.interface.verify import verifyObject

  >>> request = TestRequest()
  >>> form = FormData(content, request)
  >>> form.ignoreContent = False

  >>> widgets = Widgets(fields, form=form, request=request)
  >>> widgets.update()
  >>> widget = widgets['form.field.programs']
  >>> widget
  <ListObjectFieldWidget Program selection>

  >>> verifyObject(interfaces.IFieldWidget, widget)
  True

You can add and remove values, and have already a value::

  >>> widget.allowAdding
  True
  >>> widget.allowRemove
  True
  >>> list(widget.valueWidgets)
  [<ObjectFieldWidget form.field.programs.field.0>]

  >>> map(lambda w: list(w.objectWidgets), widget.valueWidgets)
  [[<TextLineWidget Name>, <TextLineWidget Where>, <DateFieldWidget When>]]
  >>> map(lambda w: map(lambda x: x.value, w.objectWidgets), widget.valueWidgets)
  [[{'form.field.programs.field.0.field.name': u'TV'},
    {'form.field.programs.field.0.field.location': u'at my place'},
    {'form.field.programs.field.0.field.date': u'01/06/23'}]]

Getting input from the request
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can extract information from the request.

  >>> programs_request = TestRequest(form={
  ...     'form.field.programs': u'2',
  ...     'form.field.programs.present.0': u'1',
  ...     'form.field.programs.field.0': u'1',
  ...     'form.field.programs.field.0.field.name': u'Manga',
  ...     'form.field.programs.field.0.field.location': u'Mangashop',
  ...     'form.field.programs.present.1': u'1',
  ...     'form.field.programs.field.1': u'1',
  ...     'form.field.programs.field.1.field.name': u'TV',
  ...     'form.field.programs.field.1.field.location': u'at my place',
  ...     'form.field.programs.field.1.field.date': u'10/06/23'})

  >>> programs_form = FormData(content, programs_request)

  >>> programs_widgets = Widgets(
  ...     fields,
  ...     form=programs_form,
  ...     request=programs_request)
  >>> programs_widgets.update()
  >>> programs_widget = programs_widgets['form.field.programs']

  >>> list(programs_widget.valueWidgets)
  [<ObjectFieldWidget form.field.programs.field.0>,
   <ObjectFieldWidget form.field.programs.field.1>]

  >>> map(lambda w: list(w.objectWidgets), programs_widget.valueWidgets)
  [[<TextLineWidget Name>, <TextLineWidget Where>, <DateFieldWidget When>],
   [<TextLineWidget Name>, <TextLineWidget Where>, <DateFieldWidget When>]]
  >>> map(lambda w: map(lambda x: x.value, w.objectWidgets), programs_widget.valueWidgets)
  [[{'form.field.programs.field.0.field.name': u'Manga'},
    {'form.field.programs.field.0.field.location': u'Mangashop'},
    {'form.field.programs.field.0.field.date': u''}],
   [{'form.field.programs.field.1.field.name': u'TV'},
    {'form.field.programs.field.1.field.location': u'at my place'},
    {'form.field.programs.field.1.field.date': u'10/06/23'}]]


Widget Extractor
----------------

You can extract data from the request::

  >>> from zope.component import getMultiAdapter

  >>> programs_extractor = getMultiAdapter(
  ...      (field, programs_form, programs_request),
  ...      interfaces.IWidgetExtractor)

  >>> programs_extractor
  <zeam.form.ztk.widgets.collection.MultiGenericWidgetExtractor object at ...>
  >>> value, errors = programs_extractor.extract()
  >>> value
  [<Program object at ...>, <Program object at ...>]
  >>> errors


If the records doesn't validate properly errors are reported::

  >>> programs_invalid_request = TestRequest(form={
  ...     'form.field.programs': u'4',
  ...     'form.field.programs.present.0': u'1',
  ...     'form.field.programs.field.0': u'1',
  ...     'form.field.programs.field.0.field.name': u'Manga',
  ...     'form.field.programs.present.1': u'1',
  ...     'form.field.programs.field.1': u'1',
  ...     'form.field.programs.field.1.field.name': u'TV',
  ...     'form.field.programs.field.1.field.location': u'at my place',
  ...     'form.field.programs.field.1.field.date': u'10/06/23',
  ...     'form.field.programs.present.3': u'1',
  ...     'form.field.programs.field.3': u'1',
  ...     'form.field.programs.field.3.field.location': u'Antwerpen'})

  >>> programs_invalid_form = FormData(content, programs_invalid_request)

  >>> programs_invalid_extractor = getMultiAdapter(
  ...      (field, programs_invalid_form, programs_invalid_request),
  ...      interfaces.IWidgetExtractor)

  >>> programs_invalid_extractor
  <zeam.form.ztk.widgets.collection.MultiGenericWidgetExtractor object at ...>
  >>> value, errors = programs_invalid_extractor.extract()
  >>> value
  >>> errors
  <Errors for 'form.field.programs'>
  >>> list(errors)
  [<Errors for 'form.field.programs.field.0'>,
   <Errors for 'form.field.programs'>,
   <Errors for 'form.field.programs.field.3'>]

If the field is required and the value is missing, an error is reported::

  >>> required_invalid_form = FormData(content, request)
  >>> value, errors = required_invalid_form.extractData(fields)
  >>> value
  {'programs': <Marker NO_VALUE>}
  >>> errors
  <Errors>
  >>> list(errors)
  [<Error Missing required value.>, <Error There were errors.>]


Reporting sub field errors
--------------------------

If you edit data coming from the requests and validation fails, errors
are reported on top of each field::

  >>> programs_invalid_form.extractData(fields)
  ({'programs': None}, <Errors>)
  >>> programs_invalid_form.errors
  <Errors>
  >>> list(programs_invalid_form.errors)
  [<Errors for 'form.field.programs'>, <Error There were errors.>]

  >>> programs_invalid_widgets = Widgets(
  ...     fields,
  ...     form=programs_invalid_form,
  ...     request=programs_invalid_request)

  >>> programs_invalid_widgets.update()
  >>> programs_invalid_widget = programs_invalid_widgets['form.field.programs']

The widgets keep the request values, and report each error correctly::

  >>> from zeam.form.base.interfaces import IError

  >>> programs_invalid_widget.error
  <Errors for 'form.field.programs'>
  >>> verifyObject(IError, programs_invalid_widget.error)
  True
  >>> len(programs_invalid_widget.valueWidgets)
  3
  >>> list(programs_invalid_widget.valueWidgets)
  [<ObjectFieldWidget form.field.programs.field.0>,
   <ObjectFieldWidget form.field.programs.field.1>,
   <ObjectFieldWidget form.field.programs.field.3>]
  >>> map(lambda w: w.error , programs_invalid_widget.valueWidgets)
  [<Errors for 'form.field.programs.field.0'>,
   None,
   <Errors for 'form.field.programs.field.3'>]


If the value was missing, we can still have working widgets:

  >>> required_invalid_widgets = Widgets(
  ...     fields,
  ...     form=required_invalid_form,
  ...     request=request)

  >>> required_invalid_widgets.update()
  >>> required_invalid_widget = required_invalid_widgets['form.field.programs']
  >>> required_invalid_widget.error
  <Error Missing required value.>
