=================================
zeam.form.ztk multichoice widgets
=================================

Define some interfaces that specifcy list of choices:

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

  >>> choices = SimpleVocabulary([
  ...     SimpleTerm(token='vodka', title=u'Vodka', value='42%'),
  ...     SimpleTerm(token='tequila', title=u'Tequila', value='30%'),
  ...     SimpleTerm(token='cognac', title=u'Cognac', value='51%'),
  ...     SimpleTerm(token='champagne', title=u'Champagne', value='12%')])

  >>> class ISetOfChoices(interface.Interface):
  ...     choices = schema.Set(
  ...         title=u"My choices",
  ...         value_type=schema.Choice(source=choices),
  ...         required=False)

  >>> class IRequiredSetOfChoices(interface.Interface):
  ...     choices = schema.Set(
  ...         title=u"My required choices",
  ...         value_type=schema.Choice(source=choices),
  ...         required=True)

Define content for testing purpose:

  >>> class SetOfChoices(object):
  ...    interface.implements(ISetOfChoices)
  ...    choices = None

  >>> class RequiredSetOfChoices(object):
  ...    interface.implements(IRequiredSetOfChoices)
  ...    choices = None

  >>> content = SetOfChoices()
  >>> content.choices = set()
  >>> required_content = RequiredSetOfChoices()
  >>> required_content.choices = set()

Field
-----

We can have a field out of the interface:

  >>> from zeam.form.base import interfaces
  >>> from zeam.form.base.markers import NO_VALUE
  >>> from zeam.form.ztk import Fields
  >>> from zeam.form.ztk.interfaces import ICollectionField

  >>> fields = Fields(ISetOfChoices)
  >>> field = fields['choices']
  >>> field
  <SetField My choices>
  >>> verifyObject(interfaces.IField, field)
  True
  >>> field.required
  False

And the field manage a set collection of choice:

  >>> field.collectionType
  <type 'set'>
  >>> value_field = field.valueField
  >>> value_field
  <ChoiceField >
  >>> verifyObject(interfaces.IField, value_field)
  True

It can be required:

  >>> required_fields = Fields(IRequiredSetOfChoices)
  >>> required_field = required_fields['choices']
  >>> required_field
  <SetField My required choices>
  >>> required_field.required
  True

Validation
~~~~~~~~~~

Validation should validate only set that contains correct choices:

  >>> field.validate(NO_VALUE, None)
  >>> field.validate(set(), None)
  >>> field.validate(set(['42%', '51%']), None)

