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