    =====================
    Hint (title) Adapters
    =====================

    A widget can provide a hint. Hints are not a standard concept, the
    implementations can be very different in each project. Hints are most
    of the time implemented with JavaScript since the default ``input
    title`` hint in browsers are almost unusable.

    Our hint support is limited and only offers some helpers. Which means
    we will offer an adapter that shows the schema field description as
    the title. Since this is very specific we only provide a
    ``FieldDescriptionAsHint`` adapter which you can configure as a named
    IValue adapter.

      >>> from pyramid.testing import setUp, tearDown
      >>> config = setUp(hook_zca=True)

      >>> from cornice import includeme as include_cornice
      >>> include_cornice(config)
      >>> from cornice_swagger import includeme as include_swagger
      >>> include_swagger(config)
      >>> from pyams_utils import includeme as include_utils
      >>> include_utils(config)
      >>> from pyams_site import includeme as include_site
      >>> include_site(config)
      >>> from pyams_i18n import includeme as include_i18n
      >>> include_i18n(config)
      >>> from pyams_form import includeme as include_form
      >>> include_form(config)

    We also need to setup the form defaults:

      >>> from pyams_utils.testing import format_html
      >>> from pyams_form import testing
      >>> testing.setup_form_defaults(config.registry)

      >>> from pyams_layer.interfaces import IFormLayer
      >>> from pyams_form import interfaces
      >>> from pyams_form import form
      >>> from pyams_form import field
      >>> from pyams_form import hint

    Let's create a couple of simple widgets and forms first:

      >>> import zope.interface
      >>> import zope.schema

      >>> class IContent(zope.interface.Interface):
      ...
      ...     textLine = zope.schema.TextLine(
      ...         title='Title',
      ...         description='A TextLine description')
      ...
      ...     anotherLine = zope.schema.TextLine(
      ...         title='Other')

      >>> @zope.interface.implementer(IContent)
      ... class Content:
      ...     textLine = None
      ...     otherLine = None
      ...
      >>> content = Content()

      >>> from pyams_form.testing import TestRequest
      >>> request = TestRequest()

      >>> class HintForm(form.Form):
      ...     fields = field.Fields(IContent)

      >>> hintForm = HintForm(content, request)

    As you can see, there is no title value set for our widgets:

      >>> hintForm.update()
      >>> print(hintForm.widgets['textLine'].render())
>>> print(hintForm.widgets['anotherLine'].render())
    Let's configure our IValue ``hint`` adapter:

      >>> from pyams_form.hint import FieldDescriptionAsHint
      >>> config.registry.registerAdapter(FieldDescriptionAsHint,
      ...       required=(zope.interface.Interface, IFormLayer, interfaces.form.IForm, interfaces.IField, interfaces.widget.IWidget),
      ...       provided=interfaces.IValue, name='title')

    If we update our form, we can see that the title is used based on the schema
    field description:

      >>> hintForm.update()
      >>> print(hintForm.widgets['textLine'].render())
