=====================================
zeam.form.ztk list collection widgets
=====================================

First we need some test interfaces that uses collections::

  >>> from zope import schema, interface
  >>> from zope.component import getMultiAdapter
  >>> from zope.interface.verify import verifyObject

  >>> class IListOfTextlines(interface.Interface):
  ...     names = schema.List(
  ...         title=u"People Names",
  ...         value_type=schema.TextLine(required=False),
  ...         required=False)

  >>> class IRequiredListOfBools(interface.Interface):
  ...     assertions = schema.List(
  ...         title=u"Assertions result",
  ...         value_type=schema.Bool(required=True),
  ...         required=True)

  >>> class ListOfTextlines(object):
  ...    interface.implements(IListOfTextlines)
  ...    names = None

  >>> content = ListOfTextlines()
  >>> content.names = list()

Field
-----

So now you can create a field, and get a collection field::

  >>> from zeam.form.base import interfaces
  >>> from zeam.form.base import Fields
  >>> from zeam.form.ztk.interfaces import ICollectionSchemaField
  >>> from zeam.form.ztk.interfaces import IListSchemaField

  >>> fields = Fields(IListOfTextlines)
  >>> field = fields['names']
  >>> field
  <ListSchemaField People Names>
  >>> verifyObject(ICollectionSchemaField, field)
  True
  >>> ICollectionSchemaField.extends(interfaces.IField)
  True
  >>> verifyObject(IListSchemaField, field)
  True
  >>> IListSchemaField.extends(ICollectionSchemaField)
  True
  >>> field.required
  False

This field works on list, and contains text lines:

  >>> field.collectionType
  <type 'list'>
  >>> value_field = field.valueField
  >>> value_field
  <TextLineSchemaField >
  >>> verifyObject(interfaces.IField, value_field)
  True
  >>> value_field.required
  False

By default, ``allowAdding``, ``allowRemove``, and ``allowOrdering``
are on. ``inlineValidation`` is not::

  >>> field.allowAdding
  True
  >>> field.allowRemove
  True
  >>> field.allowOrdering
  True
  >>> field.inlineValidation
  False

On a required list of required booleans:

  >>> required_fields = Fields(IRequiredListOfBools)
  >>> required_field = required_fields['assertions']
  >>> required_field
  <ListSchemaField Assertions result>
  >>> verifyObject(ICollectionSchemaField, required_field)
  True
  >>> required_field.required
  True
  >>> required_field.collectionType
  <type 'list'>
  >>> required_value_field = required_field.valueField
  >>> required_value_field
  <BooleanSchemaField >
  >>> verifyObject(interfaces.IField, required_value_field)
  True
  >>> required_value_field.required
  True

Validation
~~~~~~~~~~

Validation should validate only a list of textlines:

  >>> from zeam.form.base.markers import NO_VALUE
  >>> field.validate(NO_VALUE, None)
  >>> field.validate([], None)
  >>> field.validate([u'paul', u'henri'], None)

And fail in other conditions:

  >>> field.validate(set(), None)
  u'Object is of wrong type.'
  >>> field.validate([4, 5, 6], None)
  u'Wrong contained type'

If the field is required you cannot provide empty lists:

  >>> required_field.validate(NO_VALUE, None)
  u'Missing required value.'
  >>> required_field.validate([], None)
  u'Missing required value.'
  >>> required_field.validate([True, False, True], None)

Widget
------

You can get a generic widget out of your field::

  >>> from zeam.form.base import Widgets, FormData
  >>> from zope.publisher.browser import TestRequest

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

  >>> widgets = Widgets(fields, form=form, request=request)
  >>> widgets.update()
  >>> widget = widgets['form.field.names']
  >>> widget
  <ListGenericFieldWidget People Names>
  >>> verifyObject(interfaces.IFieldWidget, widget)
  True

The widget create a list of sub-widget to edit each value of the list.
But since our list of values was empty in our contant, we have no
widgets here. An another side effect is that we can't remove values as
well::

  >>> widget.valueField
  <TextLineSchemaField >
  >>> len(widget.valueWidgets)
  0
  >>> widget.allowAdding
  True
  >>> widget.allowRemove
  True
  >>> widget.inlineValidation
  False
  >>> widget.allowOrdering
  True

