============================
Select Widget, missing terms
============================

The select widget allows you to select one or more values from a set of given
options. The "SELECT" and "OPTION" elements are described here:

http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#edef-SELECT

As for all widgets, the select widget must provide the new ``IWidget``
interface:

  >>> from z3c.form import interfaces
  >>> from z3c.form.browser import select

The widget can be instantiated only using the request:

  >>> from z3c.form.testing import TestRequest
  >>> request = TestRequest()

  >>> widget = select.SelectWidget(request)

Before rendering the widget, one has to set the name and id of the widget:

  >>> widget.id = 'widget-id'
  >>> widget.name = 'widget.name'

We also need to register the template for at least the widget and request:

  >>> import zope.component
  >>> from zope.pagetemplate.interfaces import IPageTemplate
  >>> from z3c.form.testing import getPath
  >>> from z3c.form.widget import WidgetTemplateFactory

  >>> zope.component.provideAdapter(
  ...     WidgetTemplateFactory(getPath('select_input.pt'), 'text/html'),
  ...     (None, None, None, None, interfaces.ISelectWidget),
  ...     IPageTemplate, name=interfaces.INPUT_MODE)

We need some context:

  >>> class IPerson(zope.interface.Interface):
  ...     rating = zope.schema.Choice(
  ...     vocabulary='Ratings')

  >>> @zope.interface.implementer(IPerson)
  ... class Person(object):
  ...     pass
  >>> person = Person()

Let's provide some values for this widget. We can do this by defining a source
providing ``ITerms``. This source uses descriminators which will fit our setup.

  >>> import zope.schema.interfaces
  >>> from zope.schema.vocabulary import SimpleVocabulary
  >>> from zope.schema.vocabulary import SimpleTerm
  >>> import z3c.form.term

  >>> from zope.schema import vocabulary
  >>> ratings = vocabulary.SimpleVocabulary([
  ...     vocabulary.SimpleVocabulary.createTerm(0, '0', u'bad'),
  ...     vocabulary.SimpleVocabulary.createTerm(1, '1', u'okay'),
  ...     vocabulary.SimpleVocabulary.createTerm(2, '2', u'good')
  ...     ])

  >>> def RatingsVocabulary(obj):
  ...     return ratings

  >>> vr = vocabulary.getVocabularyRegistry()
  >>> vr.register('Ratings', RatingsVocabulary)

  >>> class SelectionTerms(z3c.form.term.MissingChoiceTermsVocabulary):
  ...     def __init__(self, context, request, form, field, widget):
  ...         self.context = context
  ...         self.field = field
  ...         self.terms = ratings
  ...         self.widget = widget
  ...
  ...     def _makeMissingTerm(self, token):
  ...         if token == 'x':
  ...             return super(SelectionTerms, self)._makeMissingTerm(token)
  ...         else:
  ...             raise LookupError

  >>> zope.component.provideAdapter(SelectionTerms,
  ...     (None, interfaces.IFormLayer, None, None, interfaces.ISelectWidget) )

  >>> import z3c.form.datamanager
  >>> zope.component.provideAdapter(z3c.form.datamanager.AttributeField)

Now let's try if we get widget values:

  >>> widget.update()
  >>> print(widget.render())
  <select id="widget-id" name="widget.name:list"
          class="select-widget" size="1">
  <option id="widget-id-novalue" value="--NOVALUE--">No value</option>
  <option id="widget-id-0" value="0">bad</option>
  <option id="widget-id-1" value="1">okay</option>
  <option id="widget-id-2" value="2">good</option>
  </select>
  <input name="widget.name-empty-marker" type="hidden" value="1" />

If we set the widget value to "x", then it should be present and selected:

  >>> widget.value = ['x']
  >>> widget.context = person
  >>> widget.field = IPerson['rating']
  >>> zope.interface.alsoProvides(widget, interfaces.IContextAware)
  >>> person.rating = 'x'
  >>> widget.terms = None

  >>> widget.update()
  >>> print(widget.render())
  <select id="widget-id" name="widget.name:list"
          class="select-widget" size="1">
  <option id="widget-id-novalue" value="--NOVALUE--">No value</option>
  <option id="widget-id-0" value="0">bad</option>
  <option id="widget-id-1" value="1">okay</option>
  <option id="widget-id-2" value="2">good</option>
  <option id="widget-id-missing-0" selected="selected" value="x">Missing: x</option>
  </select>
  <input name="widget.name-empty-marker" type="hidden" value="1" />