If the field has no description as it is with the second one, no "title" will be set for the widget: >>> print(hintForm.widgets['anotherLine'].render())
    Check all fields
    ----------------

    Just to make sure that all the widgets are handled correctly, we will
    go through all of them. This sample can be useful if you need to
    implement a JavaScript based hint concept:

      >>> import datetime
      >>> import decimal
      >>> from zope.schema import vocabulary

    Let's setup a simple vocabulary:

      >>> vocab = vocabulary.SimpleVocabulary([
      ...     vocabulary.SimpleVocabulary.createTerm(1, '1', 'One'),
      ...     vocabulary.SimpleVocabulary.createTerm(2, '2', 'Two'),
      ...     vocabulary.SimpleVocabulary.createTerm(3, '3', 'Three'),
      ...     vocabulary.SimpleVocabulary.createTerm(4, '4', 'Four'),
      ...     vocabulary.SimpleVocabulary.createTerm(5, '5', 'Five')
      ...     ])

      >>> class IAllInOne(zope.interface.Interface):
      ...
      ...     asciiField = zope.schema.ASCII(
      ...         title='ASCII',
      ...         description='This is an ASCII field.',
      ...         default='This is\n ASCII.')
      ...
      ...     asciiLineField = zope.schema.ASCIILine(
      ...         title='ASCII Line',
      ...         description='This is an ASCII-Line field.',
      ...         default='An ASCII line.')
      ...
      ...     boolField = zope.schema.Bool(
      ...         title='Boolean',
      ...         description='This is a Bool field.',
      ...         default=True)
      ...
      ...     checkboxBoolField = zope.schema.Bool(
      ...         title='Boolean (Checkbox)',
      ...         description='This is a Bool field displayed suing a checkbox.',
      ...         default=True)
      ...
      ...     bytesLineField = zope.schema.BytesLine(
      ...         title='Bytes Line',
      ...         description='This is a bytes line field.',
      ...         default=b'A Bytes line.')
      ...
      ...     choiceField = zope.schema.Choice(
      ...         title='Choice',
      ...         description='This is a choice field.',
      ...         default=3,
      ...         vocabulary=vocab)
      ...
      ...     optionalChoiceField = zope.schema.Choice(
      ...         title='Choice (Not Required)',
      ...         description='This is a non-required choice field.',
      ...         vocabulary=vocab,
      ...         required=False)
      ...
      ...     promptChoiceField = zope.schema.Choice(
      ...         title='Choice (Explicit Prompt)',
      ...         description='This is a choice field with an explicit prompt.',
      ...         vocabulary=vocab,
      ...         required=False)
      ...
      ...     dateField = zope.schema.Date(
      ...         title='Date',
      ...         description='This is a Date field.',
      ...         default=datetime.date(2007, 4, 1))
      ...
      ...     datetimeField = zope.schema.Datetime(
      ...         title='Date/Time',
      ...         description='This is a Datetime field.',
      ...         default=datetime.datetime(2007, 4, 1, 12))
      ...
      ...     decimalField = zope.schema.Decimal(
      ...         title='Decimal',
      ...         description='This is a Decimal field.',
      ...         default=decimal.Decimal('12.87'))
      ...
      ...     dottedNameField = zope.schema.DottedName(
      ...         title='Dotted Name',
      ...         description='This is a DottedName field.',
      ...         default='pyams_form.util')
      ...
      ...     floatField = zope.schema.Float(
      ...         title='Float',
      ...         description='This is a Float field.',
      ...         default=12.8)
      ...
      ...     frozenSetField = zope.schema.FrozenSet(
      ...         title='Frozen Set',
      ...         description='This is a FrozenSet field.',
      ...         value_type=choiceField,
      ...         default=frozenset([1, 3]) )
      ...
      ...     idField = zope.schema.Id(
      ...         title='Id',
      ...         description='This is an Id field.',
      ...         default='pyams_form.util')
      ...
      ...     intField = zope.schema.Int(
      ...         title='Integer',
      ...         description='This is an Int field.',
      ...         default=12345)
      ...
      ...     listField = zope.schema.List(
      ...         title='List',
      ...         description='This is a List field.',
      ...         value_type=choiceField,
      ...         default=[1, 3])
      ...
      ...     passwordField = zope.schema.Password(
      ...         title='Password',
      ...         description='This is a Password field.',
      ...         default='mypwd',
      ...         required=False)
      ...
      ...     setField = zope.schema.Set(
      ...         title='Set',
      ...         description='This is a Set field.',
      ...         value_type=choiceField,
      ...         default=set([1, 3]) )
      ...
      ...     sourceTextField = zope.schema.SourceText(
      ...         title='Source Text',
      ...         description='This is a SourceText field.',
      ...         default='
    ')
      ...
      ...     textField = zope.schema.Text(
      ...         title='Text',
      ...         description='This is a Text field.',
      ...         default='Some\n Text.')
      ...
      ...     textLineField = zope.schema.TextLine(
      ...         title='Text Line',
      ...         description='This is a TextLine field.',
      ...         default='Some Text line.')
      ...
      ...     timeField = zope.schema.Time(
      ...         title='Time',
      ...         description='This is a Time field.',
      ...         default=datetime.time(12, 0))
      ...
      ...     timedeltaField = zope.schema.Timedelta(
      ...         title='Time Delta',
      ...         description='This is a Timedelta field.',
      ...         default=datetime.timedelta(days=3))
      ...
      ...     tupleField = zope.schema.Tuple(
      ...         title='Tuple',
      ...         description='This is a Tuple field.',
      ...         value_type=choiceField,
      ...         default=(1, 3))
      ...
      ...     uriField = zope.schema.URI(
      ...         title='URI',
      ...         description='This is an URI field.',
      ...         default='http://pyams.readthedocs.io')
      ...
      ...     hiddenField = zope.schema.TextLine(
      ...         title='Hidden Text Line',
      ...         description='This is a hidden TextLine field.',
      ...         default='Some Hidden Text.')

      >>> @zope.interface.implementer(IAllInOne)
      ... class AllInOne:
      ...     asciiField = None
      ...     asciiLineField = None
      ...     boolField = None
      ...     checkboxBoolField = None
      ...     choiceField = None
      ...     optionalChoiceField = None
      ...     promptChoiceField = None
      ...     dateField = None
      ...     decimalField = None
      ...     dottedNameField = None
      ...     floatField = None
      ...     frozenSetField = None
      ...     idField = None
      ...     intField = None
      ...     listField = None
      ...     passwordField = None
      ...     setField = None
      ...     sourceTextField = None
      ...     textField = None
      ...     textLineField = None
      ...     timeField = None
      ...     timedeltaField = None
      ...     tupleField = None
      ...     uriField = None
      ...     hiddenField = None

      >>> allInOne = AllInOne()

      >>> class AllInOneForm(form.Form):
      ...     fields = field.Fields(IAllInOne)

    Now test the hints in our widgets:

      >>> allInOneForm = AllInOneForm(allInOne, request)
      >>> allInOneForm.update()
      >>> print(allInOneForm.widgets['asciiField'].render())
    This is
       ASCII.
>>> print(allInOneForm.widgets['asciiLineField'].render()) >>> print(allInOneForm.widgets['boolField'].render()) yes no >>> print(allInOneForm.widgets['checkboxBoolField'].render()) yes no >>> print(allInOneForm.widgets['textField'].render())
    Some
       Text.
>>> print(allInOneForm.widgets['textLineField'].render()) >>> print(allInOneForm.widgets['bytesLineField'].render()) >>> print(format_html(allInOneForm.widgets['choiceField'].render()))
>>> print(format_html(allInOneForm.widgets['optionalChoiceField'].render()))
>>> print(format_html(allInOneForm.widgets['promptChoiceField'].render()))
>>> print(allInOneForm.widgets['dateField'].render()) >>> print(allInOneForm.widgets['datetimeField'].render()) >>> print(allInOneForm.widgets['decimalField'].render()) >>> print(allInOneForm.widgets['dottedNameField'].render()) >>> print(allInOneForm.widgets['floatField'].render()) >>> print(format_html(allInOneForm.widgets['frozenSetField'].render()))
>>> print(allInOneForm.widgets['idField'].render()) >>> print(allInOneForm.widgets['intField'].render()) >>> print(format_html(allInOneForm.widgets['listField'].render()))

>>> print(allInOneForm.widgets['passwordField'].render()) >>> print(format_html(allInOneForm.widgets['setField'].render()))
>>> print(allInOneForm.widgets['sourceTextField'].render())
    <source />
>>> print(allInOneForm.widgets['timeField'].render()) >>> print(allInOneForm.widgets['timedeltaField'].render()) >>> print(format_html(allInOneForm.widgets['tupleField'].render()))

>>> print(allInOneForm.widgets['uriField'].render()) >>> print(allInOneForm.widgets['hiddenField'].render()) Tests cleanup: >>> tearDown()