Let's now create a widget which have values, and we will see that we
have a sub-widget for each entry of the collection::

  >>> list_content = ListOfTextlines()
  >>> list_content.names = [u'Arthur', u'Henri', u'Paul']

  >>> list_form = FormData(list_content, request)
  >>> list_form.ignoreContent = False

  >>> list_widgets = Widgets(
  ...     fields, form=list_form, request=request)

  >>> list_widgets.update()
  >>> list_widget = list_widgets['form.field.names']
  >>> list_widget
  <ListGenericFieldWidget People Names>

  >>> len(list_widget.valueWidgets)
  3
  >>> list_widget.allowAdding
  True
  >>> list_widget.allowRemove
  True
  >>> list(list_widget.valueWidgets)
  [<TextLineWidget form.field.names.field.0>,
   <TextLineWidget form.field.names.field.1>,
   <TextLineWidget form.field.names.field.2>]
  >>> map(lambda w:w.value, list_widget.valueWidgets)
  [{'form.field.names.field.0': u'Arthur'},
   {'form.field.names.field.1': u'Henri'},
   {'form.field.names.field.2': u'Paul'}]

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

The widget is able to get its data from the request. Sub-widgets will be
created for each data value found in the request::

  >>> list_request = TestRequest(form={
  ...     'form.field.names': u'2',
  ...     'form.field.names.present.0': u'1',
  ...     'form.field.names.field.0': u'Nicolas',
  ...     'form.field.names.present.1': u'1',
  ...     'form.field.names.field.1': u'Jean',})

This request parsed with our widget we will create two sub-widget, one
with the value ``Nicolas``, the other with the value ``Jean``::

  >>> list_form = FormData(content, list_request)

  >>> list_widgets = Widgets(
  ...     fields,
  ...     form=list_form,
  ...     request=list_request)

  >>> list_widgets.update()
  >>> list_widget = list_widgets['form.field.names']
  >>> len(list_widget.valueWidgets)
  2
  >>> list_widget.allowAdding
  True
  >>> list_widget.allowRemove
  True
  >>> list(list_widget.valueWidgets)
  [<TextLineWidget form.field.names.field.0>,
   <TextLineWidget form.field.names.field.1>]
  >>> map(lambda w:w.value, list_widget.valueWidgets)
  [{'form.field.names.field.0': u'Nicolas'},
   {'form.field.names.field.1': u'Jean'}]
  >>> list_widget.error

Removing input from the request
...............................

A request can ask to remove one (or more) of the values contained in
the request::

  >>> list_remove_request = TestRequest(form={
  ...     'form.field.names': u'2',
  ...     'form.field.names.remove': u'Remove',
  ...     'form.field.names.checked.0': u'checked',
  ...     'form.field.names.present.0': u'1',
  ...     'form.field.names.field.0': u'Nicolas',
  ...     'form.field.names.present.1': u'1',
  ...     'form.field.names.field.1': u'Jean',})

And if we parse this request, we will have only one sub-widget created
for the second value, the first will have been ignored (and so
removed), and there is no error::

  >>> list_remove_form = FormData(content, list_remove_request)

  >>> list_remove_widgets = Widgets(
  ...     fields,
  ...     form=list_remove_form,
  ...     request=list_remove_request)

  >>> list_remove_widgets.update()
  >>> list_remove_widget = list_remove_widgets['form.field.names']
  >>> len(list_remove_widget.valueWidgets)
  1
  >>> list_remove_widget.allowAdding
  True
  >>> list_remove_widget.allowRemove
  True
  >>> list_remove_widget.inlineValidation
  False
  >>> list(list_remove_widget.valueWidgets)
  [<TextLineWidget form.field.names.field.1>]
  >>> map(lambda w:w.value, list_remove_widget.valueWidgets)
  [{'form.field.names.field.1': u'Jean'}]
  >>> list_remove_widget.error

There is a possibility that the user made a mistake, and click on
remove after. We want the data to be validated if the option
``inlineValidation`` is on::

  >>> inline_fields = Fields(field.clone())
  >>> inline_fields['names'].inlineValidation = True

  >>> list_remove_inline_widgets = Widgets(
  ...     inline_fields,
  ...     form=list_remove_form,
  ...     request=list_remove_request)

  >>> list_remove_inline_widgets.update()
  >>> list_remove_inline_widget = list_remove_inline_widgets['form.field.names']
  >>> list_remove_inline_widget.allowAdding
  True
  >>> list_remove_inline_widget.allowRemove
  True
  >>> list_remove_inline_widget.inlineValidation
  True
  >>> list(list_remove_inline_widget.valueWidgets)
  [<TextLineWidget form.field.names.field.1>]
  >>> map(lambda w:w.value, list_remove_inline_widget.valueWidgets)
  [{'form.field.names.field.1': u'Jean'}]
  >>> list_remove_inline_widget.error

