├── .gitignore ├── .travis.yml ├── .jshintrc ├── src ├── .jshintrc ├── django-formset.coffee ├── django-formset.js.map └── django-formset.js ├── libs ├── jquery-loader.js └── qunit │ └── qunit.css ├── package.json ├── test ├── .jshintrc ├── django-formset_test-bootstrap.js.map ├── django-formset-bootstrap.html ├── django-formset_test-bootstrap.js ├── django-formset_test-bootstrap.coffee ├── django-formset.html ├── django-formset_test.js.map ├── django-formset_test.js └── django-formset_test.coffee ├── django-formset.jquery.json ├── LICENSE-MIT ├── CONTRIBUTING.md ├── Gruntfile.js ├── README.md └── dist ├── django-formset.min.js └── django-formset.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true 14 | } 15 | -------------------------------------------------------------------------------- /src/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "browser": true, 14 | "predef": ["jQuery"] 15 | } 16 | -------------------------------------------------------------------------------- /libs/jquery-loader.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Default to the local version. 3 | var path = '../libs/jquery/jquery.js'; 4 | // Get any jquery=___ param from the query string. 5 | var jqversion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/); 6 | // If a version was specified, use that version from code.jquery.com. 7 | if (jqversion) { 8 | path = 'http://code.jquery.com/jquery-' + jqversion[1] + '.js'; 9 | } 10 | // This is the only time I'll ever use document.write, I promise! 11 | document.write(''); 12 | }()); 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "django-formset", 3 | "version": "0.3.0", 4 | "engines": { 5 | "node": ">= 0.8.0" 6 | }, 7 | "scripts": { 8 | "test": "grunt qunit" 9 | }, 10 | "devDependencies": { 11 | "grunt-contrib-jshint": "~0.6.0", 12 | "grunt-contrib-qunit": "~0.2.0", 13 | "grunt-contrib-concat": "~0.3.0", 14 | "grunt-contrib-uglify": "~0.2.0", 15 | "grunt-contrib-watch": "~0.4.0", 16 | "grunt-contrib-clean": "~0.4.0", 17 | "grunt-contrib-coffee": "~0.10.1", 18 | "grunt-coffeelint": "~0.0.8", 19 | "grunt": "~0.4.2", 20 | "grunt-cli": "~0.1.13" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "browser": true, 14 | "predef": [ 15 | "jQuery", 16 | "QUnit", 17 | "module", 18 | "test", 19 | "asyncTest", 20 | "expect", 21 | "start", 22 | "stop", 23 | "ok", 24 | "equal", 25 | "notEqual", 26 | "deepEqual", 27 | "notDeepEqual", 28 | "strictEqual", 29 | "notStrictEqual", 30 | "throws" 31 | ] 32 | } -------------------------------------------------------------------------------- /django-formset.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "django-formset", 3 | "title": "Django Formset", 4 | "description": "Dynamic formsets for Django", 5 | "version": "0.3.0", 6 | "homepage": "https://github.com/mbertheau/jquery.django-formset", 7 | "author": { 8 | "name": "Markus Bertheau", 9 | "email": "mbertheau@gmail.com", 10 | "url": "https://github.com/mbertheau" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/mbertheau/jquery.django-formset.git" 15 | }, 16 | "bugs": "https://github.com/mbertheau/jquery.django-formset/issues", 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/mbertheau/jquery.django-formset/blob/master/LICENSE-MIT" 21 | } 22 | ], 23 | "dependencies": { 24 | "jquery": "1.9.1" 25 | }, 26 | "keywords": [] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Markus Bertheau 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Important notes 4 | Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! 5 | 6 | ### Code style 7 | Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** 8 | 9 | ### PhantomJS 10 | While Grunt can run the included unit tests via [PhantomJS](http://phantomjs.org/), this shouldn't be considered a substitute for the real thing. Please be sure to test the `test/*.html` unit test file(s) in _actual_ browsers. 11 | 12 | ## Modifying the code 13 | First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. 14 | 15 | Test that Grunt's CLI is installed by running `grunt --version`. If the command isn't found, run `npm install -g grunt-cli`. For more information about installing Grunt, see the [getting started guide](http://gruntjs.com/getting-started). 16 | 17 | 1. Fork and clone the repo. 18 | 1. Run `npm install` to install all dependencies (including Grunt). 19 | 1. Run `grunt` to grunt this project. 20 | 21 | Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken. 22 | 23 | ## Submitting pull requests 24 | 25 | 1. Create a new branch, please don't work in your `master` branch directly. 26 | 1. Add failing tests for the change you want to make. Run `grunt` to see the tests fail. 27 | 1. Fix stuff. 28 | 1. Run `grunt` to see if the tests pass. Repeat steps 2-4 until done. 29 | 1. Open `test/*.html` unit test file(s) in actual browser to ensure tests pass everywhere. 30 | 1. Update the documentation to reflect any changes. 31 | 1. Push to your fork and submit a pull request. 32 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | // Metadata. 8 | pkg: grunt.file.readJSON('django-formset.jquery.json'), 9 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 10 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 11 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 12 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 13 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n', 14 | // Task configuration. 15 | clean: { 16 | files: ['dist'] 17 | }, 18 | concat: { 19 | options: { 20 | banner: '<%= banner %>', 21 | stripBanners: true 22 | }, 23 | dist: { 24 | src: ['src/<%= pkg.name %>.js'], 25 | dest: 'dist/<%= pkg.name %>.js' 26 | }, 27 | }, 28 | uglify: { 29 | options: { 30 | banner: '<%= banner %>' 31 | }, 32 | dist: { 33 | src: '<%= concat.dist.dest %>', 34 | dest: 'dist/<%= pkg.name %>.min.js' 35 | }, 36 | }, 37 | qunit: { 38 | files: ['test/**/*.html'] 39 | }, 40 | jshint: { 41 | gruntfile: { 42 | options: { 43 | jshintrc: '.jshintrc' 44 | }, 45 | src: 'Gruntfile.js' 46 | }, 47 | }, 48 | coffeelint: { 49 | src: { 50 | src: ['src/**/*.coffee'] 51 | }, 52 | test: { 53 | src: ['test/**/*.coffee'] 54 | }, 55 | options: { 56 | 'arrow_spacing': {'level': 'error'}, 57 | 'colon_assignment_spacing': {'level': 'error', 58 | 'spacing': {'left': 0, 'right': 1}}, 59 | 'cyclomatic_complexity': {'level': 'warn'}, 60 | 'line_endings': {'level': 'error'}, 61 | 'newlines_after_classes': {'level': 'error', 'value': 1}, 62 | 'no_empty_param_list': {'level': 'error'}, 63 | 'no_implicit_parens': {'level': 'error'}, 64 | 'no_standalone_at': {'level': 'error'}, 65 | 'space_operators': {'level': 'error'}, 66 | }, 67 | }, 68 | watch: { 69 | gruntfile: { 70 | files: '<%= jshint.gruntfile.src %>', 71 | tasks: ['jshint:gruntfile'] 72 | }, 73 | src: { 74 | files: ['src/**/*.coffee'], 75 | tasks: ['coffee:src', 'coffeelint:src', 'qunit', 'clean', 'concat', 'uglify'] 76 | }, 77 | test: { 78 | files: ['test/**/*.coffee', 'test/django-formset.html'], 79 | tasks: ['coffee:test', 'coffeelint:test', 'qunit', 'clean', 'concat', 'uglify'] 80 | }, 81 | }, 82 | coffee: { 83 | options: { 84 | bare: true, 85 | sourceMap: true, 86 | }, 87 | src: { 88 | files: { 89 | 'src/django-formset.js': 'src/django-formset.coffee' 90 | }, 91 | }, 92 | test: { 93 | files: { 94 | 'test/django-formset_test.js': 'test/django-formset_test.coffee', 95 | 'test/django-formset_test-bootstrap.js': 'test/django-formset_test-bootstrap.coffee' 96 | }, 97 | }, 98 | }, 99 | }); 100 | 101 | // These plugins provide necessary tasks. 102 | grunt.loadNpmTasks('grunt-contrib-clean'); 103 | grunt.loadNpmTasks('grunt-contrib-concat'); 104 | grunt.loadNpmTasks('grunt-contrib-uglify'); 105 | grunt.loadNpmTasks('grunt-contrib-qunit'); 106 | grunt.loadNpmTasks('grunt-contrib-jshint'); 107 | grunt.loadNpmTasks('grunt-contrib-watch'); 108 | grunt.loadNpmTasks('grunt-contrib-coffee'); 109 | grunt.loadNpmTasks('grunt-coffeelint'); 110 | 111 | // Default task. 112 | grunt.registerTask('default', ['coffee', 'coffeelint', 'jshint', 'qunit', 'clean', 'concat', 'uglify']); 113 | 114 | }; 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Formset 2 | 3 | Add new forms to Django form sets dynamically. Supports nested formsets and Bootstrap tabs. 4 | 5 | [](https://travis-ci.org/mbertheau/jquery.django-formset) 6 | 7 | Download the [production version][min] or the [development version][max]. 8 | 9 | [min]: https://raw.github.com/mbertheau/jquery.django-formset/master/dist/django-formset.min.js 10 | [max]: https://raw.github.com/mbertheau/jquery.django-formset/master/dist/django-formset.js 11 | 12 | ## Examples 13 | 14 | ### A simple formset 15 | 16 | [Code][Dynamic] 17 | 18 | [Dynamic]: https://github.com/mbertheau/jquery.django-formset-example/blob/master/blocks/templates/blocks/building_form_dynamic.html 19 | 20 | The plugin doesn't automatically add an "add another form" button. You have to do that yourself 21 | somewhere and hook it up to call the `addForm` method on the object returned by `djangoFormset()`. 22 | 23 | Pass a jQuery selection of the form elements in your formset. One of the form elements must be a 24 | form template, and it must have the CSS class passed as option `formTemplateClass` (`'empty-form'` 25 | by default). That class should be styled to be invisible. The correct form prefix is derived from 26 | the name of the first form element in the form template. For every form a Form object is created. If 27 | a form includes a delete checkbox it is replaced with a delete button. Dynamically added forms 28 | always have a delete button. The form object is also accessible via `.data('djangoFormset.Form')` on 29 | the any of the form elements. 30 | 31 | When a form is added the template is copied and its `empty-form` CSS class is removed. The new form 32 | is always added after the last existing form, or after the form template, if there are no existing 33 | forms. Inside the new form in the attributes of the following elements the correct form index is 34 | put in place of `__prefix__`: 35 | 36 | * `id` and `name` attributes of ``, `` and `` 37 | * `for` attribute of `` 38 | * `id` attribute of the form's root element if that's a `` 39 | 40 | After adding the `formAdded` event is triggered to which you can attach a listener to do additional 41 | setup on the new form. The listener is passed the newly created form object. The `elem` member of 42 | the form object is the jquery object that contains the DOM node of the new form. 43 | 44 | When a form is deleted that was dynamically added before, it is removed from the DOM completely and 45 | the remaining forms are renumbered. If a form is deleted that existed on page load, it is just 46 | hidden and marked as deleted. 47 | 48 | Forms can be deleted from JavaScript by calling `delete()` on the form object or `deleteForm(index)` 49 | on the object returned by `djangoFormset`. `index` is the 0-based form number. Hidden forms count 50 | towards this index as well. 51 | 52 | If something doesn't work have a look at the JavaScript console. For a number of error conditions 53 | exceptions are raised. 54 | 55 | ### A Bootstrap2-tabbed formset 56 | 57 | [Code][Dynamic with tabs] 58 | 59 | [Dynamic with tabs]: https://github.com/mbertheau/jquery.django-formset-example/blob/master/blocks/templates/blocks/building_form_dynamic_tabs.html 60 | 61 | The tab functionality is triggered by the template form having the CSS class `tab-pane`. 62 | 63 | To find the tab navigation `djangoFormset` then looks for a child of a `.nav` that has an `` or a 64 | `` with a `href` or `data-target` that references the `id` of the form template root 65 | element. In the example that `id` is the form prefix. 66 | 67 | When a new form is added a new tab is added after the last visible tab or after the template tab if 68 | there's no visible tab. Inside the tab the `href` and `data-target` attributes of `` and 69 | `` elements are updated to reflect the new form index. 70 | 71 | When a form is deleted the tab is hidden or removed depending on whether the form existed at page 72 | load time or not. 73 | 74 | ### A nested Bootstrap2-tabbed formset 75 | 76 | Use the python package [django-nested-formset] for the Django side of things. 77 | 78 | [django-nested-formset]: https://pypi.python.org/pypi/django-nested-formset 79 | 80 | In the view: 81 | 82 | ```python 83 | formset_class = nestedformset_factory( 84 | models.Block, 85 | models.Building, 86 | nested_formset=inlineformset_factory(models.Building, models.Tenant), 87 | ) 88 | ``` 89 | 90 | [Template code][Dynamic nested with tabs] 91 | 92 | [Dynamic nested with tabs]: https://github.com/mbertheau/jquery.django-formset-example/blob/master/blocks/templates/blocks/building_form_dynamic_tabs_nested.html 93 | 94 | The plugin takes care to replace just the first occurrence of the template marker `__prefix__`. It 95 | also is careful not to leave the boundaries of the current form instance when it applies the needed 96 | modifications to the DOM. Lastly it provides events to hook up the initialization of the inner 97 | formset. 98 | 99 | I haven't tried it, but more than one nesting level should work just fine. 100 | 101 | ## Release History 102 | 103 | ### 0.3.0 (unreleased) 104 | 105 | * Make examples available at [github.com/mbertheau/jquery.django-formset-example](https://github.com/mbertheau/jquery.django-formset-example) 106 | * Handle the delete input being inside its own label (#4) 107 | 108 | ### 0.2.0 (2014-09-02) 109 | 110 | * Add option `deleteButtonText` to change the text from the default "Delete" 111 | * Add method `Form.field(name)` to get a form field by its non-prefixed name 112 | * Add method `Form.prev()` to get the previous non-deleted form 113 | 114 | ### 0.1.0 (2014-03-08) 115 | 116 | Initial Release 117 | -------------------------------------------------------------------------------- /libs/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.11.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /dist/django-formset.min.js: -------------------------------------------------------------------------------- 1 | /*! Django Formset - v0.3.0 - 2014-11-15 2 | * https://github.com/mbertheau/jquery.django-formset 3 | * Copyright (c) 2014 Markus Bertheau; Licensed MIT */ 4 | var __hasProp={}.hasOwnProperty,__extends=function(a,b){function c(){this.constructor=a}for(var d in b)__hasProp.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};!function(a){var b;a.fn.djangoFormset=function(b){return new a.fn.djangoFormset.Formset(this,b)},b=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}return __extends(b,a),b}(Error),a.fn.djangoFormset.Formset=function(){function c(c,d){var e,f,g,h;if(this.opts=a.extend({},a.fn.djangoFormset.defaultOptions,d),0===c.length)throw new b("Empty selector.");if(this.template=c.filter("."+this.opts.formTemplateClass),0===this.template.length)throw new b("Can't find template (looking for ."+this.opts.formTemplateClass+")");if(g=this.template.find("input,select,textarea").first().attr("name"),!g)throw new b("Can't figure out form prefix because there's no form element in the form template. Please add one.");if(h=g.indexOf("-__prefix__"),-1===h)throw new b("Can't figure out form prefix from template because it doesn't contain '-__prefix__'.");if(this.prefix=g.substring(0,h),this.totalForms=a("#id_"+this.prefix+"-TOTAL_FORMS"),0===this.totalForms.length)throw new b("Management form field 'TOTAL_FORMS' not found for prefix "+this.prefix+".");this._initTabs(),f=c.not("."+this.opts.formTemplateClass),this.initialForms=f.length,a(this).on(this.opts.on),this.forms=f.map(function(b){return function(c,d){var e,f,g;return b.hasTabs&&(g=a.djangoFormset.getTabActivator(d.id),f=new b.opts.tabClass(g.closest(".nav > *"))),e=new b.opts.formClass(a(d),b,c,f),a(b).trigger("formInitialized",[e]),e}}(this)),this.forms.length!==parseInt(this.totalForms.val())&&console.error("TOTAL_FORMS is "+this.totalForms.val()+", but "+this.forms.length+" non-template elements found in passed selection."),e=this.forms.filter(function(){return this.deleteInput.val()}),e.each(function(){return this["delete"]()}),this.insertAnchor=c.not("."+this.opts.formTemplateClass).last(),0===this.insertAnchor.length&&(this.insertAnchor=this.template)}return c.prototype._initTabs=function(){var c,d;if(this.hasTabs=this.template.is(".tab-pane"),this.hasTabs){if(c=a.djangoFormset.getTabActivator(this.template.attr("id")),0===c.length)throw new b("Template is .tab-pane but couldn't find corresponding tab activator.");if(d=c.closest(".nav"),0===d.length)throw new b("Template is .tab-pane but couldn't find corresponding .nav.");if(this.tabTemplate=d.children("."+this.opts.formTemplateClass),0===this.tabTemplate.length)throw new b("Tab nav template not found (looking for ."+this.opts.formTemplateClass+").")}},c.prototype.addForm=function(){var b,c,d,e,f;return this.hasTabs&&(e=this.tabTemplate.clone().removeClass(this.opts.formTemplateClass),d=new this.opts.tabClass(e),f=this.forms.length>0?this.forms[this.forms.length-1].tab.elem:this.tabTemplate,e.insertAfter(f)),c=this.template.clone().removeClass(this.opts.formTemplateClass),b=new this.opts.formClass(c,this,parseInt(this.totalForms.val()),d),c.insertAfter(this.insertAnchor),this.insertAnchor=c,this.forms.push(b),this.totalForms.val(parseInt(this.totalForms.val())+1),this.hasTabs&&d.activate(),a(this).trigger("formInitialized",[b]),a(this).trigger("formAdded",[b]),b},c.prototype.deleteForm=function(a){var b;b=this.forms[a],b["delete"]()},c.prototype.handleFormRemoved=function(a){var b,c,d,e,f;for(this.totalForms.val(parseInt(this.totalForms.val())-1),this.forms.splice(a,1),f=this.forms,c=d=0,e=f.length;e>d;c=++d)b=f[c],b._updateFormIndex(c);this.insertAnchor=0===this.forms.length?this.template:this.forms[this.forms.length-1].elem},c}(),a.fn.djangoFormset.Form=function(){function b(a,b,c,d){var e;this.elem=a,this.formset=b,this.index=c,this.tab=d,this.elem.data("djangoFormset.Form",this),void 0!==this.index&&this._initFormIndex(this.index),this.deleteInput=this.field("DELETE"),e=this.index0||!e)&&this._replaceDeleteCheckboxWithButton()}return b.prototype.getDeleteButton=function(){return a(" "+this.formset.opts.deleteButtonText+" ")},b.prototype.insertDeleteButton=function(){this.deleteInput.length>0?this.deleteInput.after(this.deleteButton):(this.elem.is("TR")?this.elem.children().last():this.elem.is("UL")||this.elem.is("OL")?this.elem.append("li").children().last():this.elem).append(this.deleteButton)},b.prototype["delete"]=function(){var a,b,c;return a=this.index0&&b.data("djangoFormset.tab").activate()),void(a?(this.deleteInput.length>0&&this.deleteInput.val("on"),this.tab&&this.tab.elem.hide(),this.hide()):(this.tab&&this.tab.elem.remove(),this.elem.remove(),this.formset.handleFormRemoved(this.index))))},b.prototype.hide=function(){return this.elem.hide()},b.prototype.field=function(a){return this.elem.find("[name='"+this.formset.prefix+"-"+this.index+"-"+a+"']")},b.prototype.prev=function(){var a,b,c;for(c=this.formset.forms.slice(0,+(this.index-1)+1||9e9),b=c.length-1;b>=0;b+=-1)if(a=c[b],a.elem.is(":visible"))return a},b.prototype._replaceDeleteCheckboxWithButton=function(){var b,c;this.deleteInput.length>0&&(c=a(""),b=this.elem.find("label[for='"+this.deleteInput.attr("id")+"']"),b.has(this.deleteInput).length>0?b.replaceWith(c):(b.remove(),this.deleteInput.replaceWith(c)),this.deleteInput=c),this.deleteButton=this.getDeleteButton(),this.deleteButton.on("click",function(a){return function(){return a["delete"]()}}(this)),this.insertDeleteButton()},b.prototype._replaceFormIndex=function(b,c){var d,e,f;this.index=c,e=new RegExp(""+this.formset.prefix+"-"+b),d=""+this.formset.prefix+"-"+c,f=function(a){var b,c,f,g,h,i,j;f={input:["id","name"],select:["id","name"],textarea:["id","name"],label:["for"],div:["id"],"*":["href","data-target"]},g=a.get(0).tagName,c=[],g.toLowerCase()in f&&(c=f[g.toLowerCase()]),c.push.apply(c,f["*"]),j=[];for(h=0,i=c.length;i>h;h++)b=c[h],j.push(a.attr(b)?a.attr(b,a.attr(b).replace(e,d)):void 0);return j},f(this.elem),this.elem.find("input, select, textarea, label").each(function(){f(a(this))}),this.tab&&(f(this.tab.elem),this.tab.elem.find("a, button").each(function(){f(a(this))}))},b.prototype._initFormIndex=function(a){this._replaceFormIndex("__prefix__",a)},b.prototype._updateFormIndex=function(a){this._replaceFormIndex("\\d+",a)},b}(),a.fn.djangoFormset.Tab=function(){function a(a){this.elem=a,this.elem.data("djangoFormset.tab",this)}return a.prototype.activate=function(){return this.elem.find("[data-toggle='tab']").trigger("click")},a.prototype.remove=function(){return this.elem.remove()},a}(),a.fn.djangoFormset.defaultOptions={formTemplateClass:"empty-form",formClass:a.fn.djangoFormset.Form,tabClass:a.fn.djangoFormset.Tab,deleteButtonText:"Delete"},a.djangoFormset={getTabActivator:function(b){return a("[href='#"+b+"'], [data-target='#"+b+"']")}}}(jQuery); -------------------------------------------------------------------------------- /test/django-formset_test-bootstrap.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "django-formset_test-bootstrap.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "django-formset_test-bootstrap.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "AAAA,IAAA;iSAAA;;AAAA,CAAC,SAAC,CAAD,GAAA;AAwBC,MAAA,WAAA;AAAA,EAAA,WAAA,GAAc,SAAA,GAAA;AACZ,QAAA,WAAA;AAAA,IAAA,WAAA,GAAc,CAAA,CAAE,gBAAF,CAAd,CAAA;AAAA,IACA,IAAC,CAAA,yBAAD,GAA6B,WAAW,CAAC,IAAZ,CAAiB,wBAAjB,CAD7B,CAAA;AAAA,IAEA,IAAC,CAAA,+BAAD,GAAmC,WAAW,CAAC,IAAZ,CACjC,+BADiC,CAFnC,CAAA;AAAA,IAIA,IAAC,CAAA,qBAAD,GAAyB,WAAW,CAAC,IAAZ,CAAiB,uBAAjB,CAJzB,CAAA;AAAA,IAKA,IAAC,CAAA,wBAAD,GAA4B,WAAW,CAAC,IAAZ,CAAiB,uBAAjB,CAL5B,CADY;EAAA,CAAd,CAAA;AAAA,EASA,MAAA,CAAO,yCAAP,EAAkD;AAAA,IAAA,KAAA,EAAO,WAAP;GAAlD,CATA,CAAA;AAAA,EAWA,IAAA,CAAK,+DAAL,EAAsE,SAAA,GAAA;AACpE,IAAA,MAAA,CAAO,CAAC,SAAA,GAAA;aACN,IAAC,CAAA,qBAAqB,CAAC,IAAvB,CAA4B,oBAA5B,CAAiD,CAAC,aAAlD,CAAA,EADM;IAAA,CAAD,CAAP,EAEE,sEAFF,EAGE,8CAHF,CAAA,CADoE;EAAA,CAAtE,CAXA,CAAA;AAAA,EAmBA,IAAA,CAAK,gCAAL,EAAuC,SAAA,GAAA;AACrC,IAAA,MAAA,CAAO,CAAC,SAAA,GAAA;aACN,IAAC,CAAA,wBAAwB,CAAC,IAA1B,CAA+B,oBAA/B,CAAoD,CAAC,aAArD,CAAA,EADM;IAAA,CAAD,CAAP,EAEE,yDAFF,EAGE,6CAHF,CAAA,CADqC;EAAA,CAAvC,CAnBA,CAAA;AAAA,EA2BA,IAAA,CAAK,uDAAL,EAA8D,SAAA,GAAA;AAC5D,QAAA,yCAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CADV,CAAA;AAAA,IAGA,WAAA,GAAc,OAAO,CAAC,IAAR,CAAa,WAAb,CAAyB,CAAC,IAA1B,CAAA,CAHd,CAAA;AAAA,IAIA,UAAA,GAAa,OAAO,CAAC,IAAR,CAAc,UAAA,GAAS,CAAA,WAAW,CAAC,IAAZ,CAAiB,IAAjB,CAAA,CAAT,GAAiC,IAA/C,CACX,CAAC,OADU,CACF,UADE,CAJb,CAAA;AAAA,IAOA,OAAO,CAAC,OAAR,CAAA,CAPA,CAAA;AAAA,IASA,KAAA,CAAM,WAAW,CAAC,IAAZ,CAAA,CAAkB,CAAC,IAAnB,CAAwB,IAAxB,CAAN,EAAqC,mCAArC,EACE,8BADF,CATA,CAAA;AAAA,IAYA,KAAA,CAAM,UAAU,CAAC,IAAX,CAAA,CAAiB,CAAC,IAAlB,CAAuB,GAAvB,CAA2B,CAAC,IAA5B,CAAiC,MAAjC,CAAN,EACE,oCADF,EAEE,iDAFF,CAZA,CAAA;AAAA,IAgBA,KAAA,CAAM,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CAAwC,CAAC,IAAzC,CAA8C,qBAA9C,CACJ,CAAC,IADG,CACE,MADF,CAAN,EACiB,oCADjB,EAEE,4BAFF,CAhBA,CAD4D;EAAA,CAA9D,CA3BA,CAAA;AAAA,EAmDA,IAAA,CAAK,uEAAL,EACE,SAAA,GAAA;AACE,QAAA,2BAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CADV,CAAA;AAAA,IAGA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CAHZ,CAAA;AAAA,IAIA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CAJA,CAAA;AAAA,IAMA,KAAA,CAAM,SAAS,CAAC,MAAV,CAAiB,SAAjB,CAA2B,CAAC,MAAlC,EAA0C,CAA1C,EACE,wCADF,CANA,CAAA;AAAA,IASA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CATZ,CAAA;AAAA,IAUA,KAAA,CAAM,SAAS,CAAC,IAAV,CAAe,qBAAf,CAAqC,CAAC,IAAtC,CAA2C,MAA3C,CAAN,EACE,oCADF,EAEE,oCAFF,CAVA,CADF;EAAA,CADF,CAnDA,CAAA;AAAA,EAsEA,IAAA,CAAK,wEAAL,EACA,SAAA,GAAA;AACE,QAAA,2BAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CADV,CAAA;AAAA,IAEA,OAAO,CAAC,IAAR,CAAa,mCAAb,CAAiD,CAAC,EAAlD,CAAqD,CAArD,CAAuD,CAAC,OAAxD,CAAgE,OAAhE,CAFA,CAAA;AAAA,IAIA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CAJZ,CAAA;AAAA,IAKA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CALA,CAAA;AAAA,IAOA,KAAA,CAAM,SAAS,CAAC,EAAV,CAAa,SAAb,CAAN,EAA+B,KAA/B,EACE,gDADF,CAPA,CAAA;AAAA,IAUA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CAVZ,CAAA;AAAA,IAWA,KAAA,CAAM,SAAS,CAAC,IAAV,CAAe,qBAAf,CAAqC,CAAC,IAAtC,CAA2C,MAA3C,CAAN,EACE,oCADF,EAEE,4CAFF,CAXA,CADF;EAAA,CADA,CAtEA,CAAA;AAAA,EA0FA,IAAA,CAAK,sEAAL,EACA,SAAA,GAAA;AACE,QAAA,2BAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CADV,CAAA;AAAA,IAEA,OAAO,CAAC,IAAR,CAAa,mCAAb,CAAiD,CAAC,IAAlD,CAAA,CAAwD,CAAC,OAAzD,CAAiE,OAAjE,CAFA,CAAA;AAAA,IAIA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CAJZ,CAAA;AAAA,IAKA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CALA,CAAA;AAAA,IAOA,KAAA,CAAM,SAAS,CAAC,MAAV,CAAiB,SAAjB,CAA2B,CAAC,MAAlC,EAA0C,CAA1C,EACE,gDADF,CAPA,CAAA;AAAA,IAUA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CAVZ,CAAA;AAAA,IAWA,KAAA,CAAM,SAAS,CAAC,IAAV,CAAe,qBAAf,CAAqC,CAAC,IAAtC,CAA2C,MAA3C,CAAN,EACE,oCADF,EAEE,4CAFF,CAXA,CADF;EAAA,CADA,CA1FA,CAAA;AAAA,EA8GA,IAAA,CAAK,sDAAL,EAA6D,SAAA,GAAA;AAC3D,QAAA,gBAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CADV,CAAA;AAAA,IAGA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CAHA,CAAA;AAAA,IAIA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CAJA,CAAA;AAAA,IAKA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CALA,CAAA;AAAA,IAOA,KAAA,CAAM,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,6BAA9B,CAA4D,CAAC,MAAnE,EACE,CADF,EACK,4BADL,CAPA,CAD2D;EAAA,CAA7D,CA9GA,CAAA;AAAA,EA4HA,IAAA,CAAK,iEAAL,EAAwE,SAAA,GAAA;AACtE,QAAA,2BAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CADV,CAAA;AAAA,IAEA,OAAO,CAAC,IAAR,CAAa,mCAAb,CAAiD,CAAC,IAAlD,CAAA,CAAwD,CAAC,OAAzD,CAAiE,OAAjE,CAFA,CAAA;AAAA,IAIA,OAAO,CAAC,OAAR,CAAA,CAJA,CAAA;AAAA,IAKA,OAAO,CAAC,OAAR,CAAA,CALA,CAAA;AAAA,IAMA,OAAO,CAAC,IAAR,CAAa,mCAAb,CAAiD,CAAC,EAAlD,CAAqD,CAArD,CAAuD,CAAC,OAAxD,CAAgE,OAAhE,CANA,CAAA;AAAA,IAOA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CAPA,CAAA;AAAA,IASA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CATZ,CAAA;AAAA,IAUA,KAAA,CAAM,SAAS,CAAC,IAAV,CAAe,qBAAf,CAAqC,CAAC,IAAtC,CAA2C,MAA3C,CAAN,EACE,oCADF,EAEE,oCAFF,CAVA,CADsE;EAAA,CAAxE,CA5HA,CAAA;AAAA,EA8IA,IAAA,CAAK,yDAAL,EAAgE,SAAA,GAAA;AAC9D,QAAA,2BAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CADV,CAAA;AAAA,IAGA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CAHZ,CAAA;AAAA,IAIA,OAAO,CAAC,UAAR,CAAmB,CAAnB,CAJA,CAAA;AAAA,IAMA,SAAA,GAAY,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,SAA9B,CANZ,CAAA;AAAA,IAOA,KAAA,CAAM,SAAS,CAAC,IAAV,CAAe,qBAAf,CAAqC,CAAC,IAAtC,CAA2C,MAA3C,CAAN,EACE,oCADF,EAEE,qCAFF,CAPA,CAD8D;EAAA,CAAhE,CA9IA,CAAA;AAAA,EA6JA,IAAA,CAAK,mEAAL,EAA0E,SAAA,GAAA;AACxE,QAAA,gBAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,yBAAX,CAAA;AAAA,IACA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,kBAAb,CAAgC,CAAC,aAAjC,CAAA,CADV,CAAA;AAAA,IAGA,OAAO,CAAC,OAAR,CAAA,CAHA,CAAA;AAAA,IAKA,KAAA,CAAM,OAAO,CAAC,IAAR,CAAa,MAAb,CAAoB,CAAC,QAArB,CAA8B,UAA9B,CAAyC,CAAC,MAAhD,EAAwD,CAAxD,EACE,yBADF,CALA,CAAA;AAAA,IAQA,KAAA,CAAM,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAsC,UAAtC,CAAiD,CAAC,MAAxD,EAAgE,CAAhE,EACE,8BADF,CARA,CADwE;EAAA,CAA1E,CA7JA,CAAA;AAAA,EA4KA,IAAA,CAAK,6CAAL,EAAoD,SAAA,GAAA;AAClD,QAAA,gBAAA;AAAA,IAAA,OAAA,GAAU,IAAC,CAAA,+BAAX,CAAA;AAAA,IACA,OAAO,CAAC,IAAR,CAAa,qDAAb,CACE,CAAC,IADH,CACQ,SADR,EACmB,IADnB,CADA,CAAA;AAAA,IAGA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CAAuC,CAAC,aAAxC,CAAA,CAHV,CAAA;AAAA,IAKA,KAAA,CAAM,OAAO,CAAC,IAAR,CAAa,mDAAb,CACJ,CAAC,OADG,CACK,UADL,CACgB,CAAC,EADjB,CACoB,UADpB,CAAN,EACuC,KADvC,EAEE,mDAFF,CALA,CAAA;AAAA,IASA,KAAA,CAAM,OAAO,CAAC,IAAR,CAAa,gDAAb,CAA8D,CAAC,GAA/D,CAAA,CAAN,EACE,IADF,EACQ,uCADR,CATA,CADkD;EAAA,CAApD,CA5KA,CAAA;AAAA,EA4LA,IAAA,CAAK,8DAAL,EAAqE,SAAA,GAAA;AACnE,QAAA,oCAAA;AAAA,IAAM;AAEJ,+BAAA,CAAA;;;;OAAA;;AAAA,uBAAA,eAAA,GAAiB,SAAA,GAAA;eACf,CAAA,CAAE,uFAAF,EADe;MAAA,CAAjB,CAAA;;oBAAA;;OAFmB,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,KAAxC,CAAA;AAAA,IAOM;AAEJ,8BAAA,CAAA;;AAAa,MAAA,eAAE,IAAF,GAAA;AACX,QADY,IAAC,CAAA,OAAA,IACb,CAAA;AAAA,QAAA,wCAAA,SAAA,CAAA,CAAA;AAAA,QACA,IAAC,CAAA,IAAI,CAAC,IAAN,CAAW,cAAX,EAA2B,IAA3B,CADA,CADW;MAAA,CAAb;;mBAAA;;OAFkB,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,IAPvC,CAAA;AAAA,IAaA,OAAA,GAAU,IAAC,CAAA,+BAbX,CAAA;AAAA,IAcA,OAAA,GAAU,OAAO,CAAC,IAAR,CAAa,cAAb,CAA4B,CAAC,QAA7B,CAAA,CACR,CAAC,aADO,CACO;AAAA,MAAA,SAAA,EAAW,MAAX;AAAA,MAAmB,QAAA,EAAU,KAA7B;KADP,CAdV,CAAA;AAAA,IAgBA,OAAO,CAAC,OAAR,CAAA,CAhBA,CAAA;AAAA,IAkBA,KAAA,CAAM,OAAO,CAAC,IAAR,CAAa,kDAAb,CACJ,CAAC,IADG,CAAA,CACG,CAAC,MADV,EACkB,CADlB,EACqB,4CADrB,CAlBA,CAAA;AAAA,IAoBA,GAAA,GAAM,OAAO,CAAC,KAAM,CAAA,CAAA,CAAE,CAAC,GApBvB,CAAA;AAAA,IAqBA,KAAA,CAAM,GAAG,CAAC,IAAI,CAAC,IAAT,CAAc,cAAd,CAAN,EAAqC,GAArC,EAA0C,+BAA1C,CArBA,CADmE;EAAA,CAArE,CA5LA,CAxBD;AAAA,CAAD,CAAA,CA8OE,MA9OF,CAAA,CAAA" 10 | } -------------------------------------------------------------------------------- /test/django-formset-bootstrap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Django Formset Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Delete: 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | __prefix__ 57 | 0 58 | Add another 59 | 60 | 61 | 62 | 63 | 64 | Delete: 65 | 66 | 67 | 68 | 0 69 | Delete: 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | __prefix__ 82 | 83 | 84 | 85 | 86 | 87 | Delete: 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | __prefix__ 100 | 0 101 | 1 102 | 2 103 | Add another 104 | 105 | 106 | 107 | 108 | 109 | Delete: 110 | 111 | 112 | 113 | 0 114 | Delete: 115 | 116 | 117 | 118 | 1 119 | Delete: 120 | 121 | 122 | 123 | 2 124 | Delete: 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /test/django-formset_test-bootstrap.js: -------------------------------------------------------------------------------- 1 | var __hasProp = {}.hasOwnProperty, 2 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 3 | 4 | (function($) { 5 | var moduleSetup; 6 | moduleSetup = function() { 7 | var allFixtures; 8 | allFixtures = $("#qunit-fixture"); 9 | this.fixtureTabsNoInitialForms = allFixtures.find('#tabs-no-initial-forms'); 10 | this.fixtureTabsWithFormThreeInitial = allFixtures.find('#tabs-with-form-three-initial'); 11 | this.fixtureTabsWithoutNav = allFixtures.find('#tabs-without-tab-nav'); 12 | this.fixtureTabsNoTabTemplate = allFixtures.find('#tabs-no-tab-template'); 13 | }; 14 | module("jQuery#djangoFormset - functional tests", { 15 | setup: moduleSetup 16 | }); 17 | test("Throws on missing tab activator if form template is .tab-pane", function() { 18 | throws((function() { 19 | return this.fixtureTabsWithoutNav.find('.tab-content > div').djangoFormset(); 20 | }), /Template is .tab-pane but couldn't find corresponding tab activator./, "Doesn't complain about missing tab activator"); 21 | }); 22 | test("Throws on missing tab template", function() { 23 | throws((function() { 24 | return this.fixtureTabsNoTabTemplate.find('.tab-content > div').djangoFormset(); 25 | }), /Tab nav template not found \(looking for .empty-form\)./, "Doesn't complain about missing tab template"); 26 | }); 27 | test("Adds new tab pane and new tab nav after the last form", function() { 28 | var fixture, formset, lastTabNav, lastTabPane; 29 | fixture = this.fixtureTabsWithFormThreeInitial; 30 | formset = fixture.find('.tab-content').children().djangoFormset(); 31 | lastTabPane = fixture.find('.tab-pane').last(); 32 | lastTabNav = fixture.find("[href='#" + (lastTabPane.attr('id')) + "']").closest('.nav > *'); 33 | formset.addForm(); 34 | equal(lastTabPane.next().attr('id'), 'id_tabs-with-form-three-initial-3', "tab pane with id 3 was added"); 35 | equal(lastTabNav.next().find('a').attr('href'), '#id_tabs-with-form-three-initial-3', "the last tab activates the newly added tab pane"); 36 | equal(fixture.find('.nav').children('.active').find("[data-toggle='tab']").attr('href'), '#id_tabs-with-form-three-initial-3', "the new tab pane is active"); 37 | }); 38 | test("deleting first initially existing form activates following tab header", function() { 39 | var activeTab, fixture, formset; 40 | fixture = this.fixtureTabsWithFormThreeInitial; 41 | formset = fixture.find('.tab-content').children().djangoFormset(); 42 | activeTab = fixture.find('.nav').children('.active'); 43 | formset.deleteForm(0); 44 | equal(activeTab.filter('.active').length, 0, "first tab header is not active anymore"); 45 | activeTab = fixture.find('.nav').children('.active'); 46 | equal(activeTab.find("[data-toggle='tab']").attr('href'), "#id_tabs-with-form-three-initial-1", "following tab header is now active"); 47 | }); 48 | test("deleting middle initially existing form activates following tab header", function() { 49 | var activeTab, fixture, formset; 50 | fixture = this.fixtureTabsWithFormThreeInitial; 51 | formset = fixture.find('.tab-content').children().djangoFormset(); 52 | fixture.find(".nav :visible [data-toggle='tab']").eq(1).trigger('click'); 53 | activeTab = fixture.find('.nav').children('.active'); 54 | formset.deleteForm(1); 55 | equal(activeTab.is('.active'), false, "previously active tab header is now not active"); 56 | activeTab = fixture.find('.nav').children('.active'); 57 | equal(activeTab.find("[data-toggle='tab']").attr('href'), "#id_tabs-with-form-three-initial-2", "now active tab header is the preceding one"); 58 | }); 59 | test("deleting last initially existing form activates preceding tab header", function() { 60 | var activeTab, fixture, formset; 61 | fixture = this.fixtureTabsWithFormThreeInitial; 62 | formset = fixture.find('.tab-content').children().djangoFormset(); 63 | fixture.find(".nav :visible [data-toggle='tab']").last().trigger('click'); 64 | activeTab = fixture.find('.nav').children('.active'); 65 | formset.deleteForm(2); 66 | equal(activeTab.filter('.active').length, 0, "previously active tab header is now not active"); 67 | activeTab = fixture.find('.nav').children('.active'); 68 | equal(activeTab.find("[data-toggle='tab']").attr('href'), "#id_tabs-with-form-three-initial-1", "now active tab header is the preceding one"); 69 | }); 70 | test("deleting the only tab deletes the tab header as well", function() { 71 | var fixture, formset; 72 | fixture = this.fixtureTabsWithFormThreeInitial; 73 | formset = fixture.find('.tab-content').children().djangoFormset(); 74 | formset.deleteForm(2); 75 | formset.deleteForm(1); 76 | formset.deleteForm(0); 77 | equal(fixture.find('.nav').children("[data-toggle='tab']:visible").length, 0, "no tab headers are visible"); 78 | }); 79 | test('deleting new form hides current tab and activates following tab', function() { 80 | var activeTab, fixture, formset; 81 | fixture = this.fixtureTabsWithFormThreeInitial; 82 | formset = fixture.find('.tab-content').children().djangoFormset(); 83 | fixture.find(".nav :visible [data-toggle='tab']").last().trigger('click'); 84 | formset.addForm(); 85 | formset.addForm(); 86 | fixture.find(".nav :visible [data-toggle='tab']").eq(3).trigger('click'); 87 | formset.deleteForm(3); 88 | activeTab = fixture.find('.nav').children('.active'); 89 | equal(activeTab.find("[data-toggle='tab']").attr('href'), "#id_tabs-with-form-three-initial-3", "following tab header is now active"); 90 | }); 91 | test("deleting a non-active tab doesn't change the active tab", function() { 92 | var activeTab, fixture, formset; 93 | fixture = this.fixtureTabsWithFormThreeInitial; 94 | formset = fixture.find('.tab-content').children().djangoFormset(); 95 | activeTab = fixture.find('.nav').children('.active'); 96 | formset.deleteForm(1); 97 | activeTab = fixture.find('.nav').children('.active'); 98 | equal(activeTab.find("[data-toggle='tab']").attr('href'), "#id_tabs-with-form-three-initial-0", "active tab header is still the same"); 99 | }); 100 | test("Adding a new form to a tabbed formset without initial forms works", function() { 101 | var fixture, formset; 102 | fixture = this.fixtureTabsNoInitialForms; 103 | formset = fixture.find('.tab-content > *').djangoFormset(); 104 | formset.addForm(); 105 | equal(fixture.find('.nav').children(':visible').length, 1, "there's one visible tab"); 106 | equal(fixture.find('.tab-content').children(':visible').length, 1, "there's one visible tab pane"); 107 | }); 108 | test("Initially deleted form is hidden right away", function() { 109 | var fixture, formset; 110 | fixture = this.fixtureTabsWithFormThreeInitial; 111 | fixture.find("input[name='tabs-with-form-three-initial-1-DELETE']").prop('checked', true); 112 | formset = fixture.find('.tab-content').children().djangoFormset(); 113 | equal(fixture.find(".nav a[href='#id_tabs-with-form-three-initial-1']").closest('.nav > *').is(':visible'), false, "the tab header of the deleted form is not visible"); 114 | equal(fixture.find("[name='tabs-with-form-three-initial-1-DELETE']").val(), "on", "the DELETE element has the value 'on'"); 115 | }); 116 | test("Form and Tab class can be replaced with a customized version", function() { 117 | var MyForm, MyTab, fixture, formset, tab; 118 | MyForm = (function(_super) { 119 | __extends(MyForm, _super); 120 | 121 | function MyForm() { 122 | return MyForm.__super__.constructor.apply(this, arguments); 123 | } 124 | 125 | MyForm.prototype.getDeleteButton = function() { 126 | return $(' Delete '); 127 | }; 128 | 129 | return MyForm; 130 | 131 | })($.fn.djangoFormset.Form); 132 | MyTab = (function(_super) { 133 | __extends(MyTab, _super); 134 | 135 | function MyTab(elem) { 136 | this.elem = elem; 137 | MyTab.__super__.constructor.apply(this, arguments); 138 | this.elem.data('mycustomdata', this); 139 | } 140 | 141 | return MyTab; 142 | 143 | })($.fn.djangoFormset.Tab); 144 | fixture = this.fixtureTabsWithFormThreeInitial; 145 | formset = fixture.find('.tab-content').children().djangoFormset({ 146 | formClass: MyForm, 147 | tabClass: MyTab 148 | }); 149 | formset.addForm(); 150 | equal(fixture.find("button.my-delete-button-class:contains('Delete')").last().length, 1, "The custom getDeleteButton method was used"); 151 | tab = formset.forms[0].tab; 152 | equal(tab.elem.data('mycustomdata'), tab, "The custom Tab class was used"); 153 | }); 154 | })(jQuery); 155 | 156 | //# sourceMappingURL=django-formset_test-bootstrap.js.map 157 | -------------------------------------------------------------------------------- /test/django-formset_test-bootstrap.coffee: -------------------------------------------------------------------------------- 1 | (($) -> 2 | 3 | # 4 | # ======== A Handy Little QUnit Reference ======== 5 | # http://api.qunitjs.com/ 6 | # 7 | # Test methods: 8 | # module(name, {[setup][ ,teardown]}) 9 | # test(name, callback) 10 | # expect(numberOfAssertions) 11 | # stop(increment) 12 | # start(decrement) 13 | # Test assertions: 14 | # ok(value, [message]) 15 | # equal(actual, expected, [message]) 16 | # notEqual(actual, expected, [message]) 17 | # deepEqual(actual, expected, [message]) 18 | # notDeepEqual(actual, expected, [message]) 19 | # strictEqual(actual, expected, [message]) 20 | # notStrictEqual(actual, expected, [message]) 21 | # throws(block, [expected], [message]) 22 | # 23 | 24 | # This will run before each test in each module. 25 | moduleSetup = -> 26 | allFixtures = $("#qunit-fixture") 27 | @fixtureTabsNoInitialForms = allFixtures.find('#tabs-no-initial-forms') 28 | @fixtureTabsWithFormThreeInitial = allFixtures.find( 29 | '#tabs-with-form-three-initial') 30 | @fixtureTabsWithoutNav = allFixtures.find('#tabs-without-tab-nav') 31 | @fixtureTabsNoTabTemplate = allFixtures.find('#tabs-no-tab-template') 32 | return 33 | 34 | module("jQuery#djangoFormset - functional tests", setup: moduleSetup) 35 | 36 | test("Throws on missing tab activator if form template is .tab-pane", -> 37 | throws((-> 38 | @fixtureTabsWithoutNav.find('.tab-content > div').djangoFormset()), 39 | /Template is .tab-pane but couldn't find corresponding tab activator./ 40 | "Doesn't complain about missing tab activator") 41 | return 42 | ) 43 | 44 | test("Throws on missing tab template", -> 45 | throws((-> 46 | @fixtureTabsNoTabTemplate.find('.tab-content > div').djangoFormset()), 47 | /Tab nav template not found \(looking for .empty-form\)./ 48 | "Doesn't complain about missing tab template") 49 | return 50 | ) 51 | 52 | test("Adds new tab pane and new tab nav after the last form", -> 53 | fixture = @fixtureTabsWithFormThreeInitial 54 | formset = fixture.find('.tab-content').children().djangoFormset() 55 | 56 | lastTabPane = fixture.find('.tab-pane').last() 57 | lastTabNav = fixture.find("[href='##{lastTabPane.attr('id')}']") 58 | .closest('.nav > *') 59 | 60 | formset.addForm() 61 | 62 | equal(lastTabPane.next().attr('id'), 'id_tabs-with-form-three-initial-3', 63 | "tab pane with id 3 was added") 64 | 65 | equal(lastTabNav.next().find('a').attr('href'), 66 | '#id_tabs-with-form-three-initial-3', 67 | "the last tab activates the newly added tab pane") 68 | 69 | equal(fixture.find('.nav').children('.active').find("[data-toggle='tab']") 70 | .attr('href'), '#id_tabs-with-form-three-initial-3', 71 | "the new tab pane is active") 72 | 73 | return 74 | ) 75 | 76 | test("deleting first initially existing form activates following tab header", 77 | -> 78 | fixture = @fixtureTabsWithFormThreeInitial 79 | formset = fixture.find('.tab-content').children().djangoFormset() 80 | 81 | activeTab = fixture.find('.nav').children('.active') 82 | formset.deleteForm(0) 83 | 84 | equal(activeTab.filter('.active').length, 0, 85 | "first tab header is not active anymore") 86 | 87 | activeTab = fixture.find('.nav').children('.active') 88 | equal(activeTab.find("[data-toggle='tab']").attr('href'), 89 | "#id_tabs-with-form-three-initial-1", 90 | "following tab header is now active") 91 | 92 | return 93 | ) 94 | 95 | test("deleting middle initially existing form activates following tab header", 96 | -> 97 | fixture = @fixtureTabsWithFormThreeInitial 98 | formset = fixture.find('.tab-content').children().djangoFormset() 99 | fixture.find(".nav :visible [data-toggle='tab']").eq(1).trigger('click') 100 | 101 | activeTab = fixture.find('.nav').children('.active') 102 | formset.deleteForm(1) 103 | 104 | equal(activeTab.is('.active'), false, 105 | "previously active tab header is now not active") 106 | 107 | activeTab = fixture.find('.nav').children('.active') 108 | equal(activeTab.find("[data-toggle='tab']").attr('href'), 109 | "#id_tabs-with-form-three-initial-2", 110 | "now active tab header is the preceding one") 111 | 112 | return 113 | ) 114 | 115 | test("deleting last initially existing form activates preceding tab header", 116 | -> 117 | fixture = @fixtureTabsWithFormThreeInitial 118 | formset = fixture.find('.tab-content').children().djangoFormset() 119 | fixture.find(".nav :visible [data-toggle='tab']").last().trigger('click') 120 | 121 | activeTab = fixture.find('.nav').children('.active') 122 | formset.deleteForm(2) 123 | 124 | equal(activeTab.filter('.active').length, 0, 125 | "previously active tab header is now not active") 126 | 127 | activeTab = fixture.find('.nav').children('.active') 128 | equal(activeTab.find("[data-toggle='tab']").attr('href'), 129 | "#id_tabs-with-form-three-initial-1", 130 | "now active tab header is the preceding one") 131 | 132 | return 133 | ) 134 | 135 | test("deleting the only tab deletes the tab header as well", -> 136 | fixture = @fixtureTabsWithFormThreeInitial 137 | formset = fixture.find('.tab-content').children().djangoFormset() 138 | 139 | formset.deleteForm(2) 140 | formset.deleteForm(1) 141 | formset.deleteForm(0) 142 | 143 | equal(fixture.find('.nav').children("[data-toggle='tab']:visible").length, 144 | 0, "no tab headers are visible") 145 | 146 | return 147 | ) 148 | 149 | test('deleting new form hides current tab and activates following tab', -> 150 | fixture = @fixtureTabsWithFormThreeInitial 151 | formset = fixture.find('.tab-content').children().djangoFormset() 152 | fixture.find(".nav :visible [data-toggle='tab']").last().trigger('click') 153 | 154 | formset.addForm() 155 | formset.addForm() 156 | fixture.find(".nav :visible [data-toggle='tab']").eq(3).trigger('click') 157 | formset.deleteForm(3) 158 | 159 | activeTab = fixture.find('.nav').children('.active') 160 | equal(activeTab.find("[data-toggle='tab']").attr('href'), 161 | "#id_tabs-with-form-three-initial-3", 162 | "following tab header is now active") 163 | 164 | return 165 | ) 166 | 167 | test("deleting a non-active tab doesn't change the active tab", -> 168 | fixture = @fixtureTabsWithFormThreeInitial 169 | formset = fixture.find('.tab-content').children().djangoFormset() 170 | 171 | activeTab = fixture.find('.nav').children('.active') 172 | formset.deleteForm(1) 173 | 174 | activeTab = fixture.find('.nav').children('.active') 175 | equal(activeTab.find("[data-toggle='tab']").attr('href'), 176 | "#id_tabs-with-form-three-initial-0", 177 | "active tab header is still the same") 178 | 179 | return 180 | ) 181 | 182 | test("Adding a new form to a tabbed formset without initial forms works", -> 183 | fixture = @fixtureTabsNoInitialForms 184 | formset = fixture.find('.tab-content > *').djangoFormset() 185 | 186 | formset.addForm() 187 | 188 | equal(fixture.find('.nav').children(':visible').length, 1, 189 | "there's one visible tab") 190 | 191 | equal(fixture.find('.tab-content').children(':visible').length, 1, 192 | "there's one visible tab pane") 193 | 194 | return 195 | ) 196 | 197 | test("Initially deleted form is hidden right away", -> 198 | fixture = @fixtureTabsWithFormThreeInitial 199 | fixture.find("input[name='tabs-with-form-three-initial-1-DELETE']") 200 | .prop('checked', true) 201 | formset = fixture.find('.tab-content').children().djangoFormset() 202 | 203 | equal(fixture.find(".nav a[href='#id_tabs-with-form-three-initial-1']") 204 | .closest('.nav > *').is(':visible'), false, 205 | "the tab header of the deleted form is not visible") 206 | 207 | equal(fixture.find("[name='tabs-with-form-three-initial-1-DELETE']").val(), 208 | "on", "the DELETE element has the value 'on'") 209 | 210 | return 211 | ) 212 | 213 | test("Form and Tab class can be replaced with a customized version", -> 214 | class MyForm extends $.fn.djangoFormset.Form 215 | 216 | getDeleteButton: -> 217 | $(' 218 | Delete 219 | ') 220 | 221 | class MyTab extends $.fn.djangoFormset.Tab 222 | 223 | constructor: (@elem) -> 224 | super 225 | @elem.data('mycustomdata', this) 226 | 227 | fixture = @fixtureTabsWithFormThreeInitial 228 | formset = fixture.find('.tab-content').children() 229 | .djangoFormset(formClass: MyForm, tabClass: MyTab) 230 | formset.addForm() 231 | 232 | equal(fixture.find("button.my-delete-button-class:contains('Delete')") 233 | .last().length, 1, "The custom getDeleteButton method was used") 234 | tab = formset.forms[0].tab 235 | equal(tab.elem.data('mycustomdata'), tab, "The custom Tab class was used") 236 | return 237 | ) 238 | return 239 | )(jQuery) 240 | -------------------------------------------------------------------------------- /src/django-formset.coffee: -------------------------------------------------------------------------------- 1 | # 2 | # * django-formset 3 | # * https://github.com/mbertheau/jquery.django-formset 4 | # * 5 | # * Copyright (c) 2014 Markus Bertheau 6 | # * Licensed under the MIT license. 7 | # 8 | # * Heavily inspired by Stanislaus Madueke's django-dynamic-formset 9 | # * https://github.com/elo80ka/django-dynamic-formset 10 | # 11 | 12 | (($) -> 13 | 14 | $.fn.djangoFormset = (options) -> 15 | new $.fn.djangoFormset.Formset(this, options) 16 | 17 | class FormsetError extends Error 18 | 19 | class $.fn.djangoFormset.Formset 20 | constructor: (base, options) -> 21 | @opts = $.extend({}, $.fn.djangoFormset.defaultOptions, options) 22 | if base.length == 0 23 | throw new FormsetError("Empty selector.") 24 | 25 | @template = base.filter(".#{@opts.formTemplateClass}") 26 | if @template.length == 0 27 | throw new FormsetError( 28 | "Can't find template (looking for .#{@opts.formTemplateClass})") 29 | 30 | inputName = @template.find("input,select,textarea").first().attr('name') 31 | if not inputName 32 | throw new FormsetError("Can't figure out form prefix because there's no 33 | form element in the form template. Please add 34 | one.") 35 | placeholderPos = inputName.indexOf('-__prefix__') 36 | if placeholderPos == -1 37 | throw new FormsetError("Can't figure out form prefix from template 38 | because it doesn't contain '-__prefix__'.") 39 | @prefix = inputName.substring(0, placeholderPos) 40 | 41 | @totalForms = $("#id_#{@prefix}-TOTAL_FORMS") 42 | if @totalForms.length == 0 43 | throw new FormsetError("Management form field 'TOTAL_FORMS' not found 44 | for prefix #{@prefix}.") 45 | 46 | @_initTabs() 47 | 48 | forms = base.not(".#{@opts.formTemplateClass}") 49 | @initialForms = forms.length 50 | 51 | $(this).on(@opts.on) 52 | 53 | @forms = forms.map((index, element) => 54 | if @hasTabs 55 | tabActivator = $.djangoFormset.getTabActivator(element.id) 56 | tab = new @opts.tabClass(tabActivator.closest('.nav > *')) 57 | newForm = new @opts.formClass($(element), this, index, tab) 58 | $(this).trigger("formInitialized", [newForm]) 59 | newForm 60 | ) 61 | 62 | if @forms.length != parseInt(@totalForms.val()) 63 | console.error("TOTAL_FORMS is #{@totalForms.val()}, but #{@forms.length} 64 | non-template elements found in passed selection.") 65 | 66 | deletedForms = @forms.filter(-> @deleteInput.val()) 67 | deletedForms.each(-> @delete()) 68 | 69 | @insertAnchor = base.not(".#{@opts.formTemplateClass}").last() 70 | if @insertAnchor.length == 0 71 | @insertAnchor = @template 72 | 73 | return 74 | 75 | _initTabs: -> 76 | @hasTabs = @template.is('.tab-pane') 77 | if not @hasTabs 78 | return 79 | 80 | tabActivator = $.djangoFormset.getTabActivator(@template.attr('id')) 81 | if tabActivator.length == 0 82 | throw new FormsetError("Template is .tab-pane but couldn't find 83 | corresponding tab activator.") 84 | 85 | tabNav = tabActivator.closest('.nav') 86 | if tabNav.length == 0 87 | throw new FormsetError("Template is .tab-pane but couldn't find 88 | corresponding .nav.") 89 | 90 | @tabTemplate = tabNav.children(".#{@opts.formTemplateClass}") 91 | if @tabTemplate.length == 0 92 | throw new FormsetError("Tab nav template not found (looking for 93 | .#{@opts.formTemplateClass}).") 94 | 95 | return 96 | 97 | addForm: -> 98 | if @hasTabs 99 | newTabElem = @tabTemplate.clone() 100 | .removeClass(@opts.formTemplateClass) 101 | newTab = new @opts.tabClass(newTabElem) 102 | if @forms.length > 0 103 | tabInsertAnchor = @forms[@forms.length - 1].tab.elem 104 | else 105 | tabInsertAnchor = @tabTemplate 106 | newTabElem.insertAfter(tabInsertAnchor) 107 | 108 | newFormElem = @template.clone() 109 | .removeClass(@opts.formTemplateClass) 110 | 111 | newForm = new @opts.formClass(newFormElem, this, 112 | parseInt(@totalForms.val()), newTab) 113 | 114 | newFormElem.insertAfter(@insertAnchor) 115 | @insertAnchor = newFormElem 116 | @forms.push(newForm) 117 | 118 | @totalForms.val(parseInt(@totalForms.val()) + 1) 119 | if @hasTabs 120 | newTab.activate() 121 | $(this).trigger("formInitialized", [newForm]) 122 | $(this).trigger("formAdded", [newForm]) 123 | 124 | newForm 125 | 126 | deleteForm: (index) -> 127 | form = @forms[index] 128 | form.delete() 129 | return 130 | 131 | handleFormRemoved: (index) -> 132 | @totalForms.val(parseInt(@totalForms.val()) - 1) 133 | @forms.splice(index, 1) 134 | for form, i in @forms 135 | form._updateFormIndex(i) 136 | 137 | if @forms.length == 0 138 | @insertAnchor = @template 139 | else 140 | @insertAnchor = @forms[@forms.length - 1].elem 141 | 142 | return 143 | 144 | class $.fn.djangoFormset.Form 145 | constructor: (@elem, @formset, @index, @tab) -> 146 | @elem.data('djangoFormset.Form', this) 147 | if @index isnt undefined 148 | @_initFormIndex(@index) 149 | @deleteInput = @field('DELETE') 150 | isInitial = @index < @formset.initialForms 151 | if @deleteInput.length > 0 or not isInitial 152 | @_replaceDeleteCheckboxWithButton() 153 | 154 | getDeleteButton: -> 155 | $(" 156 | #{@formset.opts.deleteButtonText} 157 | ") 158 | 159 | insertDeleteButton: -> 160 | if @deleteInput.length > 0 161 | @deleteInput.after(@deleteButton) 162 | else 163 | (if @elem.is('TR') 164 | @elem.children().last() 165 | else if @elem.is('UL') or @elem.is('OL') 166 | @elem.append('li').children().last() 167 | else 168 | @elem).append(@deleteButton) 169 | return 170 | 171 | delete: -> 172 | isInitial = @index < @formset.initialForms 173 | 174 | if @deleteInput.length == 0 and isInitial 175 | console.warn("Tried do delete non-deletable form #{@formset.prefix} 176 | ##{@index}.") 177 | return 178 | 179 | 180 | if @tab and @tab.elem.is('.active') 181 | tabElems = @formset.forms.map((index, form) -> form.tab.elem[0]) 182 | nextTab = tabElems[@index + 1..].filter(':visible').first() 183 | if nextTab.length == 0 184 | nextTab = tabElems[...@index].filter(':visible').last() 185 | if nextTab.length > 0 186 | nextTab.data('djangoFormset.tab').activate() 187 | 188 | if isInitial 189 | if @deleteInput.length > 0 190 | @deleteInput.val('on') 191 | if @tab 192 | @tab.elem.hide() 193 | @hide() 194 | else 195 | if @tab 196 | @tab.elem.remove() 197 | @elem.remove() 198 | @formset.handleFormRemoved(@index) 199 | return 200 | 201 | hide: -> 202 | @elem.hide() 203 | 204 | field: (name) -> 205 | return @elem.find("[name='#{@formset.prefix}-#{@index}-#{name}']") 206 | 207 | prev: -> 208 | for form in @formset.forms[..@index - 1] by -1 209 | if form.elem.is(':visible') 210 | return form 211 | 212 | _replaceDeleteCheckboxWithButton: -> 213 | if @deleteInput.length > 0 214 | newDeleteInput = $("") 218 | 219 | # Remove any label for the delete checkbox 220 | label = @elem.find("label[for='#{@deleteInput.attr('id')}']") 221 | if label.has(@deleteInput).length > 0 222 | label.replaceWith(newDeleteInput) 223 | else 224 | label.remove() 225 | @deleteInput.replaceWith(newDeleteInput) 226 | @deleteInput = newDeleteInput 227 | 228 | @deleteButton = @getDeleteButton() 229 | @deleteButton.on('click', (event) => @delete()) 230 | @insertDeleteButton() 231 | return 232 | 233 | _replaceFormIndex: (oldIndexPattern, index) -> 234 | @index = index 235 | 236 | prefixRegex = new RegExp("#{@formset.prefix}-#{oldIndexPattern}") 237 | newPrefix = "#{@formset.prefix}-#{index}" 238 | 239 | _replaceFormIndexElement = (elem) -> 240 | attributeNamesByTagName = 241 | input: ['id', 'name'] 242 | select: ['id', 'name'] 243 | textarea: ['id', 'name'] 244 | label: ['for'] 245 | div: ['id'] 246 | '*': ['href', 'data-target'] 247 | 248 | tagName = elem.get(0).tagName 249 | attributeNames = [] 250 | if tagName.toLowerCase() of attributeNamesByTagName 251 | attributeNames = attributeNamesByTagName[tagName.toLowerCase()] 252 | 253 | attributeNames.push(attributeNamesByTagName['*']...) 254 | 255 | for attributeName in attributeNames 256 | if elem.attr(attributeName) 257 | elem.attr(attributeName, 258 | elem.attr(attributeName).replace(prefixRegex, newPrefix)) 259 | 260 | _replaceFormIndexElement(@elem) 261 | 262 | @elem.find('input, select, textarea, label').each(-> 263 | _replaceFormIndexElement($(this)) 264 | return 265 | ) 266 | 267 | if @tab 268 | _replaceFormIndexElement(@tab.elem) 269 | @tab.elem.find('a, button').each(-> 270 | _replaceFormIndexElement($(this)) 271 | return 272 | ) 273 | 274 | return 275 | 276 | _initFormIndex: (index) -> 277 | @_replaceFormIndex("__prefix__", index) 278 | return 279 | 280 | _updateFormIndex: (index) -> 281 | @_replaceFormIndex('\\d+', index) 282 | return 283 | 284 | class $.fn.djangoFormset.Tab 285 | constructor: (@elem) -> 286 | @elem.data('djangoFormset.tab', this) 287 | 288 | activate: -> 289 | @elem.find("[data-toggle='tab']").trigger('click') 290 | 291 | remove: -> 292 | @elem.remove() 293 | 294 | $.fn.djangoFormset.defaultOptions = 295 | formTemplateClass: 'empty-form' 296 | formClass: $.fn.djangoFormset.Form 297 | tabClass: $.fn.djangoFormset.Tab 298 | deleteButtonText: 'Delete' 299 | 300 | $.djangoFormset = 301 | getTabActivator: (id) -> 302 | $("[href='##{id}'], [data-target='##{id}']") 303 | 304 | return 305 | 306 | )(jQuery) 307 | -------------------------------------------------------------------------------- /test/django-formset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Django Formset Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | template 49 | lame test markup 50 | normal test markup 51 | awesome test markup 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Delete: 61 | 62 | 63 | 64 | 65 | 66 | 67 | Delete: 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | Delete: 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Delete: 89 | 90 | 91 | 92 | 93 | 94 | 95 | Table caption 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | foo 111 | 112 | 113 | Check: 114 | 115 | Delete: 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | foo 128 | 129 | 130 | Check: 131 | 132 | Delete: 133 | 134 | 135 | 136 | 137 | 138 | foo 139 | 140 | 141 | Check: 142 | 143 | Delete: 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | Delete: 166 | 167 | 168 | 169 | 170 | 171 | 172 | Delete: 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | Delete: 182 | 183 | 184 | 185 | 186 | Delete: 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /src/django-formset.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "django-formset.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "django-formset.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "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" 10 | } -------------------------------------------------------------------------------- /test/django-formset_test.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "django-formset_test.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "django-formset_test.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "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" 10 | } -------------------------------------------------------------------------------- /src/django-formset.js: -------------------------------------------------------------------------------- 1 | var __hasProp = {}.hasOwnProperty, 2 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 3 | 4 | (function($) { 5 | var FormsetError; 6 | $.fn.djangoFormset = function(options) { 7 | return new $.fn.djangoFormset.Formset(this, options); 8 | }; 9 | FormsetError = (function(_super) { 10 | __extends(FormsetError, _super); 11 | 12 | function FormsetError() { 13 | return FormsetError.__super__.constructor.apply(this, arguments); 14 | } 15 | 16 | return FormsetError; 17 | 18 | })(Error); 19 | $.fn.djangoFormset.Formset = (function() { 20 | function Formset(base, options) { 21 | var deletedForms, forms, inputName, placeholderPos; 22 | this.opts = $.extend({}, $.fn.djangoFormset.defaultOptions, options); 23 | if (base.length === 0) { 24 | throw new FormsetError("Empty selector."); 25 | } 26 | this.template = base.filter("." + this.opts.formTemplateClass); 27 | if (this.template.length === 0) { 28 | throw new FormsetError("Can't find template (looking for ." + this.opts.formTemplateClass + ")"); 29 | } 30 | inputName = this.template.find("input,select,textarea").first().attr('name'); 31 | if (!inputName) { 32 | throw new FormsetError("Can't figure out form prefix because there's no form element in the form template. Please add one."); 33 | } 34 | placeholderPos = inputName.indexOf('-__prefix__'); 35 | if (placeholderPos === -1) { 36 | throw new FormsetError("Can't figure out form prefix from template because it doesn't contain '-__prefix__'."); 37 | } 38 | this.prefix = inputName.substring(0, placeholderPos); 39 | this.totalForms = $("#id_" + this.prefix + "-TOTAL_FORMS"); 40 | if (this.totalForms.length === 0) { 41 | throw new FormsetError("Management form field 'TOTAL_FORMS' not found for prefix " + this.prefix + "."); 42 | } 43 | this._initTabs(); 44 | forms = base.not("." + this.opts.formTemplateClass); 45 | this.initialForms = forms.length; 46 | $(this).on(this.opts.on); 47 | this.forms = forms.map((function(_this) { 48 | return function(index, element) { 49 | var newForm, tab, tabActivator; 50 | if (_this.hasTabs) { 51 | tabActivator = $.djangoFormset.getTabActivator(element.id); 52 | tab = new _this.opts.tabClass(tabActivator.closest('.nav > *')); 53 | } 54 | newForm = new _this.opts.formClass($(element), _this, index, tab); 55 | $(_this).trigger("formInitialized", [newForm]); 56 | return newForm; 57 | }; 58 | })(this)); 59 | if (this.forms.length !== parseInt(this.totalForms.val())) { 60 | console.error("TOTAL_FORMS is " + (this.totalForms.val()) + ", but " + this.forms.length + " non-template elements found in passed selection."); 61 | } 62 | deletedForms = this.forms.filter(function() { 63 | return this.deleteInput.val(); 64 | }); 65 | deletedForms.each(function() { 66 | return this["delete"](); 67 | }); 68 | this.insertAnchor = base.not("." + this.opts.formTemplateClass).last(); 69 | if (this.insertAnchor.length === 0) { 70 | this.insertAnchor = this.template; 71 | } 72 | return; 73 | } 74 | 75 | Formset.prototype._initTabs = function() { 76 | var tabActivator, tabNav; 77 | this.hasTabs = this.template.is('.tab-pane'); 78 | if (!this.hasTabs) { 79 | return; 80 | } 81 | tabActivator = $.djangoFormset.getTabActivator(this.template.attr('id')); 82 | if (tabActivator.length === 0) { 83 | throw new FormsetError("Template is .tab-pane but couldn't find corresponding tab activator."); 84 | } 85 | tabNav = tabActivator.closest('.nav'); 86 | if (tabNav.length === 0) { 87 | throw new FormsetError("Template is .tab-pane but couldn't find corresponding .nav."); 88 | } 89 | this.tabTemplate = tabNav.children("." + this.opts.formTemplateClass); 90 | if (this.tabTemplate.length === 0) { 91 | throw new FormsetError("Tab nav template not found (looking for ." + this.opts.formTemplateClass + ")."); 92 | } 93 | }; 94 | 95 | Formset.prototype.addForm = function() { 96 | var newForm, newFormElem, newTab, newTabElem, tabInsertAnchor; 97 | if (this.hasTabs) { 98 | newTabElem = this.tabTemplate.clone().removeClass(this.opts.formTemplateClass); 99 | newTab = new this.opts.tabClass(newTabElem); 100 | if (this.forms.length > 0) { 101 | tabInsertAnchor = this.forms[this.forms.length - 1].tab.elem; 102 | } else { 103 | tabInsertAnchor = this.tabTemplate; 104 | } 105 | newTabElem.insertAfter(tabInsertAnchor); 106 | } 107 | newFormElem = this.template.clone().removeClass(this.opts.formTemplateClass); 108 | newForm = new this.opts.formClass(newFormElem, this, parseInt(this.totalForms.val()), newTab); 109 | newFormElem.insertAfter(this.insertAnchor); 110 | this.insertAnchor = newFormElem; 111 | this.forms.push(newForm); 112 | this.totalForms.val(parseInt(this.totalForms.val()) + 1); 113 | if (this.hasTabs) { 114 | newTab.activate(); 115 | } 116 | $(this).trigger("formInitialized", [newForm]); 117 | $(this).trigger("formAdded", [newForm]); 118 | return newForm; 119 | }; 120 | 121 | Formset.prototype.deleteForm = function(index) { 122 | var form; 123 | form = this.forms[index]; 124 | form["delete"](); 125 | }; 126 | 127 | Formset.prototype.handleFormRemoved = function(index) { 128 | var form, i, _i, _len, _ref; 129 | this.totalForms.val(parseInt(this.totalForms.val()) - 1); 130 | this.forms.splice(index, 1); 131 | _ref = this.forms; 132 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 133 | form = _ref[i]; 134 | form._updateFormIndex(i); 135 | } 136 | if (this.forms.length === 0) { 137 | this.insertAnchor = this.template; 138 | } else { 139 | this.insertAnchor = this.forms[this.forms.length - 1].elem; 140 | } 141 | }; 142 | 143 | return Formset; 144 | 145 | })(); 146 | $.fn.djangoFormset.Form = (function() { 147 | function Form(elem, formset, index, tab) { 148 | var isInitial; 149 | this.elem = elem; 150 | this.formset = formset; 151 | this.index = index; 152 | this.tab = tab; 153 | this.elem.data('djangoFormset.Form', this); 154 | if (this.index !== void 0) { 155 | this._initFormIndex(this.index); 156 | } 157 | this.deleteInput = this.field('DELETE'); 158 | isInitial = this.index < this.formset.initialForms; 159 | if (this.deleteInput.length > 0 || !isInitial) { 160 | this._replaceDeleteCheckboxWithButton(); 161 | } 162 | } 163 | 164 | Form.prototype.getDeleteButton = function() { 165 | return $(" " + this.formset.opts.deleteButtonText + " "); 166 | }; 167 | 168 | Form.prototype.insertDeleteButton = function() { 169 | if (this.deleteInput.length > 0) { 170 | this.deleteInput.after(this.deleteButton); 171 | } else { 172 | (this.elem.is('TR') ? this.elem.children().last() : this.elem.is('UL') || this.elem.is('OL') ? this.elem.append('li').children().last() : this.elem).append(this.deleteButton); 173 | } 174 | }; 175 | 176 | Form.prototype["delete"] = function() { 177 | var isInitial, nextTab, tabElems; 178 | isInitial = this.index < this.formset.initialForms; 179 | if (this.deleteInput.length === 0 && isInitial) { 180 | console.warn("Tried do delete non-deletable form " + this.formset.prefix + " #" + this.index + "."); 181 | return; 182 | } 183 | if (this.tab && this.tab.elem.is('.active')) { 184 | tabElems = this.formset.forms.map(function(index, form) { 185 | return form.tab.elem[0]; 186 | }); 187 | nextTab = tabElems.slice(this.index + 1).filter(':visible').first(); 188 | if (nextTab.length === 0) { 189 | nextTab = tabElems.slice(0, this.index).filter(':visible').last(); 190 | } 191 | if (nextTab.length > 0) { 192 | nextTab.data('djangoFormset.tab').activate(); 193 | } 194 | } 195 | if (isInitial) { 196 | if (this.deleteInput.length > 0) { 197 | this.deleteInput.val('on'); 198 | } 199 | if (this.tab) { 200 | this.tab.elem.hide(); 201 | } 202 | this.hide(); 203 | } else { 204 | if (this.tab) { 205 | this.tab.elem.remove(); 206 | } 207 | this.elem.remove(); 208 | this.formset.handleFormRemoved(this.index); 209 | } 210 | }; 211 | 212 | Form.prototype.hide = function() { 213 | return this.elem.hide(); 214 | }; 215 | 216 | Form.prototype.field = function(name) { 217 | return this.elem.find("[name='" + this.formset.prefix + "-" + this.index + "-" + name + "']"); 218 | }; 219 | 220 | Form.prototype.prev = function() { 221 | var form, _i, _ref; 222 | _ref = this.formset.forms.slice(0, +(this.index - 1) + 1 || 9e9); 223 | for (_i = _ref.length - 1; _i >= 0; _i += -1) { 224 | form = _ref[_i]; 225 | if (form.elem.is(':visible')) { 226 | return form; 227 | } 228 | } 229 | }; 230 | 231 | Form.prototype._replaceDeleteCheckboxWithButton = function() { 232 | var label, newDeleteInput; 233 | if (this.deleteInput.length > 0) { 234 | newDeleteInput = $(""); 235 | label = this.elem.find("label[for='" + (this.deleteInput.attr('id')) + "']"); 236 | if (label.has(this.deleteInput).length > 0) { 237 | label.replaceWith(newDeleteInput); 238 | } else { 239 | label.remove(); 240 | this.deleteInput.replaceWith(newDeleteInput); 241 | } 242 | this.deleteInput = newDeleteInput; 243 | } 244 | this.deleteButton = this.getDeleteButton(); 245 | this.deleteButton.on('click', (function(_this) { 246 | return function(event) { 247 | return _this["delete"](); 248 | }; 249 | })(this)); 250 | this.insertDeleteButton(); 251 | }; 252 | 253 | Form.prototype._replaceFormIndex = function(oldIndexPattern, index) { 254 | var newPrefix, prefixRegex, _replaceFormIndexElement; 255 | this.index = index; 256 | prefixRegex = new RegExp("" + this.formset.prefix + "-" + oldIndexPattern); 257 | newPrefix = "" + this.formset.prefix + "-" + index; 258 | _replaceFormIndexElement = function(elem) { 259 | var attributeName, attributeNames, attributeNamesByTagName, tagName, _i, _len, _results; 260 | attributeNamesByTagName = { 261 | input: ['id', 'name'], 262 | select: ['id', 'name'], 263 | textarea: ['id', 'name'], 264 | label: ['for'], 265 | div: ['id'], 266 | '*': ['href', 'data-target'] 267 | }; 268 | tagName = elem.get(0).tagName; 269 | attributeNames = []; 270 | if (tagName.toLowerCase() in attributeNamesByTagName) { 271 | attributeNames = attributeNamesByTagName[tagName.toLowerCase()]; 272 | } 273 | attributeNames.push.apply(attributeNames, attributeNamesByTagName['*']); 274 | _results = []; 275 | for (_i = 0, _len = attributeNames.length; _i < _len; _i++) { 276 | attributeName = attributeNames[_i]; 277 | if (elem.attr(attributeName)) { 278 | _results.push(elem.attr(attributeName, elem.attr(attributeName).replace(prefixRegex, newPrefix))); 279 | } else { 280 | _results.push(void 0); 281 | } 282 | } 283 | return _results; 284 | }; 285 | _replaceFormIndexElement(this.elem); 286 | this.elem.find('input, select, textarea, label').each(function() { 287 | _replaceFormIndexElement($(this)); 288 | }); 289 | if (this.tab) { 290 | _replaceFormIndexElement(this.tab.elem); 291 | this.tab.elem.find('a, button').each(function() { 292 | _replaceFormIndexElement($(this)); 293 | }); 294 | } 295 | }; 296 | 297 | Form.prototype._initFormIndex = function(index) { 298 | this._replaceFormIndex("__prefix__", index); 299 | }; 300 | 301 | Form.prototype._updateFormIndex = function(index) { 302 | this._replaceFormIndex('\\d+', index); 303 | }; 304 | 305 | return Form; 306 | 307 | })(); 308 | $.fn.djangoFormset.Tab = (function() { 309 | function Tab(elem) { 310 | this.elem = elem; 311 | this.elem.data('djangoFormset.tab', this); 312 | } 313 | 314 | Tab.prototype.activate = function() { 315 | return this.elem.find("[data-toggle='tab']").trigger('click'); 316 | }; 317 | 318 | Tab.prototype.remove = function() { 319 | return this.elem.remove(); 320 | }; 321 | 322 | return Tab; 323 | 324 | })(); 325 | $.fn.djangoFormset.defaultOptions = { 326 | formTemplateClass: 'empty-form', 327 | formClass: $.fn.djangoFormset.Form, 328 | tabClass: $.fn.djangoFormset.Tab, 329 | deleteButtonText: 'Delete' 330 | }; 331 | $.djangoFormset = { 332 | getTabActivator: function(id) { 333 | return $("[href='#" + id + "'], [data-target='#" + id + "']"); 334 | } 335 | }; 336 | })(jQuery); 337 | 338 | //# sourceMappingURL=django-formset.js.map 339 | -------------------------------------------------------------------------------- /dist/django-formset.js: -------------------------------------------------------------------------------- 1 | /*! Django Formset - v0.3.0 - 2014-11-15 2 | * https://github.com/mbertheau/jquery.django-formset 3 | * Copyright (c) 2014 Markus Bertheau; Licensed MIT */ 4 | var __hasProp = {}.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 6 | 7 | (function($) { 8 | var FormsetError; 9 | $.fn.djangoFormset = function(options) { 10 | return new $.fn.djangoFormset.Formset(this, options); 11 | }; 12 | FormsetError = (function(_super) { 13 | __extends(FormsetError, _super); 14 | 15 | function FormsetError() { 16 | return FormsetError.__super__.constructor.apply(this, arguments); 17 | } 18 | 19 | return FormsetError; 20 | 21 | })(Error); 22 | $.fn.djangoFormset.Formset = (function() { 23 | function Formset(base, options) { 24 | var deletedForms, forms, inputName, placeholderPos; 25 | this.opts = $.extend({}, $.fn.djangoFormset.defaultOptions, options); 26 | if (base.length === 0) { 27 | throw new FormsetError("Empty selector."); 28 | } 29 | this.template = base.filter("." + this.opts.formTemplateClass); 30 | if (this.template.length === 0) { 31 | throw new FormsetError("Can't find template (looking for ." + this.opts.formTemplateClass + ")"); 32 | } 33 | inputName = this.template.find("input,select,textarea").first().attr('name'); 34 | if (!inputName) { 35 | throw new FormsetError("Can't figure out form prefix because there's no form element in the form template. Please add one."); 36 | } 37 | placeholderPos = inputName.indexOf('-__prefix__'); 38 | if (placeholderPos === -1) { 39 | throw new FormsetError("Can't figure out form prefix from template because it doesn't contain '-__prefix__'."); 40 | } 41 | this.prefix = inputName.substring(0, placeholderPos); 42 | this.totalForms = $("#id_" + this.prefix + "-TOTAL_FORMS"); 43 | if (this.totalForms.length === 0) { 44 | throw new FormsetError("Management form field 'TOTAL_FORMS' not found for prefix " + this.prefix + "."); 45 | } 46 | this._initTabs(); 47 | forms = base.not("." + this.opts.formTemplateClass); 48 | this.initialForms = forms.length; 49 | $(this).on(this.opts.on); 50 | this.forms = forms.map((function(_this) { 51 | return function(index, element) { 52 | var newForm, tab, tabActivator; 53 | if (_this.hasTabs) { 54 | tabActivator = $.djangoFormset.getTabActivator(element.id); 55 | tab = new _this.opts.tabClass(tabActivator.closest('.nav > *')); 56 | } 57 | newForm = new _this.opts.formClass($(element), _this, index, tab); 58 | $(_this).trigger("formInitialized", [newForm]); 59 | return newForm; 60 | }; 61 | })(this)); 62 | if (this.forms.length !== parseInt(this.totalForms.val())) { 63 | console.error("TOTAL_FORMS is " + (this.totalForms.val()) + ", but " + this.forms.length + " non-template elements found in passed selection."); 64 | } 65 | deletedForms = this.forms.filter(function() { 66 | return this.deleteInput.val(); 67 | }); 68 | deletedForms.each(function() { 69 | return this["delete"](); 70 | }); 71 | this.insertAnchor = base.not("." + this.opts.formTemplateClass).last(); 72 | if (this.insertAnchor.length === 0) { 73 | this.insertAnchor = this.template; 74 | } 75 | return; 76 | } 77 | 78 | Formset.prototype._initTabs = function() { 79 | var tabActivator, tabNav; 80 | this.hasTabs = this.template.is('.tab-pane'); 81 | if (!this.hasTabs) { 82 | return; 83 | } 84 | tabActivator = $.djangoFormset.getTabActivator(this.template.attr('id')); 85 | if (tabActivator.length === 0) { 86 | throw new FormsetError("Template is .tab-pane but couldn't find corresponding tab activator."); 87 | } 88 | tabNav = tabActivator.closest('.nav'); 89 | if (tabNav.length === 0) { 90 | throw new FormsetError("Template is .tab-pane but couldn't find corresponding .nav."); 91 | } 92 | this.tabTemplate = tabNav.children("." + this.opts.formTemplateClass); 93 | if (this.tabTemplate.length === 0) { 94 | throw new FormsetError("Tab nav template not found (looking for ." + this.opts.formTemplateClass + ")."); 95 | } 96 | }; 97 | 98 | Formset.prototype.addForm = function() { 99 | var newForm, newFormElem, newTab, newTabElem, tabInsertAnchor; 100 | if (this.hasTabs) { 101 | newTabElem = this.tabTemplate.clone().removeClass(this.opts.formTemplateClass); 102 | newTab = new this.opts.tabClass(newTabElem); 103 | if (this.forms.length > 0) { 104 | tabInsertAnchor = this.forms[this.forms.length - 1].tab.elem; 105 | } else { 106 | tabInsertAnchor = this.tabTemplate; 107 | } 108 | newTabElem.insertAfter(tabInsertAnchor); 109 | } 110 | newFormElem = this.template.clone().removeClass(this.opts.formTemplateClass); 111 | newForm = new this.opts.formClass(newFormElem, this, parseInt(this.totalForms.val()), newTab); 112 | newFormElem.insertAfter(this.insertAnchor); 113 | this.insertAnchor = newFormElem; 114 | this.forms.push(newForm); 115 | this.totalForms.val(parseInt(this.totalForms.val()) + 1); 116 | if (this.hasTabs) { 117 | newTab.activate(); 118 | } 119 | $(this).trigger("formInitialized", [newForm]); 120 | $(this).trigger("formAdded", [newForm]); 121 | return newForm; 122 | }; 123 | 124 | Formset.prototype.deleteForm = function(index) { 125 | var form; 126 | form = this.forms[index]; 127 | form["delete"](); 128 | }; 129 | 130 | Formset.prototype.handleFormRemoved = function(index) { 131 | var form, i, _i, _len, _ref; 132 | this.totalForms.val(parseInt(this.totalForms.val()) - 1); 133 | this.forms.splice(index, 1); 134 | _ref = this.forms; 135 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 136 | form = _ref[i]; 137 | form._updateFormIndex(i); 138 | } 139 | if (this.forms.length === 0) { 140 | this.insertAnchor = this.template; 141 | } else { 142 | this.insertAnchor = this.forms[this.forms.length - 1].elem; 143 | } 144 | }; 145 | 146 | return Formset; 147 | 148 | })(); 149 | $.fn.djangoFormset.Form = (function() { 150 | function Form(elem, formset, index, tab) { 151 | var isInitial; 152 | this.elem = elem; 153 | this.formset = formset; 154 | this.index = index; 155 | this.tab = tab; 156 | this.elem.data('djangoFormset.Form', this); 157 | if (this.index !== void 0) { 158 | this._initFormIndex(this.index); 159 | } 160 | this.deleteInput = this.field('DELETE'); 161 | isInitial = this.index < this.formset.initialForms; 162 | if (this.deleteInput.length > 0 || !isInitial) { 163 | this._replaceDeleteCheckboxWithButton(); 164 | } 165 | } 166 | 167 | Form.prototype.getDeleteButton = function() { 168 | return $(" " + this.formset.opts.deleteButtonText + " "); 169 | }; 170 | 171 | Form.prototype.insertDeleteButton = function() { 172 | if (this.deleteInput.length > 0) { 173 | this.deleteInput.after(this.deleteButton); 174 | } else { 175 | (this.elem.is('TR') ? this.elem.children().last() : this.elem.is('UL') || this.elem.is('OL') ? this.elem.append('li').children().last() : this.elem).append(this.deleteButton); 176 | } 177 | }; 178 | 179 | Form.prototype["delete"] = function() { 180 | var isInitial, nextTab, tabElems; 181 | isInitial = this.index < this.formset.initialForms; 182 | if (this.deleteInput.length === 0 && isInitial) { 183 | console.warn("Tried do delete non-deletable form " + this.formset.prefix + " #" + this.index + "."); 184 | return; 185 | } 186 | if (this.tab && this.tab.elem.is('.active')) { 187 | tabElems = this.formset.forms.map(function(index, form) { 188 | return form.tab.elem[0]; 189 | }); 190 | nextTab = tabElems.slice(this.index + 1).filter(':visible').first(); 191 | if (nextTab.length === 0) { 192 | nextTab = tabElems.slice(0, this.index).filter(':visible').last(); 193 | } 194 | if (nextTab.length > 0) { 195 | nextTab.data('djangoFormset.tab').activate(); 196 | } 197 | } 198 | if (isInitial) { 199 | if (this.deleteInput.length > 0) { 200 | this.deleteInput.val('on'); 201 | } 202 | if (this.tab) { 203 | this.tab.elem.hide(); 204 | } 205 | this.hide(); 206 | } else { 207 | if (this.tab) { 208 | this.tab.elem.remove(); 209 | } 210 | this.elem.remove(); 211 | this.formset.handleFormRemoved(this.index); 212 | } 213 | }; 214 | 215 | Form.prototype.hide = function() { 216 | return this.elem.hide(); 217 | }; 218 | 219 | Form.prototype.field = function(name) { 220 | return this.elem.find("[name='" + this.formset.prefix + "-" + this.index + "-" + name + "']"); 221 | }; 222 | 223 | Form.prototype.prev = function() { 224 | var form, _i, _ref; 225 | _ref = this.formset.forms.slice(0, +(this.index - 1) + 1 || 9e9); 226 | for (_i = _ref.length - 1; _i >= 0; _i += -1) { 227 | form = _ref[_i]; 228 | if (form.elem.is(':visible')) { 229 | return form; 230 | } 231 | } 232 | }; 233 | 234 | Form.prototype._replaceDeleteCheckboxWithButton = function() { 235 | var label, newDeleteInput; 236 | if (this.deleteInput.length > 0) { 237 | newDeleteInput = $(""); 238 | label = this.elem.find("label[for='" + (this.deleteInput.attr('id')) + "']"); 239 | if (label.has(this.deleteInput).length > 0) { 240 | label.replaceWith(newDeleteInput); 241 | } else { 242 | label.remove(); 243 | this.deleteInput.replaceWith(newDeleteInput); 244 | } 245 | this.deleteInput = newDeleteInput; 246 | } 247 | this.deleteButton = this.getDeleteButton(); 248 | this.deleteButton.on('click', (function(_this) { 249 | return function(event) { 250 | return _this["delete"](); 251 | }; 252 | })(this)); 253 | this.insertDeleteButton(); 254 | }; 255 | 256 | Form.prototype._replaceFormIndex = function(oldIndexPattern, index) { 257 | var newPrefix, prefixRegex, _replaceFormIndexElement; 258 | this.index = index; 259 | prefixRegex = new RegExp("" + this.formset.prefix + "-" + oldIndexPattern); 260 | newPrefix = "" + this.formset.prefix + "-" + index; 261 | _replaceFormIndexElement = function(elem) { 262 | var attributeName, attributeNames, attributeNamesByTagName, tagName, _i, _len, _results; 263 | attributeNamesByTagName = { 264 | input: ['id', 'name'], 265 | select: ['id', 'name'], 266 | textarea: ['id', 'name'], 267 | label: ['for'], 268 | div: ['id'], 269 | '*': ['href', 'data-target'] 270 | }; 271 | tagName = elem.get(0).tagName; 272 | attributeNames = []; 273 | if (tagName.toLowerCase() in attributeNamesByTagName) { 274 | attributeNames = attributeNamesByTagName[tagName.toLowerCase()]; 275 | } 276 | attributeNames.push.apply(attributeNames, attributeNamesByTagName['*']); 277 | _results = []; 278 | for (_i = 0, _len = attributeNames.length; _i < _len; _i++) { 279 | attributeName = attributeNames[_i]; 280 | if (elem.attr(attributeName)) { 281 | _results.push(elem.attr(attributeName, elem.attr(attributeName).replace(prefixRegex, newPrefix))); 282 | } else { 283 | _results.push(void 0); 284 | } 285 | } 286 | return _results; 287 | }; 288 | _replaceFormIndexElement(this.elem); 289 | this.elem.find('input, select, textarea, label').each(function() { 290 | _replaceFormIndexElement($(this)); 291 | }); 292 | if (this.tab) { 293 | _replaceFormIndexElement(this.tab.elem); 294 | this.tab.elem.find('a, button').each(function() { 295 | _replaceFormIndexElement($(this)); 296 | }); 297 | } 298 | }; 299 | 300 | Form.prototype._initFormIndex = function(index) { 301 | this._replaceFormIndex("__prefix__", index); 302 | }; 303 | 304 | Form.prototype._updateFormIndex = function(index) { 305 | this._replaceFormIndex('\\d+', index); 306 | }; 307 | 308 | return Form; 309 | 310 | })(); 311 | $.fn.djangoFormset.Tab = (function() { 312 | function Tab(elem) { 313 | this.elem = elem; 314 | this.elem.data('djangoFormset.tab', this); 315 | } 316 | 317 | Tab.prototype.activate = function() { 318 | return this.elem.find("[data-toggle='tab']").trigger('click'); 319 | }; 320 | 321 | Tab.prototype.remove = function() { 322 | return this.elem.remove(); 323 | }; 324 | 325 | return Tab; 326 | 327 | })(); 328 | $.fn.djangoFormset.defaultOptions = { 329 | formTemplateClass: 'empty-form', 330 | formClass: $.fn.djangoFormset.Form, 331 | tabClass: $.fn.djangoFormset.Tab, 332 | deleteButtonText: 'Delete' 333 | }; 334 | $.djangoFormset = { 335 | getTabActivator: function(id) { 336 | return $("[href='#" + id + "'], [data-target='#" + id + "']"); 337 | } 338 | }; 339 | })(jQuery); 340 | 341 | //# sourceMappingURL=django-formset.js.map 342 | -------------------------------------------------------------------------------- /test/django-formset_test.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var getTotalFormsValue, moduleSetup; 3 | moduleSetup = function() { 4 | var allFixtures; 5 | allFixtures = $("#qunit-fixture"); 6 | this.fixtureIDontExist = allFixtures.find('#i-dont-exist'); 7 | this.fixtureNoTemplate = allFixtures.find('#no-template'); 8 | this.fixtureNoTotalForms = allFixtures.find('#no-total-forms'); 9 | this.fixtureSimpleList = allFixtures.find('#simple-list'); 10 | this.fixtureSimpleFormAsList = allFixtures.find('#simple-form-as-list'); 11 | this.fixtureSimpleFormAsListDeleteInputInsideLabel = allFixtures.find('#simple-form-as-list-delete-input-inside-label'); 12 | this.fixtureSimpleTable = allFixtures.find('#simple-table'); 13 | this.fixtureDivWithForm = allFixtures.find('#div-with-form'); 14 | this.fixtureDivWithFormOneInitial = allFixtures.find('#div-with-form-one-initial'); 15 | this.fixtureDivWithNestedFormsets = allFixtures.find('#div-with-nested-formsets'); 16 | }; 17 | module("jQuery#djangoFormset - functional tests", { 18 | setup: moduleSetup 19 | }); 20 | test("throws when jQuery selection is empty", function() { 21 | throws((function() { 22 | return this.fixtureIDontExist.children('li').djangoFormset(); 23 | }), /Empty selector./, "throws Error"); 24 | }); 25 | test("throws on missing TOTAL_FORMS", function() { 26 | throws((function() { 27 | return this.fixtureNoTotalForms.children('li').djangoFormset(); 28 | }), /Management form field 'TOTAL_FORMS' not found for prefix no-total-forms/, "throws Error"); 29 | }); 30 | test("throws on missing template", function() { 31 | throws((function() { 32 | return this.fixtureNoTemplate.children('li').djangoFormset(); 33 | }), /Can\'t find template \(looking for .empty-form\)/, "throws Error"); 34 | }); 35 | test("can add form", function() { 36 | var formset; 37 | formset = this.fixtureSimpleList.children('li').djangoFormset(); 38 | equal(this.fixtureSimpleList.find(".empty-form").length, 1, "there's exactly one template form"); 39 | equal(this.fixtureSimpleList.children(":visible").length, 3, "and three visible templates"); 40 | formset.addForm(); 41 | equal(this.fixtureSimpleList.find(".empty-form").length, 1, "there's still exactly one template form"); 42 | equal(this.fixtureSimpleList.children(":visible").length, 4, "but now four visible templates"); 43 | equal(this.fixtureSimpleList.children(":visible").last().data('djangoFormset.Form'), formset.forms[3], "and the Form object is available as .data('djangoFormset.Form')"); 44 | }); 45 | test("adds form at the end", function() { 46 | var formset, lastChild; 47 | formset = this.fixtureSimpleList.children('li').djangoFormset(); 48 | equal(this.fixtureSimpleList.children(":visible:last-child").contents().first().text(), "awesome test markup", "just checking current last form"); 49 | formset.addForm(); 50 | lastChild = this.fixtureSimpleList.children(":visible:last-child").contents().first(); 51 | equal(lastChild.text(), "template", "first new form was added at the end"); 52 | lastChild[0].data = "this is the form that was added first"; 53 | equal(lastChild.text(), "this is the form that was added first", "the text of the newly added form was changed"); 54 | formset.addForm(); 55 | lastChild = this.fixtureSimpleList.children(":visible:last-child").contents().first(); 56 | equal(lastChild.text(), "template", "second new form was added at the end"); 57 | }); 58 | test("adds forms to tables as new rows", function() { 59 | var formset; 60 | formset = this.fixtureSimpleTable.children('tbody').children('tr').djangoFormset(); 61 | equal(this.fixtureSimpleTable.find('tbody > tr:visible').length, 0, "no forms there initially"); 62 | formset.addForm(); 63 | equal(this.fixtureSimpleTable.find('tbody > tr:visible').length, 1, "one row was added"); 64 | }); 65 | getTotalFormsValue = function(fixture, formset) { 66 | return parseInt(fixture.find("input[name='" + formset.prefix + "-TOTAL_FORMS']").val()); 67 | }; 68 | test("replaces form index template and updates TOTAL_FORMS", function() { 69 | var checkFormIndex, formset; 70 | checkFormIndex = function(fixture, formset, index) { 71 | var element, idValue, nameValue, selector, type, types_and_selectors; 72 | equal(getTotalFormsValue(fixture, formset), index + 1, "after adding one form TOTAL_FORMS is " + (index + 1)); 73 | types_and_selectors = { 74 | select: 'select', 75 | textarea: 'textarea', 76 | text: 'input[type="text"]', 77 | checkbox: 'input[type="checkbox"]' 78 | }; 79 | for (type in types_and_selectors) { 80 | selector = types_and_selectors[type]; 81 | element = fixture.find("div:visible " + selector + "[name$='" + type + "']").last(); 82 | nameValue = element.attr('name'); 83 | idValue = element.attr('id'); 84 | equal(nameValue, "" + formset.prefix + "-" + index + "-" + type, "the " + type + "'s name has the id " + index + " in it"); 85 | if (idValue !== void 0) { 86 | equal(idValue, "id_" + nameValue, "the " + type + "'s id value is the same as name with id_ prefix"); 87 | } 88 | } 89 | return equal(fixture.find('div:visible label').last().attr('for'), fixture.find('div:visible input[type="checkbox"]').last().attr('id'), "the label's for attribute has the same value as the checkbox' id attribute"); 90 | }; 91 | formset = this.fixtureDivWithForm.children('div').djangoFormset(); 92 | equal(parseInt(this.fixtureDivWithForm.find('input[name="div-with-form-TOTAL_FORMS"]').val()), 0, "initially TOTAL_FORMS is 0"); 93 | formset.addForm(); 94 | checkFormIndex(this.fixtureDivWithForm, formset, 0); 95 | formset.addForm(); 96 | checkFormIndex(this.fixtureDivWithForm, formset, 1); 97 | }); 98 | test("adds delete button to existing and new forms", function() { 99 | var checkDeleteButton, fixture, formset; 100 | fixture = this.fixtureDivWithFormOneInitial; 101 | formset = fixture.children('div').djangoFormset(); 102 | checkDeleteButton = function(form) { 103 | var deleteButton, deleteName; 104 | deleteButton = form.elem.children().last(); 105 | equal(deleteButton[0].outerHTML, ' Delete ', "Last element in form is the delete button"); 106 | deleteName = "" + formset.prefix + "-" + form.index + "-DELETE"; 107 | equal(form.elem.find("input[name='" + deleteName + "']").is(':hidden'), true, "the DELETE input is hidden"); 108 | return equal(form.elem.find("label[for='id_" + deleteName + "']").length, 0, "there's no label for the DELETE input"); 109 | }; 110 | checkDeleteButton(formset.forms[0]); 111 | formset.addForm(); 112 | checkDeleteButton(formset.forms[1]); 113 | }); 114 | test("deletes form that was added before", function() { 115 | var fixture, form, formset, _i, _len, _ref; 116 | _ref = [this.fixtureDivWithFormOneInitial, this.fixtureDivWithNestedFormsets]; 117 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 118 | fixture = _ref[_i]; 119 | formset = fixture.children('div').djangoFormset(); 120 | form = formset.addForm(); 121 | equal(getTotalFormsValue(fixture, formset), 2, "for " + formset.prefix + ": TOTAL_FORMS is 2 now"); 122 | equal(form.elem.find("button:contains('Delete')").length, 1, "The added form has a delete button"); 123 | formset.deleteForm(1); 124 | equal(fixture.children('div').length, 2, "for " + formset.prefix + ": the added form was deleted again"); 125 | equal(getTotalFormsValue(fixture, formset), 1, "for " + formset.prefix + ": TOTAL_FORMS is back to 1 again"); 126 | } 127 | }); 128 | test("renumbers when deleting newly added row from the middle", function() { 129 | var formset; 130 | formset = this.fixtureDivWithForm.children('div').djangoFormset(); 131 | formset.addForm(); 132 | formset.addForm(); 133 | formset.deleteForm(0); 134 | equal(this.fixtureDivWithForm.find("input[type='text']").last().attr('name'), 'div-with-form-0-text', "the text input that was at index 1 now has the name objects_set-0-text"); 135 | }); 136 | test("figures out the form prefix itself", function() { 137 | var formset; 138 | formset = this.fixtureDivWithForm.children('div').djangoFormset(); 139 | equal(formset.prefix, "div-with-form"); 140 | }); 141 | test("deletes initially existing form", function() { 142 | var fixture, formset; 143 | fixture = this.fixtureDivWithFormOneInitial; 144 | formset = fixture.children('div').djangoFormset(); 145 | formset.deleteForm(0); 146 | equal(fixture.find("input[name='div-with-form-one-initial-0-DELETE']").val(), "on", "delete checkbox is now checked"); 147 | equal(formset.forms[0].elem.is(':visible'), false, "removed form is now hidden"); 148 | }); 149 | test("add - delete - add adds a one new row", function() { 150 | var fixture, formset; 151 | fixture = this.fixtureSimpleTable.children('tbody'); 152 | formset = fixture.children('tr').djangoFormset(); 153 | equal(fixture.children('tr:visible').length, 0, "initially there's 0 forms"); 154 | formset.addForm(); 155 | equal(fixture.children('tr:visible').length, 1, "after adding one there's 1"); 156 | formset.addForm(); 157 | equal(fixture.children('tr:visible').length, 2, "after adding another one there's 2"); 158 | formset.deleteForm(1); 159 | equal(fixture.children('tr:visible').length, 1, "after deleting one it's 1 again"); 160 | formset.addForm(); 161 | equal(fixture.children('tr:visible').length, 2, "and after adding one again it's 2 again"); 162 | formset.deleteForm(1); 163 | equal(fixture.children('tr:visible').length, 1, "after deleting one it's 1 again"); 164 | formset.deleteForm(0); 165 | equal(fixture.children('tr:visible').length, 0, "after deleting the last one it's 0"); 166 | formset.addForm(); 167 | equal(fixture.children('tr:visible').length, 1, "and after adding one again it's 1 again"); 168 | }); 169 | test("replaces only first prefix when adding outer forms in nested formset", function() { 170 | var fixture, form, forms, formset, nestedEmptyForm, nestedInput; 171 | fixture = this.fixtureDivWithNestedFormsets; 172 | formset = fixture.children('div').djangoFormset(); 173 | formset.addForm(); 174 | forms = fixture.children('div:visible'); 175 | equal(forms.length, 2, "there's two visible outer forms now"); 176 | form = forms.last(); 177 | equal(form.find('input[type="text"]').first().attr('name'), "div-with-nested-formsets-1-outer", "outer form element has prefix correctly replaced in input name"); 178 | nestedEmptyForm = form.find('.empty-form'); 179 | equal(nestedEmptyForm.length, 1, "nested form template was copied as well"); 180 | nestedInput = nestedEmptyForm.find('input[type="text"]'); 181 | equal(nestedInput.attr('name'), "div-with-nested-formsets-1-variant_set-__prefix__-inner", "nested input in template has only first prefix replaced"); 182 | }); 183 | test("addForm on added inner formset works", function() { 184 | var fixture, form, formset, nestedForm, nestedFormset; 185 | fixture = this.fixtureDivWithNestedFormsets; 186 | formset = fixture.children('div').djangoFormset(); 187 | nestedFormset = null; 188 | $(formset).on('formAdded', function(event, form) { 189 | return nestedFormset = form.elem.children('div').djangoFormset(); 190 | }); 191 | formset.addForm(); 192 | form = fixture.children('div:visible').last(); 193 | equal(form.children('input[type="text"]').attr('name'), 'div-with-nested-formsets-1-outer', 'one form was added'); 194 | nestedFormset.addForm(); 195 | nestedForm = form.children('div:visible').last(); 196 | equal(nestedForm.children('input[type="text"]').attr('name'), 'div-with-nested-formsets-1-variant_set-0-inner', 'added inner form input has the prefix replaced with the correct id'); 197 | }); 198 | test("forms not marked for deletion stay that way when creating the formset", function() { 199 | var fixture, formset; 200 | fixture = this.fixtureSimpleFormAsList; 201 | formset = fixture.children('ul').djangoFormset({ 202 | formTemplateClass: "custom-template-class" 203 | }); 204 | equal(fixture.find("[name='simple-form-as-list-0-DELETE']").val(), '', "hidden field value is empty"); 205 | }); 206 | test("Form without delete checkbox doesn't get delete button", function() { 207 | var fixture, formset; 208 | fixture = this.fixtureSimpleList; 209 | formset = fixture.children('li').djangoFormset(); 210 | equal(fixture.find("button:contains('Delete')").length, 0, "There are no delete buttons in the fixture"); 211 | }); 212 | test("Form without delete checkbox can't be deleted programmatically", function() { 213 | var fixture, formset; 214 | fixture = this.fixtureSimpleList; 215 | formset = fixture.children('li').djangoFormset(); 216 | formset.deleteForm(1); 217 | equal(fixture.children('li:visible').length, 3, "there's still 3 visible forms"); 218 | }); 219 | test("formInitialized is triggered for every existing and added form", function() { 220 | var fixture, form, formset, index, _i, _len, _ref; 221 | fixture = this.fixtureSimpleList; 222 | formset = this.fixtureSimpleList.children('li').djangoFormset({ 223 | on: { 224 | formInitialized: function(event, form) { 225 | return form.myProp = "foo"; 226 | } 227 | } 228 | }); 229 | formset.addForm(); 230 | _ref = formset.forms; 231 | for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) { 232 | form = _ref[index]; 233 | equal(form.myProp, "foo", "Form #" + index + " has the custom property set"); 234 | } 235 | }); 236 | test("adds delete button and input even if original delete input is inside its own label (issue #4)", function() { 237 | var fixture, formset; 238 | fixture = this.fixtureSimpleFormAsListDeleteInputInsideLabel; 239 | formset = fixture.children('ul').djangoFormset(); 240 | equal(fixture.find("button:contains('Delete')").length, 1, "Delete button is there"); 241 | equal(formset.forms[0].field('DELETE').length, 1, "Delete input is there"); 242 | }); 243 | })(jQuery); 244 | 245 | //# sourceMappingURL=django-formset_test.js.map 246 | -------------------------------------------------------------------------------- /test/django-formset_test.coffee: -------------------------------------------------------------------------------- 1 | (($) -> 2 | 3 | # 4 | # ======== A Handy Little QUnit Reference ======== 5 | # http://api.qunitjs.com/ 6 | # 7 | # Test methods: 8 | # module(name, {[setup][ ,teardown]}) 9 | # test(name, callback) 10 | # expect(numberOfAssertions) 11 | # stop(increment) 12 | # start(decrement) 13 | # Test assertions: 14 | # ok(value, [message]) 15 | # equal(actual, expected, [message]) 16 | # notEqual(actual, expected, [message]) 17 | # deepEqual(actual, expected, [message]) 18 | # notDeepEqual(actual, expected, [message]) 19 | # strictEqual(actual, expected, [message]) 20 | # notStrictEqual(actual, expected, [message]) 21 | # throws(block, [expected], [message]) 22 | # 23 | 24 | # This will run before each test in each module. 25 | moduleSetup = -> 26 | allFixtures = $("#qunit-fixture") 27 | @fixtureIDontExist = allFixtures.find('#i-dont-exist') 28 | @fixtureNoTemplate = allFixtures.find('#no-template') 29 | @fixtureNoTotalForms = allFixtures.find('#no-total-forms') 30 | @fixtureSimpleList = allFixtures.find('#simple-list') 31 | @fixtureSimpleFormAsList = allFixtures.find('#simple-form-as-list') 32 | @fixtureSimpleFormAsListDeleteInputInsideLabel = allFixtures.find( 33 | '#simple-form-as-list-delete-input-inside-label') 34 | @fixtureSimpleTable = allFixtures.find('#simple-table') 35 | @fixtureDivWithForm = allFixtures.find('#div-with-form') 36 | @fixtureDivWithFormOneInitial = allFixtures.find( 37 | '#div-with-form-one-initial') 38 | @fixtureDivWithNestedFormsets = allFixtures.find( 39 | '#div-with-nested-formsets') 40 | return 41 | 42 | module("jQuery#djangoFormset - functional tests", setup: moduleSetup) 43 | 44 | test("throws when jQuery selection is empty", -> 45 | throws((-> @fixtureIDontExist.children('li').djangoFormset()), 46 | /Empty selector./, 47 | "throws Error") 48 | return 49 | ) 50 | 51 | test("throws on missing TOTAL_FORMS", -> 52 | throws((-> 53 | @fixtureNoTotalForms.children('li').djangoFormset()), 54 | /Management form field 'TOTAL_FORMS' not found for prefix no-total-forms/, 55 | "throws Error") 56 | return 57 | ) 58 | 59 | test("throws on missing template", -> 60 | throws((-> 61 | @fixtureNoTemplate.children('li').djangoFormset()), 62 | /Can\'t find template \(looking for .empty-form\)/ 63 | "throws Error") 64 | return 65 | ) 66 | 67 | test("can add form", -> 68 | formset = @fixtureSimpleList.children('li').djangoFormset() 69 | 70 | equal(@fixtureSimpleList.find(".empty-form").length, 1, 71 | "there's exactly one template form") 72 | equal(@fixtureSimpleList.children(":visible").length, 3, 73 | "and three visible templates") 74 | 75 | formset.addForm() 76 | 77 | equal(@fixtureSimpleList.find(".empty-form").length, 1, 78 | "there's still exactly one template form") 79 | equal(@fixtureSimpleList.children(":visible").length, 4, 80 | "but now four visible templates") 81 | equal(@fixtureSimpleList.children(":visible").last() 82 | .data('djangoFormset.Form'), formset.forms[3], 83 | "and the Form object is available as .data('djangoFormset.Form')") 84 | 85 | return 86 | ) 87 | 88 | test("adds form at the end", -> 89 | formset = @fixtureSimpleList.children('li').djangoFormset() 90 | equal(@fixtureSimpleList.children(":visible:last-child") 91 | .contents().first().text(), 92 | "awesome test markup", 93 | "just checking current last form") 94 | 95 | formset.addForm() 96 | lastChild = @fixtureSimpleList.children(":visible:last-child") 97 | .contents().first() 98 | equal(lastChild.text(), "template", 99 | "first new form was added at the end") 100 | 101 | lastChild[0].data = "this is the form that was added first" 102 | equal(lastChild.text(), "this is the form that was added first", 103 | "the text of the newly added form was changed") 104 | 105 | formset.addForm() 106 | lastChild = @fixtureSimpleList.children(":visible:last-child") 107 | .contents().first() 108 | equal(lastChild.text(), "template", 109 | "second new form was added at the end") 110 | 111 | return 112 | ) 113 | 114 | test("adds forms to tables as new rows", -> 115 | formset = @fixtureSimpleTable.children('tbody').children('tr') 116 | .djangoFormset() 117 | equal(@fixtureSimpleTable.find('tbody > tr:visible').length, 0, 118 | "no forms there initially") 119 | 120 | formset.addForm() 121 | equal(@fixtureSimpleTable.find('tbody > tr:visible').length, 1, 122 | "one row was added") 123 | 124 | return 125 | ) 126 | 127 | getTotalFormsValue = (fixture, formset) -> 128 | parseInt(fixture.find("input[name='#{formset.prefix}-TOTAL_FORMS']").val()) 129 | 130 | test("replaces form index template and updates TOTAL_FORMS", -> 131 | checkFormIndex = (fixture, formset, index) -> 132 | equal(getTotalFormsValue(fixture, formset), index + 1, 133 | "after adding one form TOTAL_FORMS is #{index + 1}") 134 | 135 | types_and_selectors = { 136 | select: 'select' 137 | textarea: 'textarea' 138 | text: 'input[type="text"]' 139 | checkbox: 'input[type="checkbox"]' 140 | } 141 | for type, selector of types_and_selectors 142 | element = fixture.find("div:visible #{selector}[name$='#{type}']") 143 | .last() 144 | nameValue = element.attr('name') 145 | idValue = element.attr('id') 146 | 147 | equal(nameValue, "#{formset.prefix}-#{index}-#{type}", 148 | "the #{type}'s name has the id #{index} in it") 149 | 150 | if idValue isnt undefined 151 | equal(idValue, "id_#{nameValue}", 152 | "the #{type}'s id value is the same as name with id_ prefix") 153 | 154 | equal(fixture.find('div:visible label').last().attr('for'), 155 | fixture.find('div:visible input[type="checkbox"]').last().attr('id') 156 | "the label's for attribute has the same value as the checkbox' id 157 | attribute") 158 | 159 | formset = @fixtureDivWithForm.children('div').djangoFormset() 160 | equal(parseInt(@fixtureDivWithForm 161 | .find('input[name="div-with-form-TOTAL_FORMS"]').val()), 0, 162 | "initially TOTAL_FORMS is 0") 163 | 164 | formset.addForm() 165 | checkFormIndex(@fixtureDivWithForm, formset, 0) 166 | formset.addForm() 167 | checkFormIndex(@fixtureDivWithForm, formset, 1) 168 | return 169 | ) 170 | 171 | test("adds delete button to existing and new forms", -> 172 | fixture = @fixtureDivWithFormOneInitial 173 | formset = fixture.children('div').djangoFormset() 174 | 175 | checkDeleteButton = (form) -> 176 | deleteButton = form.elem.children().last() 177 | equal(deleteButton[0].outerHTML 178 | ' Delete ', 179 | "Last element in form is the delete button") 180 | 181 | deleteName = "#{formset.prefix}-#{form.index}-DELETE" 182 | equal(form.elem.find("input[name='#{deleteName}']").is(':hidden'), true, 183 | "the DELETE input is hidden") 184 | 185 | equal(form.elem.find("label[for='id_#{deleteName}']").length, 0, 186 | "there's no label for the DELETE input") 187 | 188 | checkDeleteButton(formset.forms[0]) 189 | 190 | formset.addForm() 191 | checkDeleteButton(formset.forms[1]) 192 | 193 | return 194 | ) 195 | 196 | test("deletes form that was added before", -> 197 | for fixture in [@fixtureDivWithFormOneInitial 198 | @fixtureDivWithNestedFormsets] 199 | formset = fixture.children('div').djangoFormset() 200 | 201 | form = formset.addForm() 202 | equal(getTotalFormsValue(fixture, formset), 2, 203 | "for #{formset.prefix}: TOTAL_FORMS is 2 now") 204 | equal(form.elem.find("button:contains('Delete')").length, 1, 205 | "The added form has a delete button") 206 | 207 | formset.deleteForm(1) 208 | 209 | equal(fixture.children('div').length, 2, 210 | "for #{formset.prefix}: the added form was deleted again") 211 | equal(getTotalFormsValue(fixture, formset), 1, 212 | "for #{formset.prefix}: TOTAL_FORMS is back to 1 again") 213 | return 214 | ) 215 | 216 | test("renumbers when deleting newly added row from the middle", -> 217 | formset = @fixtureDivWithForm.children('div').djangoFormset() 218 | 219 | formset.addForm() 220 | formset.addForm() 221 | formset.deleteForm(0) 222 | 223 | equal(@fixtureDivWithForm.find("input[type='text']").last().attr('name'), 224 | 'div-with-form-0-text', 225 | "the text input that was at index 1 now has the name objects_set-0-text") 226 | 227 | return 228 | ) 229 | 230 | test("figures out the form prefix itself", -> 231 | formset = @fixtureDivWithForm.children('div').djangoFormset() 232 | equal(formset.prefix, "div-with-form") 233 | 234 | return 235 | ) 236 | 237 | test("deletes initially existing form", -> 238 | fixture = @fixtureDivWithFormOneInitial 239 | formset = fixture.children('div').djangoFormset() 240 | 241 | formset.deleteForm(0) 242 | 243 | equal(fixture 244 | .find("input[name='div-with-form-one-initial-0-DELETE']").val(), "on", 245 | "delete checkbox is now checked") 246 | equal(formset.forms[0].elem.is(':visible'), false, 247 | "removed form is now hidden") 248 | 249 | return 250 | ) 251 | 252 | test("add - delete - add adds a one new row", -> 253 | fixture = @fixtureSimpleTable.children('tbody') 254 | formset = fixture.children('tr').djangoFormset() 255 | 256 | equal(fixture.children('tr:visible').length, 0, 257 | "initially there's 0 forms") 258 | 259 | formset.addForm() 260 | equal(fixture.children('tr:visible').length, 1, 261 | "after adding one there's 1") 262 | 263 | formset.addForm() 264 | equal(fixture.children('tr:visible').length, 2, 265 | "after adding another one there's 2") 266 | 267 | formset.deleteForm(1) 268 | equal(fixture.children('tr:visible').length, 1, 269 | "after deleting one it's 1 again") 270 | 271 | formset.addForm() 272 | equal(fixture.children('tr:visible').length, 2, 273 | "and after adding one again it's 2 again") 274 | 275 | formset.deleteForm(1) 276 | equal(fixture.children('tr:visible').length, 1, 277 | "after deleting one it's 1 again") 278 | 279 | formset.deleteForm(0) 280 | equal(fixture.children('tr:visible').length, 0, 281 | "after deleting the last one it's 0") 282 | 283 | formset.addForm() 284 | equal(fixture.children('tr:visible').length, 1, 285 | "and after adding one again it's 1 again") 286 | 287 | return 288 | ) 289 | 290 | test("replaces only first prefix when adding outer forms in nested formset", 291 | -> 292 | fixture = @fixtureDivWithNestedFormsets 293 | formset = fixture.children('div').djangoFormset() 294 | 295 | formset.addForm() 296 | 297 | forms = fixture.children('div:visible') 298 | equal(forms.length, 2, "there's two visible outer forms now") 299 | form = forms.last() 300 | equal(form.find('input[type="text"]').first().attr('name'), 301 | "div-with-nested-formsets-1-outer", 302 | "outer form element has prefix correctly replaced in input name") 303 | nestedEmptyForm = form.find('.empty-form') 304 | equal(nestedEmptyForm.length, 1, "nested form template was copied as well") 305 | nestedInput = nestedEmptyForm.find('input[type="text"]') 306 | equal(nestedInput.attr('name'), 307 | "div-with-nested-formsets-1-variant_set-__prefix__-inner", 308 | "nested input in template has only first prefix replaced") 309 | 310 | return 311 | ) 312 | 313 | test("addForm on added inner formset works", -> 314 | fixture = @fixtureDivWithNestedFormsets 315 | formset = fixture.children('div').djangoFormset() 316 | nestedFormset = null 317 | 318 | $(formset).on('formAdded', (event, form) -> 319 | nestedFormset = form.elem.children('div').djangoFormset() 320 | ) 321 | 322 | formset.addForm() 323 | form = fixture.children('div:visible').last() 324 | equal(form.children('input[type="text"]').attr('name'), 325 | 'div-with-nested-formsets-1-outer', 326 | 'one form was added') 327 | 328 | nestedFormset.addForm() 329 | nestedForm = form.children('div:visible').last() 330 | equal(nestedForm.children('input[type="text"]').attr('name'), 331 | 'div-with-nested-formsets-1-variant_set-0-inner', 332 | 'added inner form input has the prefix replaced with the correct id') 333 | 334 | return 335 | ) 336 | 337 | test("forms not marked for deletion stay that way when creating the formset", 338 | -> 339 | fixture = @fixtureSimpleFormAsList 340 | formset = fixture.children('ul').djangoFormset( 341 | formTemplateClass: "custom-template-class") 342 | 343 | equal(fixture.find("[name='simple-form-as-list-0-DELETE']").val(), 344 | '', "hidden field value is empty") 345 | 346 | return 347 | ) 348 | 349 | test("Form without delete checkbox doesn't get delete button", -> 350 | fixture = @fixtureSimpleList 351 | formset = fixture.children('li').djangoFormset() 352 | 353 | equal(fixture.find("button:contains('Delete')").length, 0, 354 | "There are no delete buttons in the fixture") 355 | 356 | return 357 | ) 358 | 359 | test("Form without delete checkbox can't be deleted programmatically", -> 360 | fixture = @fixtureSimpleList 361 | formset = fixture.children('li').djangoFormset() 362 | 363 | formset.deleteForm(1) 364 | 365 | equal(fixture.children('li:visible').length, 3, 366 | "there's still 3 visible forms") 367 | 368 | return 369 | ) 370 | 371 | test("formInitialized is triggered for every existing and added form", -> 372 | fixture = @fixtureSimpleList 373 | formset = @fixtureSimpleList.children('li').djangoFormset( 374 | on: 375 | formInitialized: (event, form) -> 376 | form.myProp = "foo" 377 | ) 378 | 379 | formset.addForm() 380 | 381 | for form, index in formset.forms 382 | equal(form.myProp, "foo", "Form ##{index} has the custom property set") 383 | 384 | return 385 | ) 386 | 387 | test("adds delete button and input even if original delete input is inside 388 | its own label (issue #4)", -> 389 | fixture = @fixtureSimpleFormAsListDeleteInputInsideLabel 390 | formset = fixture.children('ul').djangoFormset() 391 | 392 | equal(fixture.find("button:contains('Delete')").length, 1, 393 | "Delete button is there") 394 | equal(formset.forms[0].field('DELETE').length, 1, "Delete input is there") 395 | 396 | return 397 | ) 398 | 399 | return 400 | )(jQuery) 401 | --------------------------------------------------------------------------------