================================
zeam.form.ztk 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
  >>> from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm

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

  >>> drinks = SimpleVocabulary([
  ...     SimpleTerm('vodka', title=u'Vodka'),
  ...     SimpleTerm('tequila', title=u'Tequila'),
  ...     SimpleTerm('cognac', title=u'Cognac'),
  ...     SimpleTerm('champagne', title=u'Champagne')])

  >>> class ISetOfDrinks(interface.Interface):
  ...     drinks = schema.Set(
  ...         title=u"My beverages",
  ...         value_type=schema.Choice(source=drinks))

And we will need a content and a request to test::

  >>> from zope.publisher.browser import TestRequest
  >>> request = TestRequest()

  >>> class ListOfNames(object):
  ...    interface.implements(IListOfNames)
  ...    names = None

  >>> class SetOfDrinks(object):
  ...    interface.implements(ISetOfDrinks)
  ...    drinks = None

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

  >>> drinkableContent = SetOfDrinks()
  >>> drinkableContent.drinks = set()

Field
-----

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

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

  >>> listFields = Fields(IListOfNames)
  >>> listField = listFields['names']
  >>> listField
  <CollectionSchemaField People Names>
  >>> verifyObject(ICollectionSchemaField, listField)
  True
  >>> ICollectionSchemaField.extends(interfaces.IField)
  True

This field works on list, and contains text lines::

  >>> listField.collectionType
  <type 'list'>
  >>> valueField = listField.valueField
  >>> valueField
  <TextLineSchemaField >
  >>> verifyObject(interfaces.IField, valueField)
  True

However you can have different type of collection, like this is a set
of choice::

  >>> drinkFields = Fields(ISetOfDrinks)
  >>> drinkField = drinkFields['drinks']
  >>> drinkField
  <SetSchemaField My beverages>

And like the list collection::

  >>> drinkField.collectionType
  <type 'set'>
  >>> valueField = drinkField.valueField
  >>> valueField
  <ChoiceSchemaField >
  >>> verifyObject(interfaces.IField, drinkField)
  True


Generic Widget
--------------

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

  >>> from zeam.form.ztk import Widgets, FormData

  >>> emptyList = FormData(content, request)
  >>> emptyList.ignoreContent = False

  >>> listWidgets = Widgets(
  ...     listFields,
  ...     form=emptyList,
  ...     request=request)

  >>> listWidgets.update()
  >>> listWidget = listWidgets['form.field.names']
  >>> listWidget
  <MultiGenericFieldWidget People Names>
  >>> verifyObject(interfaces.IFieldWidget, listWidget)
  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::

  >>> listWidget.valueField
  <TextLineSchemaField >
  >>> len(listWidget.valueWidgets)
  0
  >>> listWidget.allowAdding
  True
  >>> listWidget.allowRemove
  False

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::

  >>> inviteList = ListOfNames()
  >>> inviteList.names = [u'Arthur', u'Henri', u'Paul']

  >>> listInvitation = FormData(inviteList, request)
  >>> listInvitation.ignoreContent = False

  >>> invitationWidgets = Widgets(
  ...     listFields,
  ...     form=listInvitation,
  ...     request=request)

  >>> invitationWidgets.update()
  >>> invitationWidget = invitationWidgets['form.field.names']
  >>> invitationWidget
  <MultiGenericFieldWidget People Names>

  >>> len(invitationWidget.valueWidgets)
  3
  >>> invitationWidget.allowAdding
  True
  >>> invitationWidget.allowRemove
  True
  >>> list(invitationWidget.valueWidgets)
  [<TextLineWidget form.field.names.field.0>,
   <TextLineWidget form.field.names.field.1>,
   <TextLineWidget form.field.names.field.2>]
  >>> map(lambda w:w.value, invitationWidget.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::

  >>> peopleRequest = 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``::

  >>> peopleInvitation = FormData(content, peopleRequest)

  >>> peopleWidgets = Widgets(
  ...     listFields,
  ...     form=peopleInvitation,
  ...     request=peopleRequest)

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

A request can ask to remove one of the values contained in the request::

  >>> peopleRequest = 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)::

  >>> peopleInvitation = FormData(content, peopleRequest)

  >>> peopleWidgets = Widgets(
  ...     listFields,
  ...     form=peopleInvitation,
  ...     request=peopleRequest)

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

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::

  >>> peopleRequest = 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::

  >>> peopleInvitation = FormData(content, peopleRequest)

  >>> peopleWidgets = Widgets(
  ...     listFields,
  ...     form=peopleInvitation,
  ...     request=peopleRequest)

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

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

  >>> peopleInvitation = FormData(content, request)

  >>> peopleWidgets = Widgets(
  ...     listFields,
  ...     form=peopleInvitation,
  ...     request=request)

  >>> peopleWidgets.update()
  >>> peopleWidget = peopleWidgets['form.field.names']
  >>> len(peopleWidget.valueWidgets)
  0
  >>> peopleWidget.allowAdding
  True
  >>> peopleWidget.allowRemove
  False

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

  >>> zeroPeopleRequest = TestRequest(form={
  ...     'form.field.names': u'4'})
  >>> peopleInvitation = FormData(content, request)

  >>> peopleWidgets = Widgets(
  ...     listFields,
  ...     form=peopleInvitation,
  ...     request=zeroPeopleRequest)

  >>> peopleWidgets.update()
  >>> peopleWidget = peopleWidgets['form.field.names']
  >>> len(peopleWidget.valueWidgets)
  0
  >>> peopleWidget.allowAdding
  True
  >>> peopleWidget.allowRemove
  False

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

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

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

  >>> peopleInvitation = FormData(content, peopleRequest)

  >>> peopleExtractor = getMultiAdapter(
  ...      (listField, peopleInvitation, peopleRequest),
  ...      interfaces.IWidgetExtractor)

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

