Import newer version of django-selectable
[pgweb/local.git] / dep / django-selectable / docs / advanced.rst
blobd3ffe2b5a5a30b6999a19ba1d34702b40cc8b0bf
1 Advanced Usage
2 ==========================
4 We've gone through the most command and simple use cases for django-selectable. Now
5 we'll take a look at some of the more advanced features of this project. This assumes
6 that you are comfortable reading and writing a little bit of Javascript making
7 use of jQuery.
10 .. _additional-parameters:
12 Additional Parameters
13 --------------------------------------
15 The basic lookup is based on handling a search based on a single term string.
16 If additional filtering is needed it can be inside the lookup ``get_query`` but
17 you would need to define this when the lookup is defined. While this fits a fair
18 number of use cases there are times when you need to define additional query
19 parameters that won't be known until either the form is bound or until selections
20 are made on the client side. This section will detail how to handle both of these
21 cases.
24 How Parameters are Passed
25 _______________________________________
27 As with the search term, the additional parameters you define will be passed in
28 ``request.GET``. Since ``get_query`` gets the current request, you will have access to
29 them. Since they can be manipulated on the client side, these parameters should be
30 treated like all user input. It should be properly validated and sanitized.
33 Limiting the Result Set
34 _______________________________________
36 The number of results are globally limited/paginated by the :ref:`SELECTABLE_MAX_LIMIT`
37 but you can also lower this limit on the field or widget level. Each field and widget
38 takes a ``limit`` argument in the ``__init__`` that will be passed back to the lookup
39 through the ``limit`` query parameter. The result set will be automatically paginated
40 for you if you use either this parameter or the global setting.
43 .. _server-side-parameters:
45 Adding Parameters on the Server Side
46 _______________________________________
48 Each of the widgets define ``update_query_parameters`` which takes a dictionary. The
49 most common way to use this would be in the form ``__init__``.
51     .. code-block:: python
53         class FruitForm(forms.Form):
54             autocomplete = forms.CharField(
55                 label='Type the name of a fruit (AutoCompleteWidget)',
56                 widget=selectable.AutoCompleteWidget(FruitLookup),
57                 required=False,
58             )
60             def __init__(self, *args, **kwargs):
61                 super(FruitForm, self).__init__(*args, **kwargs)
62                 self.fields['autocomplete'].widget.update_query_parameters({'foo': 'bar'})
64 You can also pass the query parameters into the widget using the ``query_params``
65 keyword argument. It depends on your use case as to whether the parameters are
66 known when the form is defined or when an instance of the form is created.
69 .. _client-side-parameters:
71 Adding Parameters on the Client Side
72 _______________________________________
74 There are times where you want to filter the result set based other selections
75 by the user such as a filtering cities by a previously selected state. In this
76 case you will need to bind a ``prepareQuery`` to the field. This function should accept the query dictionary.
77 You are free to make adjustments to  the query dictionary as needed.
79     .. code-block:: html
81         <script type="text/javascript">
82             function newParameters(query) {
83                 query.foo = 'bar';
84             }
86             $(document).ready(function() {
87                 $('#id_autocomplete').djselectable('option', 'prepareQuery', newParameters);
88             });
89         </script>
91 .. note::
93     In v0.7 the scope of ``prepareQuery`` was updated so that ``this`` refers to the
94     current ``djselectable`` plugin instance. Previously ``this`` refered to the
95     plugin ``options`` instance.
98 .. _chain-select-example:
100 Chained Selection
101 --------------------------------------
103 It's a fairly common pattern to have two or more inputs depend one another such City/State/Zip.
104 In fact there are other Django apps dedicated to this purpose such as
105 `django-smart-selects <https://github.com/digi604/django-smart-selects>`_ or
106 `django-ajax-filtered-fields <http://code.google.com/p/django-ajax-filtered-fields/>`_.
107 It's possible to handle this kind of selection with django-selectable if you are willing
108 to write a little javascript.
110 Suppose we have city model
112     .. code-block:: python
114         from __future__ import unicode_literals
116         from django.db import models
117         from django.utils.encoding import python_2_unicode_compatible
119         from localflavor.us.models import USStateField
122         @python_2_unicode_compatible
123         class City(models.Model):
124             name = models.CharField(max_length=200)
125             state = USStateField()
127             def __str__(self):
128                 return self.name
130 Then in our lookup we will grab the state value and filter our results on it:
132     .. code-block:: python
134         from __future__ import unicode_literals
136         from selectable.base import ModelLookup
137         from selectable.registry import registry
139         from .models import City
142         class CityLookup(ModelLookup):
143             model = City
144             search_fields = ('name__icontains', )
146             def get_query(self, request, term):
147                 results = super(CityLookup, self).get_query(request, term)
148                 state = request.GET.get('state', '')
149                 if state:
150                     results = results.filter(state=state)
151                 return results
153             def get_item_label(self, item):
154                 return "%s, %s" % (item.name, item.state)
157         registry.register(CityLookup)
159 and a simple form
161     .. code-block:: python
163         from django import forms
165         from localflavor.us.forms import USStateField, USStateSelect
167         from selectable.forms import AutoCompleteSelectField, AutoComboboxSelectWidget
169         from .lookups import CityLookup
172         class ChainedForm(forms.Form):
173             city = AutoCompleteSelectField(
174                 lookup_class=CityLookup,
175                 label='City',
176                 required=False,
177                 widget=AutoComboboxSelectWidget
178             )
179             state = USStateField(widget=USStateSelect, required=False)
182 We want our users to select a city and if they choose a state then we will only
183 show them cities in that state. To do this we will pass back chosen state as
184 addition parameter with the following javascript:
186     .. code-block:: html
188         <script type="text/javascript">
189             $(document).ready(function() {
190                 function newParameters(query) {
191                     query.state = $('#id_state').val();
192                 }
193                 $('#id_city_0').djselectable('option', 'prepareQuery', newParameters);
194             });
195         </script>
197 And that's it! We now have a working chained selection example. The full source
198 is included in the example project.
200 .. _client-side-changes:
202 Detecting Client Side Changes
203 ____________________________________________
205 The previous example detected selection changes on the client side to allow passing
206 parameters to the lookup. Since django-selectable is built on top of the jQuery UI
207 `Autocomplete plug-in <http://jqueryui.com/demos/autocomplete/>`_, the widgets
208 expose the events defined by the plugin.
210     - djselectablecreate
211     - djselectablesearch
212     - djselectableopen
213     - djselectablefocus
214     - djselectableselect
215     - djselectableclose
216     - djselectablechange
218 For the most part these event names should be self-explanatory. If you need additional
219 detail you should refer to the `jQuery UI docs on these events <http://jqueryui.com/demos/autocomplete/#events>`_.
221 The multiple select widgets include additional events which indicate when a new item is added
222 or removed from the current list. These events are ``djselectableadd`` and ``djselectableremove``.
223 These events pass a dictionary of data with the following keys
225     - element: The original text input
226     - input: The hidden input to be added for the new item
227     - wrapper: The ``<li>`` element to be added to the deck
228     - deck: The outer ``<ul>`` deck element
230 You can use these events to prevent items from being added or removed from the deck by
231 returning ``false`` in the handling function. A simple example is given below:
233     .. code-block:: html
235         <script type="text/javascript">
236             $(document).ready(function() {
237                 $(':input[name=my_field_0]').bind('djselectableadd', function(event, item) {
238                     // Don't allow foo to be added
239                     if ($(item.input).val() === 'foo') {
240                         return false;
241                     }
242                 });
243             });
244         </script>
247 Submit On Selection
248 --------------------------------------
250 You might want to help your users by submitting the form once they have selected a valid
251 item. To do this you simply need to listen for the ``djselectableselect`` event. This
252 event is fired by the text input which has an index of 0. If your field is named ``my_field``
253 then input to watch would be ``my_field_0`` such as:
255     .. code-block:: html
257         <script type="text/javascript">
258             $(document).ready(function() {
259                 $(':input[name=my_field_0]').bind('djselectableselect', function(event, ui) {
260                     $(this).parents("form").submit();
261                 });
262             });
263         </script>
266 Dynamically Added Forms
267 --------------------------------------
269 django-selectable can work with dynamically added forms such as inlines in the admin.
270 To make django-selectable work in the admin there is nothing more to do than include
271 the necessary static media as described in the
272 :ref:`Admin Integration <admin-jquery-include>` section.
274 If you are making use of the popular `django-dynamic-formset <http://code.google.com/p/django-dynamic-formset/>`_
275 then you can make django-selectable work by passing ``bindSelectables`` to the
276 `added <http://code.google.com/p/django-dynamic-formset/source/browse/trunk/docs/usage.txt#259>`_ option:
278     .. code-block:: html
280         <script type="text/javascript">
281             $(document).ready(function() {
282                 $('#my-formset').formset({
283                         added: bindSelectables
284                 });
285             });
286         </script>
288 Currently you must include the django-selectable javascript below this formset initialization
289 code for this to work. See django-selectable `issue #31 <https://github.com/mlavin/django-selectable/issues/31>`_
290 for some additional detail on this problem.
293 .. _advanced-label-formats:
295 Label Formats on the Client Side
296 --------------------------------------
298 The lookup label is the text which is shown in the list before it is selected.
299 You can use the :ref:`get_item_label <lookup-get-item-label>` method in your lookup
300 to do this on the server side. This works for most applications. However if you don't
301 want to write your HTML in Python or need to adapt the format on the client side you
302 can use the :ref:`formatLabel <javascript-formatLabel>` option.
304 ``formatLabel`` takes two paramaters the current label and the current selected item.
305 The item is a dictionary object matching what is returned by the lookup's
306 :ref:`format_item <lookup-format-item>`. ``formatLabel`` should return the string
307 which should be used for the label.
309 Going back to the ``CityLookup`` we can adjust the label to wrap the city and state
310 portions with their own classes for additional styling:
312     .. code-block:: html
314         <script type="text/javascript">
315             $(document).ready(function() {
316                 function formatLabel(label, item) {
317                     var data = label.split(',');
318                     return '<span class="city">' + data[0] + '</span>, <span class="state">' + data[1] + '</span>';
319                 }
320                 $('#id_city_0').djselectable('option', 'formatLabel', formatLabel);
321             });
322         </script>
324 This is a rather simple example but you could also pass additional information in ``format_item``
325 such as a flag of whether the city is the capital and render the state captials differently.
327 .. _advanced-bootstrap:
329 Using with Twitter Bootstrap
330 --------------------------------------
332 django-selectable can work along side with Twitter Bootstrap but there are a few things to
333 take into consideration. Both jQuery UI and Bootstrap define a ``$.button`` plugin. This
334 plugin is used by default by django-selectable and expects the UI version. If the jQuery UI
335 JS is included after the Bootstrap JS then this will work just fine but the Bootstrap
336 button JS will not be available. This is the strategy taken by the  `jQuery UI Bootstrap
337 <http://addyosmani.github.com/jquery-ui-bootstrap/>`_ theme.
339 Another option is to rename the Bootstrap plugin using the ``noConflict`` option.
341     .. code-block:: html
343         <!-- Include Bootstrap JS -->
344         <script>$.fn.bootstrapBtn = $.fn.button.noConflict();</script>
345         <!-- Include jQuery UI JS -->
347 Even with this some might complain that it's too resource heavy to include all of
348 jQuery UI when you just want the autocomplete to work with django-selectable. For
349 this you can use the `Download Builder <http://jqueryui.com/download/>`_ to build
350 a minimal set of jQuery UI widgets. django-selectable requires the UI core, autocomplete,
351 menu and button widgets. None of the effects or interactions are needed. Minified
352 this totals around 100 kb of JS, CSS and images (based on jQuery UI 1.10).
354 .. note::
356     For a comparison this is smaller than the minified Bootstrap 2.3.0 CSS
357     which is 105 kb not including the responsive CSS or the icon graphics.
359 It is possible to remove the dependency on the UI button plugin and instead
360 use the Bootstrap button styles. This is done by overriding
361 the ``_comboButtonTemplate`` and ``_removeButtonTemplate`` functions used to
362 create the buttons. An example is given below.
364     .. code-block:: html
366         <script>
367             $.ui.djselectable.prototype._comboButtonTemplate = function (input) {
368                 var icon = $("<i>").addClass("icon-chevron-down");
369                 // Remove current classes on the text input
370                 $(input).attr("class", "");
371                 // Wrap with input-append
372                 $(input).wrap('<div class="input-append" />');
373                 // Return button link with the chosen icon
374                 return $("<a>").append(icon).addClass("btn btn-small");
375             };
376             $.ui.djselectable.prototype._removeButtonTemplate = function (item) {
377                 var icon = $("<i>").addClass("icon-remove-sign");
378                 // Return button link with the chosen icon
379                 return $("<a>").append(icon).addClass("btn btn-small pull-right");
380             };
381         </script>