├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── INSTALL.rst ├── README.rst ├── demo ├── __init__.py ├── example │ ├── __init__.py │ ├── fixtures │ │ └── initial_data.json │ ├── forms.py │ ├── models.py │ ├── urls.py │ └── views.py ├── manage.py ├── requirements.txt ├── settings.py ├── static │ ├── css │ │ ├── default.css │ │ └── jquery.autocomplete.css │ ├── images │ │ ├── add.png │ │ ├── bg01.jpg │ │ ├── bg02.jpg │ │ ├── bg03.jpg │ │ ├── bg04.jpg │ │ ├── comments.png │ │ ├── delete.png │ │ ├── doc.png │ │ ├── edit.png │ │ ├── img02.gif │ │ ├── img05.gif │ │ ├── img06.jpg │ │ ├── loading.gif │ │ └── spacer.gif │ └── js │ │ ├── autocomplete-init.js │ │ ├── jquery-1.3.2.min.js │ │ ├── jquery.autocomplete.min.js │ │ └── jquery.formset.js ├── templates │ ├── base.html │ ├── example │ │ ├── empty-form.html │ │ ├── form-template.html │ │ ├── formset-admin-widget.html │ │ ├── formset-multiple-formsets.html │ │ ├── formset-stacked.html │ │ ├── formset-table.html │ │ ├── inline-formset-autocomplete.html │ │ ├── inline-formset-django-ajax-select.html │ │ ├── inline-formset.html │ │ ├── max-forms.html │ │ ├── min-forms.html │ │ └── posted-data.html │ └── index.html └── urls.py ├── docs └── usage.rst ├── package-lock.json ├── package.json ├── src └── jquery.formset.js └── tests ├── basic.js ├── index.html └── lib └── qmock.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Run tests and prepare release 🚀 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Use NodeJS 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18.x 22 | 23 | - name: Build 24 | run: | 25 | npm install 26 | npm run build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | 6 | before_install: 7 | - npm install -g grunt-cli 8 | 9 | script: grunt --verbose 10 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON("package.json"), 4 | qunit: { 5 | "jquery.formset": ["tests/**/*.html"], 6 | options: { 7 | puppeteer: { 8 | headless: 'new', 9 | }, 10 | }, 11 | }, 12 | uglify: { 13 | options: { 14 | banner: 15 | '/*! <%= pkg.name %> (v<%= pkg.version %>) <%= grunt.template.today("yyyy-mm-dd") %> */\n', 16 | }, 17 | build: { 18 | src: "src/<%= pkg.name %>.js", 19 | dest: "build/<%= pkg.name %>.min.js", 20 | }, 21 | }, 22 | }); 23 | 24 | grunt.loadNpmTasks('grunt-contrib-jshint'); 25 | grunt.loadNpmTasks('grunt-contrib-uglify'); 26 | grunt.loadNpmTasks('grunt-contrib-qunit'); 27 | 28 | grunt.registerTask("build", ["qunit", "uglify"]); 29 | }; -------------------------------------------------------------------------------- /INSTALL.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Installation instructions 3 | ========================= 4 | 5 | There are two ways to install this application for use by your 6 | projects; the first is to do a git checkout:: 7 | 8 | git clone https://github.com/elo80ka/django-dynamic-formset 9 | 10 | The second method is to download a packaged release. The latest 11 | release is 1.1. You can download the source and documentation only, 12 | or include the demo project:: 13 | 14 | wget http://django-dynamic-formset.googlecode.com/files/jquery.formset-1.1.zip 15 | unzip jquery.formset-1.1.zip -d jquery.formset-1.1 16 | cd jquery.formset-1.1 17 | 18 | The plugin files are in the ``src/`` directory. If you downloaded 19 | the archive with the demo project, you'll need to set it up first. 20 | 21 | 22 | Setting up the demo project 23 | =========================== 24 | 25 | The demo project is a Django project, showing different ways to 26 | use the plugin. To run the project, you'll need Python_ and Django_. 27 | For instructions on installing them, see their respective sites. 28 | 29 | Once you've got the source code, run the following commands to set up the 30 | SQLite3 database and start the development server:: 31 | 32 | cd demo 33 | virtualenv venv 34 | source venv/bin/activate 35 | pip install -r requirements.txt 36 | chmod a+x manage.py 37 | ./manage.py syncdb 38 | ./manage.py runserver 39 | 40 | You can now browse to ``http://localhost:8000/`` and view the examples. 41 | 42 | .. _Python: http://python.org/ 43 | .. _Django: http://www.djangoproject.com/ 44 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://secure.travis-ci.org/elo80ka/django-dynamic-formset.png?branch=master 2 | :target: http://travis-ci.org/#!/elo80ka/django-dynamic-formset 3 | 4 | A jQuery plugin for managing Django formsets 5 | ============================================ 6 | 7 | This jQuery_ plugin helps you create more usable Django_ formsets by 8 | allowing clients add and remove forms on the client-side. 9 | 10 | For installation instructions, see the file `INSTALL.rst `_ in 11 | this directory; for documentation see the files in the ``docs/`` 12 | directory. 13 | 14 | The latest versions of these documents can be found on the 15 | Github web site for this application, which is located at 16 | `https://github.com/elo80ka/django-dynamic-formset`. 17 | 18 | .. _jQuery: http://jquery.com/ 19 | .. _Django: http://www.djangoproject.com/ 20 | -------------------------------------------------------------------------------- /demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/__init__.py -------------------------------------------------------------------------------- /demo/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/example/__init__.py -------------------------------------------------------------------------------- /demo/example/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "example.product", 5 | "fields": { 6 | "name": "Black-eyed Peas" 7 | } 8 | }, 9 | { 10 | "pk": 10, 11 | "model": "example.product", 12 | "fields": { 13 | "name": "Kidney Beans" 14 | } 15 | }, 16 | { 17 | "pk": 3, 18 | "model": "example.product", 19 | "fields": { 20 | "name": "Minty Loin of Lamb" 21 | } 22 | }, 23 | { 24 | "pk": 7, 25 | "model": "example.product", 26 | "fields": { 27 | "name": "Olive Oil (Extra Virgin)" 28 | } 29 | }, 30 | { 31 | "pk": 8, 32 | "model": "example.product", 33 | "fields": { 34 | "name": "Oreos" 35 | } 36 | }, 37 | { 38 | "pk": 2, 39 | "model": "example.product", 40 | "fields": { 41 | "name": "Prime Bac'n" 42 | } 43 | }, 44 | { 45 | "pk": 9, 46 | "model": "example.product", 47 | "fields": { 48 | "name": "Reese's Peanut Butter Cups" 49 | } 50 | }, 51 | { 52 | "pk": 6, 53 | "model": "example.product", 54 | "fields": { 55 | "name": "Rice Wine Vinegar" 56 | } 57 | }, 58 | { 59 | "pk": 4, 60 | "model": "example.product", 61 | "fields": { 62 | "name": "Sweetcorn" 63 | } 64 | }, 65 | { 66 | "pk": 5, 67 | "model": "example.product", 68 | "fields": { 69 | "name": "Szechuan Peppercorn" 70 | } 71 | }, 72 | { 73 | "pk": 11, 74 | "model": "example.product", 75 | "fields": { 76 | "name": "Uncle Bob's Hot, Hot Sauce" 77 | } 78 | }, 79 | { 80 | "pk": 1, 81 | "model": "example.order", 82 | "fields": { 83 | "customer": "Kermit the frog", 84 | "date": "2010-02-13" 85 | } 86 | }, 87 | { 88 | "pk": 1, 89 | "model": "example.ordereditem", 90 | "fields": { 91 | "product": 1, 92 | "order": 1, 93 | "quantity": 5 94 | } 95 | }, 96 | { 97 | "pk": 2, 98 | "model": "example.ordereditem", 99 | "fields": { 100 | "product": 3, 101 | "order": 1, 102 | "quantity": 1 103 | } 104 | }, 105 | { 106 | "pk": 3, 107 | "model": "example.ordereditem", 108 | "fields": { 109 | "product": 7, 110 | "order": 1, 111 | "quantity": 1 112 | } 113 | } 114 | ] 115 | -------------------------------------------------------------------------------- /demo/example/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import fields, models, formsets, widgets 3 | from django.contrib.admin.widgets import AdminDateWidget 4 | from django.conf import settings 5 | from example.models import Product, Order, OrderedItem 6 | 7 | ############################ 8 | ## Inline Formset Example ## 9 | ############################ 10 | 11 | class OrderForm(models.ModelForm): 12 | class Meta: 13 | model = Order 14 | fields = '__all__' 15 | 16 | class OrderedItemForm(models.ModelForm): 17 | class Meta: 18 | model = OrderedItem 19 | fields = '__all__' 20 | 21 | class AutoCompleteOrderedItemForm(models.ModelForm): 22 | """ 23 | Display the Ordered Item form with an autocomplete textbox for the 24 | Products instead of the Dropdown List. 25 | """ 26 | 27 | class Meta: 28 | model = OrderedItem 29 | fields = '__all__' 30 | 31 | class Media: 32 | js = ('js/jquery.autocomplete.min.js', 'js/autocomplete-init.js',) 33 | css = { 34 | 'all': ('css/jquery.autocomplete.css',), 35 | } 36 | 37 | def __init__(self, *args, **kwargs): 38 | super(AutoCompleteOrderedItemForm, self).__init__(*args, **kwargs) 39 | self.fields['product'].widget = widgets.TextInput(attrs={'class': 'autocomplete-me'}) 40 | 41 | def get_ordereditem_formset(form, formset=models.BaseInlineFormSet, **kwargs): 42 | return models.inlineformset_factory(Order, OrderedItem, form, formset, **kwargs) 43 | 44 | ################################ 45 | ## Plain 'ole Formset example ## 46 | ################################ 47 | 48 | CONTACT_INFO_TYPES = ( 49 | ('Phone', 'Phone'), 50 | ('Fax', 'Fax'), 51 | ('Email', 'Email'), 52 | ('AIM', 'AIM'), 53 | ('Gtalk', 'Gtalk/Jabber'), 54 | ('Yahoo', 'Yahoo'), 55 | ) 56 | 57 | class ContactInfoForm(forms.Form): 58 | type = fields.ChoiceField(choices=CONTACT_INFO_TYPES) 59 | value = fields.CharField(max_length=200) 60 | preferred = fields.BooleanField(required=False) 61 | 62 | ContactFormset = formsets.formset_factory(ContactInfoForm) 63 | # Define a formset, which will allow a maximum of 5 contacts, no more: 64 | MaxFiveContactsFormset = formsets.formset_factory(ContactInfoForm, extra=5, max_num=5) 65 | # Define the same formset, with no forms (so we can demo the form template): 66 | EmptyContactFormset = formsets.formset_factory(ContactInfoForm, extra=0) 67 | try: 68 | # Define the same formset, which will require a minimum of 2 contacts, no extra 69 | MinTwoContactsFormset = formsets.formset_factory(ContactInfoForm, extra=0, min_num=2) 70 | except: 71 | pass # django pre 1.7 72 | 73 | ############################################### 74 | ## Plain 'ole Formset with Javascript Widget ## 75 | ############################################### 76 | 77 | class EventForm(forms.Form): 78 | name = fields.CharField(max_length=150, label='display name') 79 | start_date = fields.DateField(widget=AdminDateWidget) 80 | end_date = fields.DateField(widget=AdminDateWidget) 81 | 82 | def _get_media(self): 83 | # The "core.js" file is required by the Admin Date widget, yet for 84 | # some reason, isn't included in the widgets media definition. 85 | # We override "_get_media" because core.js needs to appear BEFORE 86 | # the widget's JS files, and the default order puts the form's 87 | # media AFTER that of its fields. 88 | media = widgets.Media( 89 | js=('%sjs/core.js' % settings.ADMIN_MEDIA_PREFIX,) 90 | ) 91 | media += super(EventForm, self).media 92 | return media 93 | media = property(_get_media) 94 | 95 | EventFormset = formsets.formset_factory(EventForm, extra=2) 96 | 97 | ############################## 98 | ## Using Django-ajax-select ## 99 | ############################## 100 | 101 | from ajax_select.fields import AutoCompleteSelectField 102 | 103 | class AutoCompleteSelectFieldForm(models.ModelForm): 104 | """ 105 | Use the `AutoCompleteSelectField` to replace the default select field. 106 | """ 107 | 108 | product = AutoCompleteSelectField('product') 109 | 110 | class Meta: 111 | model = OrderedItem 112 | fields = '__all__' 113 | 114 | class Media: 115 | js = ('js/jquery.autocomplete.min.js',) 116 | css = { 117 | 'all': ('css/jquery.autocomplete.css',), 118 | } 119 | -------------------------------------------------------------------------------- /demo/example/models.py: -------------------------------------------------------------------------------- 1 | 2 | # Test with inlineformsets and inlinemodelformsets 3 | # Test with fieldsets (admin)? 4 | 5 | from django.db import models 6 | from django.template.defaultfilters import pluralize 7 | from datetime import date 8 | 9 | class Product(models.Model): 10 | name = models.CharField(max_length=150) 11 | 12 | class Meta: 13 | ordering = ('name',) 14 | 15 | def __unicode__(self): 16 | return self.name 17 | 18 | class Order(models.Model): 19 | customer = models.CharField(max_length=150) 20 | date = models.DateField(default=date.today, editable=False) 21 | 22 | def __unicode__(self): 23 | return u"%s's order" % self.customer 24 | 25 | class OrderedItem(models.Model): 26 | order = models.ForeignKey(Order, related_name='ordered_items') 27 | product = models.ForeignKey(Product, related_name='orders') 28 | quantity = models.PositiveSmallIntegerField() 29 | 30 | def __unicode__(self): 31 | return u"%s (%d)" % (self.product, self.quantity) 32 | -------------------------------------------------------------------------------- /demo/example/urls.py: -------------------------------------------------------------------------------- 1 | 2 | from django.conf.urls import patterns, url, include 3 | from example.forms import AutoCompleteOrderedItemForm, OrderedItemForm, ContactFormset, MaxFiveContactsFormset, EmptyContactFormset, EventFormset 4 | from example.forms import AutoCompleteSelectFieldForm 5 | 6 | urlpatterns = patterns('example.views', 7 | url(r'^stacked/$', 'formset', {'formset_class': ContactFormset, 'template': 'example/formset-stacked.html'}, name='example_stacked'), 8 | url(r'^table/$', 'formset', {'formset_class': ContactFormset, 'template': 'example/formset-table.html'}, name='example_table'), 9 | url(r'^form-template/$', 'formset_with_template', {'formset_class': EmptyContactFormset, 'template': 'example/form-template.html'}, name='example_form_template'), 10 | url(r'^admin-widget/$', 'formset', {'formset_class': EventFormset, 'template': 'example/formset-admin-widget.html'}, name='example_admin_widget'), 11 | url(r'^multiple-formsets/$', 'multiple_formsets', {'template': 'example/formset-multiple-formsets.html'}, name='example_multiple_formsets'), 12 | url(r'^inline-formset/$', 'inline_formset', 13 | {'form_class': OrderedItemForm, 'template': 'example/inline-formset.html'}, name='example_inline_formset'), 14 | url(r'^inline-formset-autocomplete/$', 'inline_formset', 15 | {'form_class': AutoCompleteOrderedItemForm, 'template': 'example/inline-formset-autocomplete.html'}, name='example_inline_autocomplete'), 16 | url(r'^inline-formset-ajax-selects/$', 'inline_formset', 17 | {'form_class': AutoCompleteSelectFieldForm, 'template': 'example/inline-formset-django-ajax-select.html'}, name='example_inline_ajax_selects'), 18 | url(r'^autocomplete-products/$', 'autocomplete_products', name='example_autocomplete_products') 19 | ) 20 | 21 | import django 22 | major, minor = django.VERSION[:2] 23 | if major >= 1 and minor >= 2: 24 | # These examples require Django 1.2 and above: 25 | urlpatterns += patterns('example.views', 26 | url(r'^max-forms/$', 'formset', {'formset_class': MaxFiveContactsFormset, 'template': 'example/max-forms.html'}, name='example_max_forms'), 27 | url(r'^empty-form/$', 'formset', {'formset_class': EmptyContactFormset, 'template': 'example/empty-form.html'}, name='example_empty_form'), 28 | ) 29 | 30 | if major >=1 and minor >= 7: 31 | from example.forms import MinTwoContactsFormset 32 | # These examples require Django 1.7 and above: 33 | urlpatterns += patterns('example.views', 34 | url(r'^min-forms/$', 'formset', {'formset_class': MinTwoContactsFormset, 'template': 'example/min-forms.html'}, name='example_min_forms'), 35 | ) -------------------------------------------------------------------------------- /demo/example/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.shortcuts import render_to_response, redirect 3 | from django.template.context import RequestContext 4 | from example.forms import ContactFormset, EventFormset, Order, OrderForm, get_ordereditem_formset 5 | from example.forms import AutoCompleteSelectFieldForm 6 | from example.models import Order, Product 7 | 8 | def autocomplete_products(request): 9 | q = request.GET.get('q', '') 10 | products = Product.objects.filter(name__icontains=q).values_list('pk', 'name') 11 | output = u'\n'.join([u'%d|%s' % tuple(product) for product in products]) 12 | return HttpResponse(output, mimetype='text/plain') 13 | 14 | def display_data(request, data, **kwargs): 15 | return render_to_response('example/posted-data.html', dict(data=data, **kwargs), 16 | context_instance=RequestContext(request)) 17 | 18 | def formset(request, formset_class, template): 19 | if request.method == 'POST': 20 | formset = formset_class(request.POST) 21 | if formset.is_valid(): 22 | data = formset.cleaned_data 23 | return display_data(request, data) 24 | else: 25 | formset = formset_class() 26 | return render_to_response(template, {'formset': formset}, 27 | context_instance=RequestContext(request)) 28 | 29 | def formset_with_template(request, formset_class, template): 30 | # If you're using a Django version older than 1.2, you won't have `formset.empty_form`; 31 | # You can create your own "empty form" instance, and even initialize it with default data. 32 | # Make sure to set the prefix as shown, so things work as expected: 33 | formset = formset_class() 34 | form = formset.form( 35 | prefix='%s-__prefix__' % formset.prefix, 36 | initial={ 37 | 'type': 'Email', 38 | 'value': 'john.Q@public.net'}) 39 | if request.method == 'POST': 40 | formset = formset_class(request.POST) 41 | if formset.is_valid(): 42 | data = formset.cleaned_data 43 | return display_data(request, data) 44 | return render_to_response(template, {'form': form, 'formset': formset}, 45 | context_instance=RequestContext(request)) 46 | 47 | def inline_formset(request, form_class, template): 48 | OrderedItemFormset = get_ordereditem_formset(form_class, extra=1, can_delete=True) 49 | order = Order.objects.all()[0] 50 | if request.method == 'POST': 51 | form = OrderForm(request.POST, instance=order) 52 | formset = OrderedItemFormset(request.POST, instance=order) 53 | if form.is_valid() and formset.is_valid(): 54 | form.save() 55 | formset.save() 56 | data = [{ 57 | 'order': item.order, 58 | 'product': item.product, 59 | 'quantity': item.quantity 60 | } for item in order.ordered_items.all()] 61 | return display_data(request, data) 62 | else: 63 | form = OrderForm(instance=order) 64 | formset = OrderedItemFormset(instance=order) 65 | return render_to_response(template, {'form': form, 'formset': formset}, 66 | context_instance=RequestContext(request)) 67 | 68 | def multiple_formsets(request, template): 69 | if request.method == 'POST': 70 | contact_formset, event_formset = ContactFormset(request.POST, prefix='contact_form'), EventFormset(request.POST, prefix='event_form') 71 | if contact_formset.is_valid() and event_formset.is_valid(): 72 | data = [contact_formset.cleaned_data, event_formset.cleaned_data] 73 | return display_data(request, data, multiple_formsets=True) 74 | else: 75 | contact_formset, event_formset = ContactFormset(prefix='contact_form'), EventFormset(prefix='event_form') 76 | return render_to_response(template, {'contact_formset': contact_formset, 'event_formset': event_formset}, 77 | context_instance=RequestContext(request)) 78 | -------------------------------------------------------------------------------- /demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | if __name__ == "__main__": 4 | import os 5 | import sys 6 | from django.core.management import execute_from_command_line 7 | 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') 9 | execute_from_command_line(sys.argv) 10 | 11 | -------------------------------------------------------------------------------- /demo/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.8 2 | django-ajax-selects==1.3.5 3 | -------------------------------------------------------------------------------- /demo/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for demo project. 2 | import os 3 | 4 | PROJECT_DIR = os.path.normpath(os.path.dirname(__file__)) 5 | 6 | DEBUG = True 7 | TEMPLATE_DEBUG = DEBUG 8 | 9 | ADMINS = ( 10 | # ('Your Name', 'your_email@domain.com'), 11 | ) 12 | 13 | MANAGERS = ADMINS 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.sqlite3', 18 | 'NAME': 'demo.sqlite', 19 | } 20 | } 21 | 22 | # Local time zone for this installation. Choices can be found here: 23 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 24 | # although not all choices may be available on all operating systems. 25 | # If running in a Windows environment this must be set to the same as your 26 | # system time zone. 27 | TIME_ZONE = 'Africa/Lagos' 28 | 29 | # Language code for this installation. All choices can be found here: 30 | # http://www.i18nguy.com/unicode/language-identifiers.html 31 | LANGUAGE_CODE = 'en-us' 32 | 33 | SITE_ID = 1 34 | 35 | # If you set this to False, Django will make some optimizations so as not 36 | # to load the internationalization machinery. 37 | USE_I18N = False 38 | 39 | # Absolute path to the directory that holds media. 40 | # Example: "/home/media/media.lawrence.com/" 41 | MEDIA_ROOT = os.path.join(PROJECT_DIR, 'static') 42 | 43 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 44 | # trailing slash if there is a path component (optional in other cases). 45 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 46 | MEDIA_URL = '/static/' 47 | 48 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 49 | # trailing slash. 50 | # Examples: "http://foo.com/media/", "/media/". 51 | ADMIN_MEDIA_PREFIX = '/media/' 52 | 53 | # Make this unique, and don't share it with anybody. 54 | SECRET_KEY = 's2r2k*nqosri4%)5c8w^--jpv+8tzrvbzr11p-frp9kq5ot13v' 55 | 56 | # List of callables that know how to import templates from various sources. 57 | TEMPLATE_LOADERS = ( 58 | 'django.template.loaders.filesystem.Loader', 59 | 'django.template.loaders.app_directories.Loader', 60 | ) 61 | 62 | MIDDLEWARE_CLASSES = ( 63 | 'django.middleware.common.CommonMiddleware', 64 | 'django.contrib.sessions.middleware.SessionMiddleware', 65 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 66 | ) 67 | 68 | ROOT_URLCONF = 'urls' 69 | 70 | TEMPLATE_DIRS = ( 71 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 72 | # Always use forward slashes, even on Windows. 73 | # Don't forget to use absolute paths, not relative paths. 74 | os.path.join(PROJECT_DIR, 'templates'), 75 | ) 76 | 77 | TEMPLATE_CONTEXT_PROCESSORS = ( 78 | 'django.contrib.auth.context_processors.auth', 79 | 'django.core.context_processors.media', 80 | #'django.core.context_processors.i18n', 81 | #'django.core.context_processors.request', 82 | #'django.core.context_processors.debug', 83 | ) 84 | 85 | INSTALLED_APPS = ( 86 | 'django.contrib.auth', 87 | 'django.contrib.contenttypes', 88 | 'django.contrib.sessions', 89 | 'django.contrib.sites', 90 | #'django.contrib.admin', 91 | 'example', 92 | 'ajax_select', 93 | ) 94 | 95 | AJAX_LOOKUP_CHANNELS = { 96 | 'product': { 97 | 'model': 'example.product', 98 | 'search_field': 'name', 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /demo/static/css/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | Design by Free CSS Templates 3 | http://www.freecsstemplates.org 4 | Released for free under a Creative Commons Attribution 2.5 License 5 | */ 6 | 7 | body { 8 | margin: 0; 9 | padding: 0; 10 | background: #FFFFFF url(../images/bg01.jpg) repeat-x top left; 11 | font-size: 13px; 12 | color: #7F7F7F; 13 | } 14 | 15 | body, th, td, input, textarea, select, option { 16 | font-weight: normal; 17 | font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; 18 | } 19 | 20 | h1, h2, h3 { 21 | font-weight: normal; 22 | color: #484848; 23 | } 24 | 25 | h1 { 26 | text-transform: lowercase; 27 | letter-spacing: -1px; 28 | font-size: 24px; 29 | } 30 | 31 | h2 { 32 | text-transform: lowercase; 33 | letter-spacing: -1px; 34 | font-size: 24px; 35 | } 36 | 37 | h3 { 38 | font-size: 1em; 39 | } 40 | 41 | p, ul, ol { 42 | line-height: 200%; 43 | } 44 | p.notice { 45 | line-height: normal; 46 | color: #003300; 47 | font-weight: bold; 48 | } 49 | 50 | blockquote { 51 | padding-left: 1em; 52 | } 53 | 54 | blockquote p, blockquote ul, blockquote ol { 55 | line-height: normal; 56 | font-style: italic; 57 | } 58 | 59 | a { 60 | color: #7ACE11; 61 | } 62 | 63 | a:hover { 64 | text-decoration: none; 65 | color: #7ACE11; 66 | } 67 | 68 | /* Comments */ 69 | 70 | .comment-header { 71 | padding-left: 10px; 72 | padding-right: 10px; 73 | background-color: #f6f6f6; 74 | border-bottom: 1px solid #ddd; 75 | font-style: italic; 76 | line-height: 250%; 77 | } 78 | .comment-header a { 79 | float: right; 80 | clear: right; 81 | } 82 | 83 | /* Header */ 84 | 85 | #header { 86 | width: 890px; 87 | height: 190px; 88 | margin: 0px auto; 89 | } 90 | 91 | /* Logo */ 92 | 93 | #logo { 94 | float: left; 95 | padding: 40px 0 0 0; 96 | } 97 | 98 | #logo h1 { 99 | margin: 0; 100 | text-transform: lowercase; 101 | letter-spacing: -2px; 102 | font-size: 3.6em; 103 | font-weight: normal; 104 | color: #FFFFFF; 105 | } 106 | 107 | #logo h1 a { 108 | padding-right: 20px; 109 | text-decoration: none; 110 | color: #FFFFFF; 111 | } 112 | 113 | #logo p { 114 | margin: -5px 0 0 0; 115 | text-transform: uppercase; 116 | font-size: 1.22em; 117 | letter-spacing: -1px; 118 | } 119 | 120 | #logo a { 121 | text-decoration: none; 122 | color: #FFFFFF; 123 | } 124 | 125 | /* Menu */ 126 | 127 | #menu { 128 | float: right; 129 | } 130 | 131 | #menu ul { 132 | margin: 0px; 133 | padding: 93px 0px 0px 0px; 134 | list-style: none; 135 | } 136 | 137 | #menu li { 138 | display: inline; 139 | } 140 | 141 | #menu a { 142 | display: block; 143 | float: left; 144 | margin-left: 20px; 145 | text-decoration: none; 146 | text-transform: lowercase; 147 | font-size: 1.6em; 148 | color: #FFFFFF; 149 | } 150 | 151 | #menu a:hover, .active a { 152 | border-bottom: 3px solid #FFFFFF; 153 | } 154 | 155 | 156 | /* Page */ 157 | 158 | #page { 159 | width: 890px; 160 | margin: 0 auto; 161 | } 162 | 163 | /* Content */ 164 | 165 | #content { 166 | float: left; 167 | width: 590px; 168 | } 169 | 170 | .post { 171 | padding: 20px 20px; 172 | background: url(../images/bg04.jpg) no-repeat top left; 173 | } 174 | 175 | .title { 176 | margin: 0; 177 | border-bottom: 1px solid #484848; 178 | color: #484848; 179 | } 180 | 181 | .byline { 182 | margin: 0; 183 | color: #D79B00; 184 | } 185 | 186 | .meta { 187 | text-align: left; 188 | color: #646464; 189 | } 190 | 191 | .meta .edit { 192 | padding-left: 20px; 193 | background: url(../images/edit.png) no-repeat left center; 194 | } 195 | 196 | .meta .more { 197 | padding-left: 20px; 198 | background: url(../images/doc.png) no-repeat left center; 199 | } 200 | 201 | .meta .comments { 202 | padding-left: 20px; 203 | background: url(../images/comments.png) no-repeat left center; 204 | } 205 | 206 | strong { 207 | font-weight: bolder; 208 | } 209 | 210 | /* Sidebar */ 211 | 212 | #sidebar { 213 | float: right; 214 | width: 240px; 215 | } 216 | 217 | #sidebar ul { 218 | margin: 0; 219 | padding: 0; 220 | list-style: none; 221 | } 222 | 223 | #sidebar li { 224 | } 225 | 226 | #sidebar li ul { 227 | padding: 15px 0; 228 | } 229 | 230 | #sidebar li li { 231 | padding-left: 20px; 232 | border-bottom: 1px dotted #7B9418; 233 | } 234 | 235 | #sidebar h2 { 236 | margin: 0; 237 | padding: 5px 0 0 20px; 238 | background: url(../images/img06.jpg) no-repeat left 80%; 239 | } 240 | #sidebar h2.user { 241 | margin-bottom: 20px; 242 | font-size: 20px; 243 | text-transform: none; 244 | } 245 | 246 | #sidebar a { 247 | text-decoration: none; 248 | } 249 | 250 | #sidebar a:hover { 251 | } 252 | 253 | a.download { 254 | display: block; 255 | padding-left: 85px; 256 | background: url(../images/download.png) 0 50% no-repeat; 257 | line-height: 80px; 258 | font-size: 22px; 259 | text-decoration: none; 260 | } 261 | 262 | /* Footer */ 263 | 264 | #footer { 265 | clear: both; 266 | margin: 0px; 267 | height: 80px; 268 | background: #F2F2F2 url(../images/bg02.jpg) repeat-x left top; 269 | } 270 | 271 | #footer p { 272 | padding: 20px 0; 273 | text-align: center; 274 | font-size: smaller; 275 | color: #717171; 276 | } 277 | 278 | /* Forms */ 279 | 280 | form { 281 | margin-left: auto; 282 | margin-right: auto; 283 | width: 90%; 284 | } 285 | form input[type="text"], 286 | form input[type="password"] { 287 | width: 200px; 288 | } 289 | form textarea { 290 | display: block; 291 | width: 100%; 292 | height: 160px; 293 | } 294 | form select { 295 | display: block; 296 | } 297 | form ul { 298 | list-style: none; 299 | margin-top: 0; 300 | margin-left: 0; 301 | } 302 | 303 | /* Calendar */ 304 | 305 | .calendar { 306 | margin-left: auto; 307 | margin-right: auto; 308 | margin-bottom: 20px; 309 | width: 235px; 310 | } 311 | .calendar table caption { 312 | font-size: 22px; 313 | text-transform: lowercase; 314 | color: #484848; 315 | } 316 | .calendar table { 317 | margin-bottom: 0; 318 | width: 235px; 319 | } 320 | .calendar th, .calendar td { 321 | text-align: center; 322 | } 323 | .calendar th { 324 | color: #484848; 325 | } 326 | .calendar td.wkend { 327 | color: #D79B00; 328 | } 329 | .calendar td a { 330 | display: block; 331 | background-color: #484848; 332 | color: #fff; 333 | } 334 | .calendar td a:hover { 335 | background-color: #7ACE11; 336 | } 337 | .previous-month { 338 | float: left; 339 | } 340 | .next-month { 341 | float: right; 342 | } 343 | -------------------------------------------------------------------------------- /demo/static/css/jquery.autocomplete.css: -------------------------------------------------------------------------------- 1 | .ac_results { 2 | padding: 0px; 3 | border: 1px solid black; 4 | background-color: white; 5 | overflow: hidden; 6 | z-index: 99999; 7 | } 8 | 9 | .ac_results ul { 10 | width: 100%; 11 | list-style-position: outside; 12 | list-style: none; 13 | padding: 0; 14 | margin: 0; 15 | } 16 | 17 | .ac_results li { 18 | margin: 0px; 19 | padding: 2px 5px; 20 | cursor: default; 21 | display: block; 22 | /* 23 | if width will be 100% horizontal scrollbar will apear 24 | when scroll mode will be used 25 | */ 26 | /*width: 100%;*/ 27 | font: menu; 28 | font-size: 12px; 29 | /* 30 | it is very important, if line-height not setted or setted 31 | in relative units scroll will be broken in firefox 32 | */ 33 | line-height: 16px; 34 | overflow: hidden; 35 | } 36 | 37 | .ac_loading { 38 | background: white url('indicator.gif') right center no-repeat; 39 | } 40 | 41 | .ac_odd { 42 | background-color: #eee; 43 | } 44 | 45 | .ac_over { 46 | background-color: #0A246A; 47 | color: white; 48 | } 49 | -------------------------------------------------------------------------------- /demo/static/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/add.png -------------------------------------------------------------------------------- /demo/static/images/bg01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/bg01.jpg -------------------------------------------------------------------------------- /demo/static/images/bg02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/bg02.jpg -------------------------------------------------------------------------------- /demo/static/images/bg03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/bg03.jpg -------------------------------------------------------------------------------- /demo/static/images/bg04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/bg04.jpg -------------------------------------------------------------------------------- /demo/static/images/comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/comments.png -------------------------------------------------------------------------------- /demo/static/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/delete.png -------------------------------------------------------------------------------- /demo/static/images/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/doc.png -------------------------------------------------------------------------------- /demo/static/images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/edit.png -------------------------------------------------------------------------------- /demo/static/images/img02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/img02.gif -------------------------------------------------------------------------------- /demo/static/images/img05.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/img05.gif -------------------------------------------------------------------------------- /demo/static/images/img06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/img06.jpg -------------------------------------------------------------------------------- /demo/static/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/loading.gif -------------------------------------------------------------------------------- /demo/static/images/spacer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/38475fb944deb6a0fbcccf5b753adad0bba3261a/demo/static/images/spacer.gif -------------------------------------------------------------------------------- /demo/static/js/autocomplete-init.js: -------------------------------------------------------------------------------- 1 | 2 | function makeAutocomplete(sel) { 3 | sel.autocomplete('/examples/autocomplete-products/', { 4 | delay: 200, 5 | formatItem: function(row) { 6 | return row[1]; 7 | } 8 | }); 9 | } 10 | 11 | $(function() { 12 | makeAutocomplete($('.autocomplete-me')); 13 | }) 14 | -------------------------------------------------------------------------------- /demo/static/js/jquery.autocomplete.min.js: -------------------------------------------------------------------------------- 1 | ;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);} 2 | break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);} 3 | break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);} 4 | break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);} 5 | break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;} 6 | break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;} 11 | v+=options.multipleSeparator;} 12 | $input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;} 13 | function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;} 14 | var currentValue=$input.val();if(!skipPrevCheck&¤tValue==previousValue) 15 | return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase) 16 | currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];} 17 | var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value)) 18 | result[i]=$.trim(value);});return result;} 19 | function lastWord(value){if(!options.multiple) 20 | return value;var words=trimWords(value);return words[words.length-1];} 21 | function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));} 22 | else 23 | $input.val("");}});} 24 | if(wasVisible) 25 | $.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase) 26 | term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"$1");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase) 28 | s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();} 29 | if(!data[q]){length++;} 30 | data[q]=value;} 31 | function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}} 37 | return csub;}else 38 | if(data[q]){return data[q];}else 39 | if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}} 40 | return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit) 41 | return;element=$("
").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("
    ").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0) 42 | element.css("width",options.width);needsInit=false;} 43 | function target(event){var element=event.target;while(element&&element.tagName!="LI") 44 | element=element.parentNode;if(!element) 45 | return[];return element;} 46 | function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset=listItems.size()){active=0;}} 47 | function limitNumberOfItems(available){return options.max&&options.max").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);} 51 | listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;} 52 | if($.fn.bgiframe) 53 | list.bgiframe();} 54 | return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}} 55 | field.focus();};})(jQuery); -------------------------------------------------------------------------------- /demo/static/js/jquery.formset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery Formset 1.3-pre 3 | * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) 4 | * @requires jQuery 1.2.6 or later 5 | * 6 | * Copyright (c) 2009, Stanislaus Madueke 7 | * All rights reserved. 8 | * 9 | * Licensed under the New BSD License 10 | * See: http://www.opensource.org/licenses/bsd-license.php 11 | */ 12 | ;(function($) { 13 | $.fn.formset = function(opts) 14 | { 15 | var options = $.extend({}, $.fn.formset.defaults, opts), 16 | flatExtraClasses = options.extraClasses.join(' '), 17 | totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'), 18 | maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'), 19 | minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'), 20 | childElementSelector = 'input,select,textarea,label,div', 21 | $$ = $(this), 22 | 23 | applyExtraClasses = function(row, ndx) { 24 | if (options.extraClasses) { 25 | row.removeClass(flatExtraClasses); 26 | row.addClass(options.extraClasses[ndx % options.extraClasses.length]); 27 | } 28 | }, 29 | 30 | updateElementIndex = function(elem, prefix, ndx) { 31 | var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'), 32 | replacement = prefix + '-' + ndx + '-'; 33 | if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement)); 34 | if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)); 35 | if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)); 36 | }, 37 | 38 | hasChildElements = function(row) { 39 | return row.find(childElementSelector).length > 0; 40 | }, 41 | 42 | showAddButton = function() { 43 | return maxForms.length == 0 || // For Django versions pre 1.2 44 | (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0)); 45 | }, 46 | 47 | /** 48 | * Indicates whether delete link(s) can be displayed - when total forms > min forms 49 | */ 50 | showDeleteLinks = function() { 51 | return minForms.length == 0 || // For Django versions pre 1.7 52 | (minForms.val() == '' || (totalForms.val() - minForms.val() > 0)); 53 | }, 54 | 55 | insertDeleteLink = function(row) { 56 | var delCssSelector = options.deleteCssClass.trim().replace(/\s+/g, '.'), 57 | addCssSelector = options.addCssClass.trim().replace(/\s+/g, '.'); 58 | if (row.is('TR')) { 59 | // If the forms are laid out in table rows, insert 60 | // the remove button into the last table cell: 61 | row.children(':last').append('' + options.deleteText + ''); 62 | } else if (row.is('UL') || row.is('OL')) { 63 | // If they're laid out as an ordered/unordered list, 64 | // insert an
  • after the last list item: 65 | row.append('
  • ' + options.deleteText +'
  • '); 66 | } else { 67 | // Otherwise, just insert the remove button as the 68 | // last child element of the form's container: 69 | row.append('' + options.deleteText +''); 70 | } 71 | // Check if we're under the minimum number of forms - not to display delete link at rendering 72 | if (!showDeleteLinks()){ 73 | row.find('a.' + delCssSelector).hide(); 74 | } 75 | 76 | row.find('a.' + delCssSelector).click(function() { 77 | var row = $(this).parents('.' + options.formCssClass), 78 | del = row.find('input:hidden[id $= "-DELETE"]'), 79 | buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'), 80 | forms; 81 | if (del.length) { 82 | // We're dealing with an inline formset. 83 | // Rather than remove this form from the DOM, we'll mark it as deleted 84 | // and hide it, then let Django handle the deleting: 85 | del.val('on'); 86 | row.hide(); 87 | forms = $('.' + options.formCssClass).not(':hidden'); 88 | } else { 89 | row.remove(); 90 | // Update the TOTAL_FORMS count: 91 | forms = $('.' + options.formCssClass).not('.formset-custom-template'); 92 | totalForms.val(forms.length); 93 | } 94 | for (var i=0, formCount=forms.length; i