Adding input from the request
.............................

In the same way, a request can ask to add a new empty sub-widget in
order to be able to enter a new value::

  >>> list_add_request = TestRequest(form={
  ...     'form.field.names': u'3',
  ...     'form.field.names.add': u'Add',
  ...     'form.field.names.present.1': u'1',
  ...     'form.field.names.field.1': u'Jean',})

With that request the widget will create two widgets, and one (new)
empty::

  >>> list_add_form = FormData(content, list_add_request)

  >>> list_add_widgets = Widgets(
  ...     fields,
  ...     form=list_add_form,
  ...     request=list_add_request)

  >>> list_add_widgets.update()
  >>> list_add_widget = list_add_widgets['form.field.names']
  >>> len(list_add_widget.valueWidgets)
  2
  >>> list_add_widget.allowAdding
  True
  >>> list_add_widget.allowRemove
  True
  >>> list_add_widget.inlineValidation
  False
  >>> list(list_add_widget.valueWidgets)
  [<TextLineWidget form.field.names.field.1>,
   <TextLineWidget form.field.names.field.3>]
  >>> map(lambda w:w.value, list_add_widget.valueWidgets)
  [{'form.field.names.field.1': u'Jean'},
   {'form.field.names.field.3': u''}]

Empty request
.............

With an empty request, you will have no values, and won't be able to
remove values::

  >>> list_empty_form = FormData(content, request)

  >>> list_empty_widgets = Widgets(
  ...     fields,
  ...     form=list_empty_form,
  ...     request=request)

  >>> list_empty_widgets.update()
  >>> list_empty_widget = list_empty_widgets['form.field.names']
  >>> len(list_empty_widget.valueWidgets)
  0
  >>> list_empty_widget.allowAdding
  True
  >>> list_empty_widget.allowRemove
  True
  >>> list_empty_widget.inlineValidation
  False

And you have the same with a request containing just the marker::

  >>> list_almost_empty_request = TestRequest(form={
  ...     'form.field.names': u'4'})
  >>> list_almost_empty_form = FormData(content, list_almost_empty_request)

  >>> list_almost_empty_widgets = Widgets(
  ...     fields,
  ...     form=list_almost_empty_form,
  ...     request=list_almost_empty_request)

  >>> list_almost_empty_widgets.update()
  >>> list_almost_empty_widget = list_almost_empty_widgets['form.field.names']
  >>> len(list_almost_empty_widget.valueWidgets)
  0
  >>> list_almost_empty_widget.allowAdding
  True
  >>> list_almost_empty_widget.allowRemove
  True

Generic Widget extractor
~~~~~~~~~~~~~~~~~~~~~~~~

You are able to extract values from the request using the
corresponding widget extractor::

  >>> list_extractor = getMultiAdapter(
  ...      (field, list_form, list_request),
  ...      interfaces.IWidgetExtractor)

  >>> list_extractor
  <zeam.form.ztk.widgets.collection.MultiGenericWidgetExtractor object at ...>
  >>> verifyObject(interfaces.IWidgetExtractor, list_extractor)
  True

And you are able to get back your values:

  >>> list_extractor.extract()
  ([u'Nicolas', u'Jean'], None)

It will work as well if you have no values in your request::

  >>> noPeopleRequest = TestRequest(form={
  ...     'form.field.names': u'0'})
  >>> noPeopleInvitation = FormData(content, noPeopleRequest)

  >>> noPeopleExtractor = getMultiAdapter(
  ...      (field, noPeopleInvitation, noPeopleRequest),
  ...      interfaces.IWidgetExtractor)
  >>> noPeopleExtractor.extract()
  ([], None)

If you have a fully empty request::

  >>> noPeopleInvitation = FormData(content, request)

  >>> noPeopleExtractor = getMultiAdapter(
  ...      (field, noPeopleInvitation, request),
  ...      interfaces.IWidgetExtractor)
  >>> noPeopleExtractor.extract()
  (<Marker NO_VALUE>, None)

