├── .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 | [![Build Status](https://travis-ci.org/mbertheau/jquery.django-formset.png?branch=master)](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 ``, ` 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 | 124 |
125 | 126 | 129 | 130 | 131 | 132 | 133 | 134 |
135 |
136 | 137 | 140 | 141 | 142 | 143 | 144 | 145 |
146 | 147 | 148 |
149 | 150 |
151 | 152 | 153 | 154 | 155 | 156 |
157 | 158 | 159 | 160 | 161 | 162 | 163 |
164 | 165 | 166 | 167 |
168 |
169 | 170 |
171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 |
180 | 181 | 182 | 183 |
184 |
185 | 186 | 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 $(""); 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 $(""); 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, '', "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 | '', 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 | --------------------------------------------------------------------------------