==============================
Introduction
==============================
.. contents::
.. image:: https://secure.travis-ci.org/collective/collective.z3cform.chosen.png
:target: http://travis-ci.org/collective/collective.z3cform.chosen
Credits
========
Companies
---------
|makinacom|_
* `Planet Makina Corpus <http://www.makina-corpus.org>`_
* `Contact us <mailto:python@makina-corpus.org>`_
.. |makinacom| image:: http://depot.makina-corpus.org/public/logo.gif
.. _makinacom: http://www.makina-corpus.com
Authors
------------
- kiorky <kiorky@cryptelium.net>
Contributors
-----------------
Description
=============
This package contains 4 widgets for z3cform using the `chosen <http://harvesthq.github.com/chosen/>`_ and `ajaxchosen <https://github.com/kiorky/ajax-chosen>`_ libraries.
- A single valued widget for chosen
- A multi valued widget for chosen
- A single valued widget for ajaxchosen
- A multi valued widget for ajaxchosen
Repository : `github <https://github.com/kiorky/collective.z3cform.chosen>`_
Chosen widget
===================
collective.chosen.widget provides an autocomplete widget based on the
jQuery Autocomplete widget.
>>> from collective.z3cform.chosen import AjaxChosenFieldWidget
>>> from collective.z3cform.chosen import AjaxChosenMultiFieldWidget
>>> from collective.z3cform.chosen import ChosenFieldWidget
>>> from collective.z3cform.chosen import ChosenMultiFieldWidget
First, we need a vocabulary to search. This is shamelessly stolen from
z3c.formwidget.query, which we extend.
>>> from zope.interface import implements
>>> from z3c.formwidget.query.interfaces import IQuerySource
>>> from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
>>> class ItalianCities(object):
... implements(IQuerySource)
...
... vocabulary = SimpleVocabulary((
... SimpleTerm(u'bologna', 'bologna', u'Bologna'),
... SimpleTerm(u'palermo', 'palermo', u'Palermo'),
... SimpleTerm(u'sorrento', 'sorrento', u'Sorrento'),
... SimpleTerm(u'torino', 'torino', u'Torino')))
...
... def __init__(self, context):
... self.context = context
...
... __contains__ = vocabulary.__contains__
... __iter__ = vocabulary.__iter__
... getTerm = vocabulary.getTerm
... getTermByToken = vocabulary.getTermByToken
...
... def search(self, query_string):
... return [v
... for v in self
... if query_string.lower() in v.value.lower()]
>>> from zope.schema.interfaces import IContextSourceBinder
>>> class ItalianCitiesSourceBinder(object):
... implements(IContextSourceBinder)
...
... def __call__(self, context):
... return ItalianCities(context)
Then, we will set up a simple test form and context.
>>> from zope.interface import alsoProvides
>>> from OFS.SimpleItem import SimpleItem
>>> from Testing.makerequest import makerequest
>>> from zope.annotation.interfaces import IAttributeAnnotatable
>>> from z3c.form.interfaces import IFormLayer
>>> def make_request(path, form={}):
... app = SimpleItem('')
... request = makerequest(app).REQUEST
... request.form.update(form)
... alsoProvides(request, IFormLayer)
... alsoProvides(request, IAttributeAnnotatable)
... request._script = path.split('/')
... request._steps = []
... request._resetURLS()
... return request
>>> from zope.interface import Interface
>>> from zope import schema
>>> from z3c.form import form, field, button
>>> from plone.z3cform.layout import wrap_form
>>> class ICities(Interface):
... afavourite_city = schema.Choice(title=u"Favourite city",
... source=ItalianCitiesSourceBinder(), required=False)
... avisited_cities = schema.List(title=u"Visited cities",
... value_type=schema.Choice(title=u"Selection",
... source=ItalianCitiesSourceBinder()))
... favourite_city = schema.Choice(title=u"Favourite city",
... source=ItalianCitiesSourceBinder())
... visited_cities = schema.List(title=u"Visited cities",
... value_type=schema.Choice(title=u"Selection",
... source=ItalianCitiesSourceBinder()))
>>> from z3c.form.interfaces import IFieldsForm
>>> from zope.interface import implements
>>> class CitiesForm(form.Form):
... implements(ICities)
... fields = field.Fields(ICities)
... fields['afavourite_city'].widgetFactory = AjaxChosenFieldWidget
... fields['avisited_cities'].widgetFactory = AjaxChosenMultiFieldWidget
... fields['favourite_city'].widgetFactory = ChosenFieldWidget
... fields['visited_cities'].widgetFactory = ChosenMultiFieldWidget
...
... @button.buttonAndHandler(u'Apply')
... def handleApply(self, action):
... data, errors = self.extractData()
... print "Submitted data:", data
>>> form_view = wrap_form(CitiesForm)
>>> from zope.component import provideAdapter
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> from zope.interface import Interface
>>> provideAdapter(adapts=(ICities, IBrowserRequest),
... provides=Interface,
... factory=form_view,
... name=u"cities-form")
>>> from OFS.SimpleItem import SimpleItem
>>> class Bar(SimpleItem):
... implements(ICities)
...
... def __init__(self, id):
... self.id = id
... self.favourite_city = None
... self.visited_cities = []
... self.afavourite_city = None
... self.avisited_cities = []
... def absolute_url(self):
... return 'http://foo/bar'
Let us now look up the form and attempt to render the widget.
>>> from zope.component import getMultiAdapter
>>> context = Bar('bar')
Simulates traversal:
>>> request = make_request('bar/@@cities-form')
>>> from Testing.makerequest import makerequest
>>> context = makerequest(context)
>>> form_view = getMultiAdapter((context, request), name=u"cities-form")
>>> form_view.__name__ = 'cities-form'
Simulates partial rendering:
>>> form = form_view.form_instance
>>> form.__name__ = 'cities-form'
>>> form.update()
>>> print form.widgets['favourite_city'].render().replace("...", "") # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE +REPORT_UDIFF
<BLANKLINE>
<script type="text/javascript"> (function($) {
$().ready(function() {
$('#form-widgets-favourite_city-select').data('klass','chosen-selection-widget required choice-field').data('title','None');
$('#form-widgets-favourite_city-select').chosen({
allow_single_deselect: false,
no_results_text: 'No results found',
width: '280px'
});
<BLANKLINE>
$('#formfield-form-widgets-favourite_city').find('.chzn-container, .chzn-results, .chzn-drop').css({'min-width': '180px'});
});
})(jQuery);
</script>
<div id="form-widgets-favourite_city-chosen" class="chosen-selection-widget required choice-field">
<select data-placeholder="Select a value" id="form-widgets-favourite_city-select" name="form.widgets.favourite_city:list" onselect="" style="" >
<BLANKLINE>
<option id="form-widgets-favourite_city-novalue" value="(nothing)" selected="selected">Select a value</option>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<option id="form-widgets-favourite_city-0" value="bologna">Bologna</option>
<BLANKLINE>
<BLANKLINE>
<option id="form-widgets-favourite_city-1" value="palermo">Palermo</option>
<BLANKLINE>
<BLANKLINE>
<option id="form-widgets-favourite_city-2" value="sorrento">Sorrento</option>
<BLANKLINE>
<BLANKLINE>
<option id="form-widgets-favourite_city-3" value="torino">Torino</option>
<BLANKLINE>
</select>
<input name="form.widgets.favourite_city-empty-marker" type="hidden" value="1" />
</div>
<BLANKLINE>
We can see that the rendered JavaScript is expecting to call a view for ajax widgets
like this:
>>> widget = form.widgets['afavourite_city']
>>> context.REQUEST._script = 'bar/@@cities-form/++widget++form.widgets.avisited_cities/@@chosen-autocomplete-search'.split('/')
>>> context.REQUEST._resetURLS()
>>> context.REQUEST.form['term'] = 'or'
>>> search_view = getMultiAdapter((widget, context.REQUEST), name=u'chosen-autocomplete-search')
The results are a json tuple list of tokens:
>>> print search_view()
[["sorrento","Sorrento"],["torino","Torino"]]
At first we didnt set anything in the request, we are missing fields
>>> form.update()
>>> data, errors = form.extractData()
>>> len(errors)
3
>>> form.request.form.update({
... "form.buttons.apply" : "Apply",
... "form.widgets.visited_cities" : ["palermo", "bologna"],
... "form.widgets.avisited_cities" : ["palermo", "bologna"],
... "form.widgets.afavourite_city" :"bologna",
... "form.widgets.favourite_city" : "palermo",
... })
>>> form.update() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Submitted data:...
>>> data, errors = form.extractData()
>>> items = data.items()
>>> items.sort(key=lambda x:x[0])
>>> pprint(items)
[('afavourite_city', u'bologna'),
('avisited_cities', [u'palermo', u'bologna']),
('favourite_city', u'palermo'),
('visited_cities', [u'palermo', u'bologna'])]
Our values are marked as selected
>>> results = form.render().replace('...', '')
>>> False not in [
... (it in results)
... for it in ['id="form-widgets-visited_cities-0" value="bologna" selected="selected">Bologna',
... 'id="form-widgets-visited_cities-1" value="palermo" selected="selected">Palermo']]
True
Our widget also handle display mode
>>> form.widgets['favourite_city'].mode = 'display'
>>> print form.widgets['favourite_city'].render().strip()
<span id="form-widgets-favourite_city" class="chosen-selection-widget required choice-field" style=""><span class="selected-option">Palermo</span></span>
>>> form.widgets['visited_cities'].mode = 'display'
>>> print form.widgets['visited_cities'].render().strip()
<span id="form-widgets-visited_cities" class="chosen-multiselection-widget required list-field" style=""><span class="selected-option">Palermo</span>, <span class="selected-option">Bologna</span></span>
Our widget also handle hidden mode
>>> form.widgets['favourite_city'].mode = 'hidden'
>>> print form.widgets['favourite_city'].render().strip()
<input id="form-widgets-favourite_city-1" name="form.widgets.favourite_city:list" value="palermo" class="hidden-widget" type="hidden" />
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<input name="form.widgets.favourite_city-empty-marker" type="hidden" value="1" />
>>> form.widgets['visited_cities'].mode = 'hidden'
>>> print form.widgets['visited_cities'].render().strip()
<input id="form-widgets-visited_cities-0" name="form.widgets.visited_cities:list" value="bologna" class="hidden-widget" type="hidden" />
<BLANKLINE>
<BLANKLINE>
<input id="form-widgets-visited_cities-1" name="form.widgets.visited_cities:list" value="palermo" class="hidden-widget" type="hidden" />
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<BLANKLINE>
<input name="form.widgets.visited_cities-empty-marker" type="hidden" value="1" />
collective.z3cform.chosen Installation
=================================================================================
To install collective.z3cform.chosen, follow this `documentation <http://plone.org/documentation/kb/installing-add-ons-quick-how-to>`_.
Changelog
=========
1.2.2 (2021-04-20)
------------------
- Do not translate the no value token to avoid an error when translated.
[mpeeters]
- Added needed ajax parameter for corresponding collective.js.chosen
[cedricmessiant, sgeulette]
1.2.1 (2014-09-25)
------------------
- Widget works when the field is initially hidden (for instance in an overlay).
[thomasdesvenain]
- Pyflakes, pep8, unused code removed, etc.
[thomasdesvenain]
1.2 (2014-06-03)
----------------
- Set width, and apply as chosen parameter rather than styling widgets with a
specific width; doing so ensures chosen will set a valid width when it's
initialized off-screen or when the element applied to it is invisible.
[damilgra]
- Updated french translations.
[cedricmessiant]
1.1 (2013-06-04)
----------------
- buildout, tests & travis [kiorky]
- Change prompt message and update French translations.
Fix bug for no value in List fields.
[cedricmessiant]
1.0 (2012-06-06)
----------------
* Initial release [kiorky]