If the field is required, an empty collection will not validate:

  >>> required_field.validate(NO_VALUE, None)
  u'Missing required value.'
  >>> required_field.validate(set(), None)
  u'Missing required value.'
  >>> required_field.validate(set(['42%', '51%']), 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:

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

  >>> request = TestRequest()
  >>> content.choices = set(['42%', '12%'])

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

  >>> widgets = Widgets(fields, form=form, request=request)

  >>> widgets.update()
  >>> widget = widgets['form.field.choices']
  >>> widget
  <MultiChoiceFieldWidget My choices>
  >>> verifyObject(interfaces.IFieldWidget, widget)
  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(widget.renderableChoice())
  [{'token': 'vodka', 'checked': True,
    'id': 'form-field-choices-0', 'title': u'Vodka'},
   {'token': 'tequila', 'checked': False,
    'id': 'form-field-choices-1', 'title': u'Tequila'},
   {'token': 'cognac', 'checked': False,
    'id': 'form-field-choices-2', 'title': u'Cognac'},
   {'token': 'champagne', 'checked': True,
    'id': 'form-field-choices-3', 'title': u'Champagne'}]

And we can render this widget:

  >>> print widget.render()
  <input type="checkbox" id="form-field-choices-0" name="form.field.choices"
   value="vodka" checked="checked" class="field field-multichoice" />
  <label for="form-field-choices-0">Vodka</label>
  <br />
  <input type="checkbox" id="form-field-choices-1" name="form.field.choices"
   value="tequila" class="field field-multichoice" />
  <label for="form-field-choices-1">Tequila</label>
  <br />
  <input type="checkbox" id="form-field-choices-2" name="form.field.choices"
   value="cognac" class="field field-multichoice" />
  <label for="form-field-choices-2">Cognac</label>
  <br />
  <input type="checkbox" id="form-field-choices-3" name="form.field.choices"
   value="champagne" checked="checked" class="field field-multichoice" />
  <label for="form-field-choices-3">Champagne</label>
  <br />
  <input type="hidden" name="form.field.choices.present" value="1" />


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:

  >>> content_request = TestRequest(form={
  ...     'form.field.choices': [u'vodka', u'tequila'],
  ...     'form.field.choices.present' : u'1'})
  >>> choice_form = FormData(content, content_request)

  >>> choice_widgets = Widgets(fields, form=choice_form, request=content_request)

  >>> choice_widgets.update()
  >>> choice_widget = choice_widgets['form.field.choices']

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

  >>> list(choice_widget.renderableChoice())
  [{'token': 'vodka', 'checked': True,
    'id': 'form-field-choices-0', 'title': u'Vodka'},
   {'token': 'tequila', 'checked': True,
    'id': 'form-field-choices-1', 'title': u'Tequila'},
   {'token': 'cognac', 'checked': False,
    'id': 'form-field-choices-2', 'title': u'Cognac'},
   {'token': 'champagne', 'checked': False,
    'id': 'form-field-choices-3', 'title': u'Champagne'}]

However the user can uncheck all available choices:

  >>> empty_request = TestRequest(form={
  ...     'form.field.choices.present' : u'1'})
  >>> empty_form = FormData(content, empty_request)

  >>> empty_widgets = Widgets(
  ...     fields, form=empty_form, request=empty_request)

  >>> empty_widgets.update()
  >>> empty_widget = empty_widgets['form.field.choices']

  >>> list(empty_widget.renderableChoice())
  [{'token': 'vodka', 'checked': False,
    'id': 'form-field-choices-0', 'title': u'Vodka'},
   {'token': 'tequila', 'checked': False,
    'id': 'form-field-choices-1', 'title': u'Tequila'},
   {'token': 'cognac', 'checked': False,
    'id': 'form-field-choices-2', 'title': u'Cognac'},
   {'token': 'champagne', 'checked': False,
    'id': 'form-field-choices-3', 'title': u'Champagne'}]

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

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

  >>> extractor = getMultiAdapter(
  ...      (field, form, content_request), interfaces.IWidgetExtractor)

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

And you can get your values out of it:

  >>> extractor.extract()
  (set(['42%', '30%']), None)

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

  >>> empty_extractor = getMultiAdapter(
  ...      (field, empty_form, empty_request), interfaces.IWidgetExtractor)

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

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

  >>> empty_extractor = getMultiAdapter(
  ...      (field, form, request), interfaces.IWidgetExtractor)

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

Multi select widget
-------------------

You can use a multi select widget as well:

  >>> request = TestRequest()
  >>> content.choices = set(['42%', '12%'])

  >>> select_form = FormData(content, request)
  >>> select_form.ignoreContent = False
  >>> select_form.mode = 'multiselect'

  >>> select_widgets = Widgets(fields, form=select_form, request=request)

  >>> select_widgets.update()
  >>> select_widget = select_widgets['form.field.choices']
  >>> select_widget
  <MultiSelectFieldWidget My choices>
  >>> verifyObject(interfaces.IFieldWidget, select_widget)
  True

And we can render this widget:

  >>> print select_widget.render()
  <select id="form-field-choices" name="form.field.choices"
   class="field field-multichoice" multiple="multiple">
    <option value="vodka" selected="selected">Vodka</option>
    <option value="tequila">Tequila</option>
    <option value="cognac">Cognac</option>
    <option value="champagne" selected="selected">Champagne</option>
  </select>
  <input type="hidden" name="form.field.choices.present" value="1" />