And you are able to get back your values:

  >>> peopleExtractor.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(
  ...      (listField, noPeopleInvitation, noPeopleRequest),
  ...      interfaces.IWidgetExtractor)
  >>> noPeopleExtractor.extract()
  ([], None)

If you have a fully empty request::

  >>> noPeopleInvitation = FormData(content, request)

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


Multi choice widget
-------------------

Collection fields can get by default a widget and extractor picked
differently dependently of the value type. When the value type is a
choice, you will get a multi-choice widget by default::

  >>> drinkableContent.drinks = set(['vodka', 'champagne'])

  >>> someDrinks = FormData(drinkableContent, request)
  >>> someDrinks.ignoreContent = False

  >>> drinkWidgets = Widgets(
  ...     drinkFields,
  ...     form=someDrinks,
  ...     request=request)

  >>> drinkWidgets.update()
  >>> drinkWidget = drinkWidgets['form.field.drinks']
  >>> drinkWidget
  <MultiChoiceFieldWidget My beverages>
  >>> verifyObject(interfaces.IFieldWidget, drinkWidget)
  True

The widget will provides a list of choice that you can render. The
ones already selected in the content will already by marked::

  >>> list(drinkWidget.renderableChoice())
  [{'token': 'vodka', 'checked': 'checked',
    'id': 'form-field-drinks-vodka', 'title': u'Vodka'},
   {'token': 'tequila', 'checked': None,
    'id': 'form-field-drinks-tequila', 'title': u'Tequila'},
   {'token': 'cognac', 'checked': None,
    'id': 'form-field-drinks-cognac', 'title': u'Cognac'},
   {'token': 'champagne', 'checked': 'checked',
    'id': 'form-field-drinks-champagne', 'title': u'Champagne'}]

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

The widget is able to have input from the request only. The present
marker is used in case the user select nothing, because in that case
the browser will send nothing and it would sounds like nothing was in
the request::

  >>> drinksRequest = TestRequest(form={
  ...     'form.field.drinks': [u'vodka', u'tequila'],
  ...     'form.field.drinks.present' : u'1'})
  >>> myChoiceOfDrinks = FormData(drinkableContent, drinksRequest)

  >>> myChoiceOfDrinksWidgets = Widgets(
  ...     drinkFields,
  ...     form=myChoiceOfDrinks,
  ...     request=drinksRequest)

  >>> myChoiceOfDrinksWidgets.update()
  >>> myChoiceOfDrinksWidget = myChoiceOfDrinksWidgets['form.field.drinks']

And so now you have only Vodka and Tequila of selected by the widget::

  >>> list(myChoiceOfDrinksWidget.renderableChoice())
  [{'token': 'vodka', 'checked': 'checked',
    'id': 'form-field-drinks-vodka', 'title': u'Vodka'},
   {'token': 'tequila', 'checked': 'checked',
    'id': 'form-field-drinks-tequila', 'title': u'Tequila'},
   {'token': 'cognac', 'checked': None,
    'id': 'form-field-drinks-cognac', 'title': u'Cognac'},
   {'token': 'champagne', 'checked': None,
    'id': 'form-field-drinks-champagne', 'title': u'Champagne'}]

However the user can uncheck all available choices::

  >>> noMoreDrinksRequest = TestRequest(form={
  ...     'form.field.drinks.present' : u'1'})
  >>> noMoreDrinks = FormData(drinkableContent, noMoreDrinksRequest)

  >>> noMoreDrinksWidgets = Widgets(
  ...     drinkFields,
  ...     form=noMoreDrinks,
  ...     request=noMoreDrinksRequest)

  >>> noMoreDrinksWidgets.update()
  >>> noMoreDrinksWidget = noMoreDrinksWidgets['form.field.drinks']

  >>> list(noMoreDrinksWidget.renderableChoice())
  [{'token': 'vodka', 'checked': None,
    'id': 'form-field-drinks-vodka', 'title': u'Vodka'},
   {'token': 'tequila', 'checked': None,
    'id': 'form-field-drinks-tequila', 'title': u'Tequila'},
   {'token': 'cognac', 'checked': None,
    'id': 'form-field-drinks-cognac', 'title': u'Cognac'},
   {'token': 'champagne', 'checked': None,
    'id': 'form-field-drinks-champagne', 'title': u'Champagne'}]


Multi Choice Widget extractor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You need to be able to get the corresponding extractor with
multi-choice widgets::

  >>> drinksExtractor = getMultiAdapter(
  ...      (drinkField, myChoiceOfDrinks, drinksRequest),
  ...      interfaces.IWidgetExtractor)

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

And you can get your values out of it::

  >>> drinksExtractor.extract()
  (set(['vodka', 'tequila']), None)

If you selected nothing, you will get an empty selection:

  >>> noMoreDrinksExtractor = getMultiAdapter(
  ...      (drinkField, noMoreDrinks, noMoreDrinksRequest),
  ...      interfaces.IWidgetExtractor)

  >>> verifyObject(interfaces.IWidgetExtractor, noMoreDrinksExtractor)
  True
  >>> noMoreDrinksExtractor.extract()
  (set([]), None)

If you have nothing in your request, you will get nothing::

  >>> noDrinksExtractor = getMultiAdapter(
  ...      (drinkField, someDrinks, request),
  ...      interfaces.IWidgetExtractor)

  >>> verifyObject(interfaces.IWidgetExtractor, noDrinksExtractor)
  True
  >>> noDrinksExtractor.extract()
  (<Marker NO_VALUE>, None)