If we set the widget value to "y", then it should NOT be around:

  >>> widget.value = ['y']
  >>> widget.update()
  >>> print(widget.render())
  <select id="widget-id" name="widget.name:list"
          class="select-widget" size="1">
  <option id="widget-id-novalue" value="--NOVALUE--">No value</option>
  <option id="widget-id-0" value="0">bad</option>
  <option id="widget-id-1" value="1">okay</option>
  <option id="widget-id-2" value="2">good</option>
  </select>
  <input name="widget.name-empty-marker" type="hidden" value="1" />

Let's now make sure that we can extract user entered data from a widget:

  >>> widget.request = TestRequest(form={'widget.name': ['c']})
  >>> widget.update()
  >>> widget.extract()
  <NO_VALUE>

Well, only of it matches the context's current value:

  >>> widget.request = TestRequest(form={'widget.name': ['x']})
  >>> widget.update()
  >>> widget.extract()
  ['x']

When "No value" is selected, then no verification against the terms is done:

  >>> widget.request = TestRequest(form={'widget.name': ['--NOVALUE--']})
  >>> widget.update()
  >>> widget.extract(default=1)
  ['--NOVALUE--']

Let's now make sure that we can extract user entered missing data from a widget:

  >>> widget.request = TestRequest(form={'widget.name': ['x']})
  >>> widget.update()
  >>> widget.extract()
  ['x']

  >>> widget.request = TestRequest(form={'widget.name': ['y']})
  >>> widget.update()
  >>> widget.extract()
  <NO_VALUE>

Unfortunately, when nothing is selected, we do not get an empty list sent into
the request, but simply no entry at all. For this we have the empty marker, so
that:

  >>> widget.request = TestRequest(form={'widget.name-empty-marker': '1'})
  >>> widget.update()
  >>> widget.extract()
  []

If nothing is found in the request, the default is returned:

  >>> widget.request = TestRequest()
  >>> widget.update()
  >>> widget.extract(default=1)
  1

Let's now make sure that a bogus value causes extract to return the default as
described by the interface:

  >>> widget.request = TestRequest(form={'widget.name': ['y']})
  >>> widget.update()
  >>> widget.extract(default=1)
  1

Display Widget
--------------

The select widget comes with a template for ``DISPLAY_MODE``. Let's
register it first:

  >>> zope.component.provideAdapter(
  ...     WidgetTemplateFactory(getPath('select_display.pt'), 'text/html'),
  ...     (None, None, None, None, interfaces.ISelectWidget),
  ...     IPageTemplate, name=interfaces.DISPLAY_MODE)

Let's see what happens if we have values that are not in the vocabulary:

  >>> widget.required = True
  >>> widget.mode = interfaces.DISPLAY_MODE
  >>> widget.value = ['0', '1', 'x']
  >>> widget.update()
  >>> print(widget.render())
  <span id="widget-id" class="select-widget">
    <span class="selected-option">bad</span>,
    <span class="selected-option">okay</span>,
    <span class="selected-option">Missing: x</span>
  </span>

Hidden Widget
-------------

The select widget comes with a template for ``HIDDEN_MODE``.  Let's
register it first:

  >>> zope.component.provideAdapter(
  ...     WidgetTemplateFactory(getPath('select_hidden.pt'), 'text/html'),
  ...     (None, None, None, None, interfaces.ISelectWidget),
  ...     IPageTemplate, name=interfaces.HIDDEN_MODE)

Let's see what happens if we have values that are not in the vocabulary:

  >>> widget.mode = interfaces.HIDDEN_MODE
  >>> widget.value = ['0', 'x']
  >>> widget.update()
  >>> print(widget.render())
  <input id="widget-id-0" name="widget.name:list" value="0" class="hidden-widget" type="hidden" />
  <input id="widget-id-missing-0" name="widget.name:list" value="x" class="hidden-widget" type="hidden" />
  <input name="widget.name-empty-marker" type="hidden" value="1" />
