1==========================
2django-formset-js-improved
3==========================
4
5**This is a fork of django-formset-js that adds support for reordering and nested formsets.**
6
7The fork was done because the original author (Tim Heap) did not get around merging my PRs and I
8need this to be on PyPI so I can depend on it. Please don't expect this fork to be permanently
9maintained.
10
11A wrapper for a JavaScript formset helper.
12
13Installing
14----------
15
16Install via pip::
17
18    pip install django-formset-js-improved
19
20Then add it and its dependancy ``django-jquery-js``
21to your ``INSTALLED_APPS``:
22
23.. code-block:: python
24
25    INSTALLED_APPS += (
26        'django.contrib.staticfiles',
27        'jquery',
28        'djangoformsetjs',
29    )
30
31Using
32-----
33
34Include the JavaScript library
35******************************
36
37Both jQuery and this library must be included in your page.
38The simplest way to do this is to add the scripts as media dependencies on your form:
39
40.. code-block:: python
41
42    from djangoformsetjs.utils import formset_media_js
43
44    class MyForm(forms.Form):
45        class Media(object):
46            js = formset_media_js + (
47                # Other form media here
48            )
49
50    MyFormSet = formset_factory(MyForm)
51
52And then include the Media of the form in your template:
53
54.. code-block:: html+django
55
56    {{ formset.media }}
57
58Alternatively, simply add the script tags:
59
60.. code-block:: html+django
61
62    <script src="{{ STATIC_URL }}js/jquery.js"></script>
63    <script src="{{ STATIC_URL }}js/jquery.formset.js"></script>
64
65Render the formset
66******************
67
68So that the library can work with your formset,
69certain blocks of your formset need to be marked up with ``data-formset-...`` attributes:
70
71.. code-block:: html+django
72
73    {% load formset_tags %}
74
75    <div id="formset" data-formset-prefix="{{ formset.prefix }}">
76        {{ formset.management_form }}
77
78        <div data-formset-body>
79            <!-- New forms will be inserted in here -->
80            {% for form in formset %}
81                <div data-formset-form>
82                    {{ form }}
83                    <button type="button" data-formset-move-up-button>Move up</button>
84                    <button type="button" data-formset-move-down-button>Move down</button>
85                    <button type="button" data-formset-delete-button>Delete form</button>
86                </div>
87            {% endfor %}
88        </div>
89
90        <!-- The empty form template. By wrapping this in a <script> tag, the
91        __prefix__ placeholder can easily be replaced in both attributes and
92        any scripts -->
93        <script type="form-template" data-formset-empty-form>
94            {% escapescript %}
95                <div data-formset-form>
96                    {{ formset.empty_form }}
97                    <button type="button" data-formset-move-up-button>Move up</button>
98                    <button type="button" data-formset-move-down-button>Move down</button>
99                    <button type="button" data-formset-delete-button>Delete form</button>
100                </div>
101            {% endescapescript %}
102        </script>
103
104        <!-- This button will add a new form when clicked -->
105        <input type="button" value="Add another" data-formset-add>
106
107        <script>jQuery(function($) {
108            $("#formset").formset({
109                animateForms: true,
110                reorderMode: 'dom',
111            });
112        });</script>
113
114    </div>
115
116The ``data-formset-`` data attributes are:
117
118``data-formset-prefix``
119  The value of ``{{ formset.prefix }}``.
120  This is used to find the management form.
121
122``data-formset-body``
123  This indicates where all the child forms are.
124  New forms are inserted in here.
125
126``data-formset-form``
127  Every form (including the empty form) should have this attribute.
128
129``data-formset-empty-form``
130  The element that contains the empty form template.
131  For best results, use a ``<script>`` tag.
132
133``data-formset-add``
134  A button that adds a new form.
135
136``data-formset-delete-button``
137  A button that deletes that form.
138
139``data-formset-move-up-button``
140  A button that moves that form one row up in a sortable formset.
141
142``data-formset-move-down-button``
143  A button that moves that form one row down in a sortable formset.
144
145The empty form template is wrapped in a ``<script>`` as plain text.
146This stops any JavaScript attached to widgets from running upon page load,
147and makes finding and replacing the ``__prefix__`` placeholder easier.
148The contents of the ``<script>`` should be wrapped in a ``{% escapescript %}`` block
149to prevent any script tags inside from closing the wrapping script tag prematurely.
150
151When the ``data-formset-add`` button is clicked, the ``formAdded`` event is
152fired on the form which was added. This event propagates upwards, and as such
153can be handled from the form container.
154For example, to select the new form added for form additions from the above
155example, bind as such:
156
157.. code-block:: javascript
158
159    $('#formset').on('formAdded', function(event) {
160        newForm = event.target;
161        //Do Stuff
162    });
163
164If the forms can be deleted, and contain a delete checkbox,
165the following actions occur:
166
167* When the checkbox is checked, marking the form for deletion,
168  the ``formDeleted`` event is fired on the ``data-formset-form`` container,
169  and the ``data-formset-form-deleted`` attribute is added.
170
171* When the checkbox is unchecked, marking the form as active again,
172  the ``formAdded`` event is fired on the ``data-formset-form`` container,
173  and the ``data-formset-form-deleted`` attribute is removed.
174
175If the forms can be deleted, and contain a delete button,
176pressing the delete button will toggle the delete checkbox for that form.
177The ``DELETE`` field should be hidden if the delete button is used.
178The delete button is identified by the ``data-formset-delete-button`` attribute:
179
180.. code-block:: html+django
181
182    {% for form in formset %}
183        <div data-formset-form>
184            {{ form.name }}
185            {{ form.age }}
186
187            <div class="hidden">{{ form.DELETE }}</div>
188            <button type="button" data-formset-delete-button>Delete form</button>
189        </div>
190    {% endfor %}
191
192If the ``animateForms`` option is set when the formset is created,
193adding and deleting forms will be animated by sliding the forms in and out.
194
195If the forms can be ordered and contain a order input field, it is expected
196that the forms are in order on page load
197
198If the forms contain two ordering buttons, identified by ``data-formset-move-up-button``
199and ``data-formset-move-down-button``, those buttons modify the value in the
200order input field by swapping it's value with the closest lower or higher value.
201In this case, the ``ORDER`` field should be hidden.
202
203If the ``reorderMode`` option is set to ``dom``, the forms will change their places
204in the DOM each time one of the ``ORDER`` fields is being changed. If it is set to
205``animate``, they will be sliding onto their new places. **Attention**: The animated
206ordering feature has to make assumptions about your markup and CSS, e.g. that both your
207formset container (``data-formset-body``) and your form (``data-formset-form``) are ``div``
208elements and can be to ``position: relative``/``position: absolute`` for the the time of
209the animation without harm. *This might not work for you out of the box*.
210
211Options
212*******
213
214The jQuery plugin takes the following options:
215
216``form``:
217  The selector to find forms.
218  Defaults to ``[data-formset-form]``.
219
220``emptyForm``:
221  The selector to find the empty form template.
222  Defaults to ``script[type=form-template][data-formset-empty-form]``.
223
224``body``:
225  The selector to find the formset body.
226  New forms will be inserted at the bottom of this element.
227  Defaults to ``[data-formset-body]``.
228
229``add``:
230  The selector to find the add button.
231  Defaults to ``[data-formset-add]``.
232
233``deleteButton``:
234  The selector to find the delete button within a form.
235  Defaults to ``[data-formset-delete-button]``.
236
237``hasMaxFormsClass``:
238  The class added to the formset when the maximum number of forms is reached.
239  The maximum number of forms is pulled from the management form.
240  Defaults to ``has-max-forms``.
241
242``animateForms``:
243  Whether to animate form addition/deletion.
244
245``reorderMode``:
246  Can be ``none``, ``dom`` or ``animate``, see above for an explaination.
247  Defaults to ``none``.
248  Defaults to ``false``.
249
250Javascript API
251--------------
252
253If the bundled functionality is not for you,
254you can interact with the formset using the JavaScript API.
255All the behaviour is driven by a ``Formset`` class.
256To get a ``Formset`` for an element, call:
257
258.. code-block:: javascript
259
260    var formset = $('#my-form').formset('getOrCreate');
261
262This can be called multiple times on a single element,
263and will always return the same ``Formset`` instance.
264All the methods and attributes listed below operate on a ``Formset`` instance.
265
266``Formset.opts``
267    The options used to create this ``Formset``.
268
269``Formset.$formset``
270    The element the ``Formset`` was created for.
271
272``Formset.$emptyForm``
273    The empty form template used to create new forms.
274
275``Formset.$body``
276    The element where new forms are created.
277
278``Formset.$add``
279    The button used to add new forms.
280
281``Formset.addForm()``
282    Add a form to the ``Formset``.
283    If the maximum number of forms would be exceeded if another form was added,
284    an error will be thrown.
285
286``Formset.$forms()``
287    Get a jQuery object of all the forms in the ``Formset``.
288
289``Formset.$managementForm(field)``
290    Get a jQuery object for the management form field ``field``:
291
292    .. code-block:: javascript
293
294        // Update the TOTAL_FORMS management form field
295        this.$managementForm('TOTAL_FORMS').val(10);
296
297``Formset.totalFormCount()``
298    Count the total number of forms in the ``Formset``, including deleted forms.
299
300``Formset.activeFormCount()``
301    Count the total number of active (not deleted) forms in the ``Formset``.
302
303``Formset.deletedFormCount()``
304    Count the number of deleted forms in the ``Formset``.
305
306``Formset.hasMaxForms()``
307    Return true if the ``Formset`` has its maximum number of forms.
308
309``Formset.checkMaxForms()``
310    Check how many forms are in the ``Formset``,
311    and set the relevant classes on the ``Formset`` element
312    if the ``Formset`` has reached its limit.
313
314``empty_prefix``:
315  The prefix placeholder your formset uses for the empty form. This is only
316  needed when you subclass FormSet to change this and defaults to ``__prefix__``.
317
318
319Example
320-------
321
322A minimal example project is provided in the ``example/`` directory.
323Read ``example/README`` for more information
324
325Developing
326----------
327
328When running ``./setup.py sdist``, the JavaScript asset is minified using
329UglifyJS if it is installed. To install UglifyJS, install node.js and npm, and
330run::
331
332    npm install uglifyjs
333
334You can minify the scripts manually using::
335
336    ./setup.py minify
337