+++ /dev/null
-[run]
-source = selectable
-omit = */tests*,*/urls.py
+++ /dev/null
-[main]
-host = https://www.transifex.com
-
-[django-selectable.txo]
-file_filter = selectable/locale/<lang>/LC_MESSAGES/django.po
-source_file = selectable/locale/en/LC_MESSAGES/django.po
-source_lang = en
-type = PO
+++ /dev/null
-Primary author:
-
-Mark Lavin
-
-The following people who have contributed to django-selectable:
-
-Michael Manfre
-Luke Plant
-Augusto Men
-@dc
-Colin Copeland
-Sławomir Ehlert
-Dan Poirier
-Felipe Prenholato
-David Ray
-Rick Testore
-Karen Tracey
-Manuel Alvarez
-Ustun Ozgur
-@leo-the-manic
-Calvin Spealman
-Rebecca Lovewell
-Thomas Güttler
-Yuri Khrustalev
-@SaeX
-Tam Huynh
-Raphael Merx
-Josh Addington
-Tobias Zanke
-Petr Dlouhy
-Vinod Kurup
-
-Thanks for all of your work!
+++ /dev/null
-Copyright (c) 2010-201999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999, Mark Lavin
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice,
-this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-this list of conditions and the following disclaimer in the documentation
-and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+++ /dev/null
-include AUTHORS.txt
-include README.rst
-include LICENSE.txt
-recursive-include selectable/locale *
-recursive-include selectable/static *
-recursive-include selectable/templates *
+++ /dev/null
-STATIC_DIR = ./selectable/static/selectable
-QUNIT_TESTS = file://`pwd`/selectable/tests/qunit/index.html
-
-test-js:
- # Run JS tests
- # Requires phantomjs
- phantomjs run-qunit.js ${QUNIT_TESTS}?jquery=1.11.2&ui=1.11.4
- phantomjs run-qunit.js ${QUNIT_TESTS}?jquery=1.11.2&ui=1.10.4
- phantomjs run-qunit.js ${QUNIT_TESTS}?jquery=1.10.2&ui=1.11.4
- phantomjs run-qunit.js ${QUNIT_TESTS}?jquery=1.10.2&ui=1.10.4
- phantomjs run-qunit.js ${QUNIT_TESTS}?jquery=1.9.1&ui=1.11.4
- phantomjs run-qunit.js ${QUNIT_TESTS}?jquery=1.9.1&ui=1.10.4
-
-
-lint-js:
- # Check JS for any problems
- # Requires jshint
- jshint ${STATIC_DIR}/js/jquery.dj.selectable.js
-
-
-.PHONY: lint-js test-js
+++ /dev/null
-django-selectable
-===================
-
-Tools and widgets for using/creating auto-complete selection widgets using Django and jQuery UI.
-
-.. image:: https://travis-ci.org/mlavin/django-selectable.svg?branch=master
- :target: https://travis-ci.org/mlavin/django-selectable
-
-.. image:: https://codecov.io/github/mlavin/django-selectable/coverage.svg?branch=master
- :target: https://codecov.io/github/mlavin/django-selectable?branch=master
-
-
-.. note::
-
- This project is looking for additional maintainers to help with Django/jQuery compatibility
- issues as well as addressing support issues/questions. If you are looking to help out
- on this project and take a look at the open
- `help-wanted <https://github.com/mlavin/django-selectable/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted>`_
- or `question <https://github.com/mlavin/django-selectable/issues?q=is%3Aissue+is%3Aopen+label%3Aquestion>`_
- and see if you can contribute a fix. Be bold! If you want to take a larger role on
- the project, please reach out on the
- `mailing list <http://groups.google.com/group/django-selectable>`_. I'm happy to work
- with you to get you going on an issue.
-
-
-Features
------------------------------------
-
-- Works with the latest jQuery UI Autocomplete library
-- Auto-discovery/registration pattern for defining lookups
-
-
-Installation Requirements
------------------------------------
-
-- Python 2.7, 3.4+
-- `Django <http://www.djangoproject.com/>`_ >= 1.11, <= 3.0
-- `jQuery <http://jquery.com/>`_ >= 1.9, < 3.0
-- `jQuery UI <http://jqueryui.com/>`_ >= 1.10
-
-To install::
-
- pip install django-selectable
-
-Next add `selectable` to your `INSTALLED_APPS` to include the related css/js::
-
- INSTALLED_APPS = (
- 'contrib.staticfiles',
- # Other apps here
- 'selectable',
- )
-
-The jQuery and jQuery UI libraries are not included in the distribution but must be included
-in your templates. See the example project for an example using these libraries from the
-Google CDN.
-
-Once installed you should add the urls to your root url patterns::
-
- urlpatterns = [
- # Other patterns go here
- url(r'^selectable/', include('selectable.urls')),
- ]
-
-
-Documentation
------------------------------------
-
-Documentation for django-selectable is available on `Read The Docs <http://django-selectable.readthedocs.io/en/latest/>`_.
-
-
-Additional Help/Support
------------------------------------
-
-You can find additional help or support on the mailing list: http://groups.google.com/group/django-selectable
-
-
-Contributing
---------------------------------------
-
-If you think you've found a bug or are interested in contributing to this project
-check out our `contributing guide <http://readthedocs.org/docs/django-selectable/en/latest/contribute.html>`_.
-
-If you are interested in translating django-selectable into your native language
-you can join the `Transifex project <https://www.transifex.com/projects/p/django-selectable/>`_.
+++ /dev/null
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Django-Selectable.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Django-Selectable.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/Django-Selectable"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Django-Selectable"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- make -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
+++ /dev/null
-Admin Integration
-====================
-
-Overview
---------------------------------------
-
-Django-Selectables will work in the admin. To get started on integrated the
-fields and widgets in the admin make sure you are familiar with the Django
-documentation on the `ModelAdmin.form <http://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form>`_
-and `ModelForms <http://docs.djangoproject.com/en/stable/topics/forms/modelforms/>`_ particularly
-on `overriding the default widgets <http://docs.djangoproject.com/en/stable/topics/forms/modelforms/#overriding-the-default-field-types-or-widgets>`_.
-As you will see integrating django-selectable in the adminis the same as working with regular forms.
-
-
-.. _admin-jquery-include:
-
-Including jQuery & jQuery UI
---------------------------------------
-
-As noted :ref:`in the quick start guide <start-include-jquery>`, the jQuery and jQuery UI libraries
-are not included in the distribution but must be included in your templates. For the
-Django admin that means overriding
-`admin/base_site.html <https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/templates/admin/base_site.html>`_.
-You can include this media in the block name `extrahead` which is defined in
-`admin/base.html <https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/templates/admin/base.html>`_.
-
- .. code-block:: html
-
- {% block extrahead %}
- {% load selectable_tags %}
- {% include_ui_theme %}
- {% include_jquery_libs %}
- {{ block.super }}
- {% endblock %}
-
-See the Django documentation on
-`overriding admin templates <https://docs.djangoproject.com/en/stable/ref/contrib/admin/#overriding-admin-templates>`_.
-See the example project for the full template example.
-
-
-.. _admin-grappelli:
-
-Using Grappelli
---------------------------------------
-
-`Grappelli <https://django-grappelli.readthedocs.org>`_ is a popular customization of the Django
-admin interface. It includes a number of interface improvements which are also built on top of
-jQuery UI. When using Grappelli you do not need to make any changes to the ``admin/base_site.html``
-template. django-selectable will detect jQuery and jQuery UI versions included by Grappelli
-and make use of them.
-
-
-.. _admin-basic-example:
-
-Basic Example
---------------------------------------
-
-For example, we may have a ``Farm`` model with a foreign key to ``auth.User`` and
-a many to many relation to our ``Fruit`` model.
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from django.db import models
- from django.utils.encoding import python_2_unicode_compatible
-
-
- @python_2_unicode_compatible
- class Fruit(models.Model):
- name = models.CharField(max_length=200)
-
- def __str__(self):
- return self.name
-
-
- @python_2_unicode_compatible
- class Farm(models.Model):
- name = models.CharField(max_length=200)
- owner = models.ForeignKey('auth.User', related_name='farms', on_delete=models.CASCADE)
- fruit = models.ManyToManyField(Fruit)
-
- def __str__(self):
- return "%s's Farm: %s" % (self.owner.username, self.name)
-
-In `admin.py` we will define the form and associate it with the `FarmAdmin`.
-
- .. code-block:: python
-
- from django.contrib import admin
- from django.contrib.auth.admin import UserAdmin
- from django.contrib.auth.models import User
- from django import forms
-
- from selectable.forms import AutoCompleteSelectField, AutoCompleteSelectMultipleWidget
-
- from .models import Fruit, Farm
- from .lookups import FruitLookup, OwnerLookup
-
-
- class FarmAdminForm(forms.ModelForm):
- owner = AutoCompleteSelectField(lookup_class=OwnerLookup, allow_new=True)
-
- class Meta(object):
- model = Farm
- widgets = {
- 'fruit': AutoCompleteSelectMultipleWidget(lookup_class=FruitLookup),
- }
- exclude = ('owner', )
-
- def __init__(self, *args, **kwargs):
- super(FarmAdminForm, self).__init__(*args, **kwargs)
- if self.instance and self.instance.pk and self.instance.owner:
- self.initial['owner'] = self.instance.owner.pk
-
- def save(self, *args, **kwargs):
- owner = self.cleaned_data['owner']
- if owner and not owner.pk:
- owner = User.objects.create_user(username=owner.username, email='')
- self.instance.owner = owner
- return super(FarmAdminForm, self).save(*args, **kwargs)
-
-
- class FarmAdmin(admin.ModelAdmin):
- form = FarmAdminForm
-
-
- admin.site.register(Farm, FarmAdmin)
-
-
-You'll note this form also allows new users to be created and associated with the
-farm, if no user is found matching the given name. To make use of this feature we
-need to add ``owner`` to the exclude so that it will pass model validation. Unfortunately
-that means we must set the owner manual in the save and in the initial data because
-the ``ModelForm`` will no longer do this for you. Since ``fruit`` does not allow new
-items you'll see these steps are not necessary.
-
-The django-selectable widgets are compatitible with the add another popup in the
-admin. It's that little green plus sign that appears next to ``ForeignKey`` or
-``ManyToManyField`` items. This makes django-selectable a user friendly replacement
-for the `ModelAdmin.raw_id_fields <https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields>`_
-when the default select box grows too long.
-
-
-.. _admin-inline-example:
-
-Inline Example
---------------------------------------
-
-With our ``Farm`` model we can also associate the ``UserAdmin`` with a ``Farm``
-by making use of the `InlineModelAdmin
-<http://docs.djangoproject.com/en/stable/ref/contrib/admin/#inlinemodeladmin-objects>`_.
-We can even make use of the same ``FarmAdminForm``.
-
- .. code-block:: python
-
- # continued from above
-
- class FarmInline(admin.TabularInline):
- model = Farm
- form = FarmAdminForm
-
-
- class NewUserAdmin(UserAdmin):
- inlines = [
- FarmInline,
- ]
-
-
- admin.site.unregister(User)
- admin.site.register(User, NewUserAdmin)
-
-The auto-complete functions will be bound as new forms are added dynamically.
+++ /dev/null
-Advanced Usage
-==========================
-
-We've gone through the most command and simple use cases for django-selectable. Now
-we'll take a look at some of the more advanced features of this project. This assumes
-that you are comfortable reading and writing a little bit of Javascript making
-use of jQuery.
-
-
-.. _additional-parameters:
-
-Additional Parameters
---------------------------------------
-
-The basic lookup is based on handling a search based on a single term string.
-If additional filtering is needed it can be inside the lookup ``get_query`` but
-you would need to define this when the lookup is defined. While this fits a fair
-number of use cases there are times when you need to define additional query
-parameters that won't be known until either the form is bound or until selections
-are made on the client side. This section will detail how to handle both of these
-cases.
-
-
-How Parameters are Passed
-_______________________________________
-
-As with the search term, the additional parameters you define will be passed in
-``request.GET``. Since ``get_query`` gets the current request, you will have access to
-them. Since they can be manipulated on the client side, these parameters should be
-treated like all user input. It should be properly validated and sanitized.
-
-
-Limiting the Result Set
-_______________________________________
-
-The number of results are globally limited/paginated by the :ref:`SELECTABLE_MAX_LIMIT`
-but you can also lower this limit on the field or widget level. Each field and widget
-takes a ``limit`` argument in the ``__init__`` that will be passed back to the lookup
-through the ``limit`` query parameter. The result set will be automatically paginated
-for you if you use either this parameter or the global setting.
-
-
-.. _server-side-parameters:
-
-Adding Parameters on the Server Side
-_______________________________________
-
-Each of the widgets define ``update_query_parameters`` which takes a dictionary. The
-most common way to use this would be in the form ``__init__``.
-
- .. code-block:: python
-
- class FruitForm(forms.Form):
- autocomplete = forms.CharField(
- label='Type the name of a fruit (AutoCompleteWidget)',
- widget=selectable.AutoCompleteWidget(FruitLookup),
- required=False,
- )
-
- def __init__(self, *args, **kwargs):
- super(FruitForm, self).__init__(*args, **kwargs)
- self.fields['autocomplete'].widget.update_query_parameters({'foo': 'bar'})
-
-You can also pass the query parameters into the widget using the ``query_params``
-keyword argument. It depends on your use case as to whether the parameters are
-known when the form is defined or when an instance of the form is created.
-
-
-.. _client-side-parameters:
-
-Adding Parameters on the Client Side
-_______________________________________
-
-There are times where you want to filter the result set based other selections
-by the user such as a filtering cities by a previously selected state. In this
-case you will need to bind a ``prepareQuery`` to the field. This function should accept the query dictionary.
-You are free to make adjustments to the query dictionary as needed.
-
- .. code-block:: html
-
- <script type="text/javascript">
- function newParameters(query) {
- query.foo = 'bar';
- }
-
- $(document).ready(function() {
- $('#id_autocomplete').djselectable('option', 'prepareQuery', newParameters);
- });
- </script>
-
-.. note::
-
- In v0.7 the scope of ``prepareQuery`` was updated so that ``this`` refers to the
- current ``djselectable`` plugin instance. Previously ``this`` refered to the
- plugin ``options`` instance.
-
-
-.. _chain-select-example:
-
-Chained Selection
---------------------------------------
-
-It's a fairly common pattern to have two or more inputs depend one another such City/State/Zip.
-In fact there are other Django apps dedicated to this purpose such as
-`django-smart-selects <https://github.com/digi604/django-smart-selects>`_ or
-`django-ajax-filtered-fields <http://code.google.com/p/django-ajax-filtered-fields/>`_.
-It's possible to handle this kind of selection with django-selectable if you are willing
-to write a little javascript.
-
-Suppose we have city model
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from django.db import models
- from django.utils.encoding import python_2_unicode_compatible
-
- from localflavor.us.models import USStateField
-
-
- @python_2_unicode_compatible
- class City(models.Model):
- name = models.CharField(max_length=200)
- state = USStateField()
-
- def __str__(self):
- return self.name
-
-Then in our lookup we will grab the state value and filter our results on it:
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import City
-
-
- class CityLookup(ModelLookup):
- model = City
- search_fields = ('name__icontains', )
-
- def get_query(self, request, term):
- results = super(CityLookup, self).get_query(request, term)
- state = request.GET.get('state', '')
- if state:
- results = results.filter(state=state)
- return results
-
- def get_item_label(self, item):
- return "%s, %s" % (item.name, item.state)
-
-
- registry.register(CityLookup)
-
-and a simple form
-
- .. code-block:: python
-
- from django import forms
-
- from localflavor.us.forms import USStateField, USStateSelect
-
- from selectable.forms import AutoCompleteSelectField, AutoComboboxSelectWidget
-
- from .lookups import CityLookup
-
-
- class ChainedForm(forms.Form):
- city = AutoCompleteSelectField(
- lookup_class=CityLookup,
- label='City',
- required=False,
- widget=AutoComboboxSelectWidget
- )
- state = USStateField(widget=USStateSelect, required=False)
-
-
-We want our users to select a city and if they choose a state then we will only
-show them cities in that state. To do this we will pass back chosen state as
-addition parameter with the following javascript:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- function newParameters(query) {
- query.state = $('#id_state').val();
- }
- $('#id_city_0').djselectable('option', 'prepareQuery', newParameters);
- });
- </script>
-
-And that's it! We now have a working chained selection example. The full source
-is included in the example project.
-
-.. _client-side-changes:
-
-Detecting Client Side Changes
-____________________________________________
-
-The previous example detected selection changes on the client side to allow passing
-parameters to the lookup. Since django-selectable is built on top of the jQuery UI
-`Autocomplete plug-in <http://jqueryui.com/demos/autocomplete/>`_, the widgets
-expose the events defined by the plugin.
-
- - djselectablecreate
- - djselectablesearch
- - djselectableopen
- - djselectablefocus
- - djselectableselect
- - djselectableclose
- - djselectablechange
-
-For the most part these event names should be self-explanatory. If you need additional
-detail you should refer to the `jQuery UI docs on these events <http://jqueryui.com/demos/autocomplete/#events>`_.
-
-The multiple select widgets include additional events which indicate when a new item is added
-or removed from the current list. These events are ``djselectableadd`` and ``djselectableremove``.
-These events pass a dictionary of data with the following keys
-
- - element: The original text input
- - input: The hidden input to be added for the new item
- - wrapper: The ``<li>`` element to be added to the deck
- - deck: The outer ``<ul>`` deck element
-
-You can use these events to prevent items from being added or removed from the deck by
-returning ``false`` in the handling function. A simple example is given below:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- $(':input[name=my_field_0]').bind('djselectableadd', function(event, item) {
- // Don't allow foo to be added
- if ($(item.input).val() === 'foo') {
- return false;
- }
- });
- });
- </script>
-
-
-Submit On Selection
---------------------------------------
-
-You might want to help your users by submitting the form once they have selected a valid
-item. To do this you simply need to listen for the ``djselectableselect`` event. This
-event is fired by the text input which has an index of 0. If your field is named ``my_field``
-then input to watch would be ``my_field_0`` such as:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- $(':input[name=my_field_0]').bind('djselectableselect', function(event, ui) {
- $(this).parents("form").submit();
- });
- });
- </script>
-
-
-Dynamically Added Forms
---------------------------------------
-
-django-selectable can work with dynamically added forms such as inlines in the admin.
-To make django-selectable work in the admin there is nothing more to do than include
-the necessary static media as described in the
-:ref:`Admin Integration <admin-jquery-include>` section.
-
-If you are making use of the popular `django-dynamic-formset <http://code.google.com/p/django-dynamic-formset/>`_
-then you can make django-selectable work by passing ``bindSelectables`` to the
-`added <http://code.google.com/p/django-dynamic-formset/source/browse/trunk/docs/usage.txt#259>`_ option:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- $('#my-formset').formset({
- added: bindSelectables
- });
- });
- </script>
-
-Currently you must include the django-selectable javascript below this formset initialization
-code for this to work. See django-selectable `issue #31 <https://github.com/mlavin/django-selectable/issues/31>`_
-for some additional detail on this problem.
-
-
-.. _advanced-label-formats:
-
-Label Formats on the Client Side
---------------------------------------
-
-The lookup label is the text which is shown in the list before it is selected.
-You can use the :ref:`get_item_label <lookup-get-item-label>` method in your lookup
-to do this on the server side. This works for most applications. However if you don't
-want to write your HTML in Python or need to adapt the format on the client side you
-can use the :ref:`formatLabel <javascript-formatLabel>` option.
-
-``formatLabel`` takes two paramaters the current label and the current selected item.
-The item is a dictionary object matching what is returned by the lookup's
-:ref:`format_item <lookup-format-item>`. ``formatLabel`` should return the string
-which should be used for the label.
-
-Going back to the ``CityLookup`` we can adjust the label to wrap the city and state
-portions with their own classes for additional styling:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- function formatLabel(label, item) {
- var data = label.split(',');
- return '<span class="city">' + data[0] + '</span>, <span class="state">' + data[1] + '</span>';
- }
- $('#id_city_0').djselectable('option', 'formatLabel', formatLabel);
- });
- </script>
-
-This is a rather simple example but you could also pass additional information in ``format_item``
-such as a flag of whether the city is the capital and render the state captials differently.
-
-.. _advanced-bootstrap:
-
-Using with Twitter Bootstrap
---------------------------------------
-
-django-selectable can work along side with Twitter Bootstrap but there are a few things to
-take into consideration. Both jQuery UI and Bootstrap define a ``$.button`` plugin. This
-plugin is used by default by django-selectable and expects the UI version. If the jQuery UI
-JS is included after the Bootstrap JS then this will work just fine but the Bootstrap
-button JS will not be available. This is the strategy taken by the `jQuery UI Bootstrap
-<http://addyosmani.github.com/jquery-ui-bootstrap/>`_ theme.
-
-Another option is to rename the Bootstrap plugin using the ``noConflict`` option.
-
- .. code-block:: html
-
- <!-- Include Bootstrap JS -->
- <script>$.fn.bootstrapBtn = $.fn.button.noConflict();</script>
- <!-- Include jQuery UI JS -->
-
-Even with this some might complain that it's too resource heavy to include all of
-jQuery UI when you just want the autocomplete to work with django-selectable. For
-this you can use the `Download Builder <http://jqueryui.com/download/>`_ to build
-a minimal set of jQuery UI widgets. django-selectable requires the UI core, autocomplete,
-menu and button widgets. None of the effects or interactions are needed. Minified
-this totals around 100 kb of JS, CSS and images (based on jQuery UI 1.10).
-
-.. note::
-
- For a comparison this is smaller than the minified Bootstrap 2.3.0 CSS
- which is 105 kb not including the responsive CSS or the icon graphics.
-
-It is possible to remove the dependency on the UI button plugin and instead
-use the Bootstrap button styles. This is done by overriding
-the ``_comboButtonTemplate`` and ``_removeButtonTemplate`` functions used to
-create the buttons. An example is given below.
-
- .. code-block:: html
-
- <script>
- $.ui.djselectable.prototype._comboButtonTemplate = function (input) {
- var icon = $("<i>").addClass("icon-chevron-down");
- // Remove current classes on the text input
- $(input).attr("class", "");
- // Wrap with input-append
- $(input).wrap('<div class="input-append" />');
- // Return button link with the chosen icon
- return $("<a>").append(icon).addClass("btn btn-small");
- };
- $.ui.djselectable.prototype._removeButtonTemplate = function (item) {
- var icon = $("<i>").addClass("icon-remove-sign");
- // Return button link with the chosen icon
- return $("<a>").append(icon).addClass("btn btn-small pull-right");
- };
- </script>
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Django-Selectable documentation build configuration file, created by
-# sphinx-quickstart on Sat Mar 12 14:14:16 2011.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import datetime
-import sys, os
-import selectable
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Django-Selectable'
-copyright = u'2011-%s, Mark Lavin' % datetime.date.today().year
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '.'.join(selectable.__version__.split('.')[0:2])
-# The full version, including alpha/beta/rc tags.
-release = selectable.__version__
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Django-Selectabledoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'Django-Selectable.tex', u'Django-Selectable Documentation',
- u'Mark Lavin', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'django-selectable', u'Django-Selectable Documentation',
- [u'Mark Lavin'], 1)
-]
+++ /dev/null
-.. _contributing-guide:
-
-Contributing
-==================
-
-There are plenty of ways to contribute to this project. If you think you've found
-a bug please submit an issue. If there is a feature you'd like to see then please
-open an ticket proposal for it. If you've come up with some helpful examples then
-you can add to our example project.
-
-
-Getting the Source
---------------------------------------
-
-The source code is hosted on `Github <https://github.com/mlavin/django-selectable>`_.
-You can download the full source by cloning the git repo::
-
- git clone git://github.com/mlavin/django-selectable.git
-
-Feel free to fork the project and make your own changes. If you think that it would
-be helpful for other then please submit a pull request to have it merged in.
-
-
-Submit an Issue
---------------------------------------
-
-The issues are also managed on `Github issue page <https://github.com/mlavin/django-selectable/issues>`_.
-If you think you've found a bug it's helpful if you indicate the version of django-selectable
-you are using the ticket version flag. If you think your bug is javascript related it is
-also helpful to know the version of jQuery, jQuery UI, and the browser you are using.
-
-Issues are also used to track new features. If you have a feature you would like to see
-you can submit a proposal ticket. You can also see features which are planned here.
-
-
-Submit a Translation
---------------------------------------
-
-We are working towards translating django-selectable into different languages. There
-are not many strings to be translated so it is a reasonably easy task and a great way
-to be involved with the project. The translations are managed through
-`Transifex <https://www.transifex.com/projects/p/django-selectable/>`_.
-
-Running the Test Suite
---------------------------------------
-
-There are a number of tests in place to test the server side code for this
-project. To run the tests you need Django and `mock <http://www.voidspace.org.uk/python/mock/>`_
-installed and run::
-
- python runtests.py
-
-`tox <http://tox.readthedocs.org/en/latest/index.html>`_ is used to test django-selectable
-against multiple versions of Django/Python. With tox installed you can run::
-
- tox
-
-to run all the version combinations. You can also run tox against a subset of supported
-environments::
-
- tox -e py27-django15
-
-For more information on running/installing tox please see the
-tox documentation: http://tox.readthedocs.org/en/latest/index.html
-
-Client side tests are written using `QUnit <http://docs.jquery.com/QUnit>`_. They
-can be found in ``selectable/tests/qunit/index.html``. The test suite also uses
-`PhantomJS <http://phantomjs.org/>`_ to
-run the tests. You can install PhantomJS from NPM::
-
- # Install requirements
- npm install -g phantomjs jshint
- make test-js
-
-
-Building the Documentation
---------------------------------------
-
-The documentation is built using `Sphinx <http://sphinx.pocoo.org/>`_
-and available on `Read the Docs <http://django-selectable.readthedocs.io/>`_. With
-Sphinx installed you can build the documentation by running::
-
- make html
-
-inside the docs directory. Documentation fixes and improvements are always welcome.
-
+++ /dev/null
-Fields
-==========
-
-Django-Selectable defines a number of fields for selecting either single or multiple
-lookup items. Item in this context corresponds to the object return by the underlying
-lookup ``get_item``. The single select select field :ref:`AutoCompleteSelectField`
-allows for the creation of new items. To use this feature the field's
-lookup class must define ``create_item``. In the case of lookups extending from
-:ref:`ModelLookup` newly created items have not yet been saved into the database and saving
-should be handled by the form. All fields take the lookup class as the first required
-argument.
-
-
-.. _AutoCompleteSelectField:
-
-AutoCompleteSelectField
---------------------------------------
-
-Field tied to :ref:`AutoCompleteSelectWidget` to bind the selection to the form and
-create new items, if allowed. The ``allow_new`` keyword argument (default: ``False``)
-which determines if the field allows new items. This field cleans to a single item.
-
- .. code-block:: python
-
- from django import forms
-
- from selectable.forms import AutoCompleteSelectField
-
- from .lookups import FruitLookup
-
-
- class FruitSelectionForm(forms.Form):
- fruit = AutoCompleteSelectField(lookup_class=FruitLookup, label='Select a fruit')
-
-`lookup_class`` may also be a dotted path.
-
-
-.. _AutoCompleteSelectMultipleField:
-
-AutoCompleteSelectMultipleField
---------------------------------------
-
-Field tied to :ref:`AutoCompleteSelectMultipleWidget` to bind the selection to the form.
-This field cleans to a list of items. :ref:`AutoCompleteSelectMultipleField` does not
-allow for the creation of new items.
-
-
- .. code-block:: python
-
- from django import forms
-
- from selectable.forms import AutoCompleteSelectMultipleField
-
- from .lookups import FruitLookup
-
-
- class FruitsSelectionForm(forms.Form):
- fruits = AutoCompleteSelectMultipleField(lookup_class=FruitLookup,
- label='Select your favorite fruits')
+++ /dev/null
-.. include:: ../README.rst
-
-Contents:
-
-.. toctree::
- :maxdepth: 2
-
- overview
- quick-start
- lookups
- advanced
- admin
- testing
- fields
- widgets
- settings
- contribute
- releases
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
+++ /dev/null
-Defining Lookups
-==================
-
-What are Lookups?
---------------------------------------
-
-Lookups define the corresponding ajax views used by the auto-completion
-fields and widgets. They take in the current request and return the JSON
-needed by the jQuery auto-complete plugin.
-
-
-Defining a Lookup
---------------------------------------
-
-django-selectable uses a registration pattern similar to the Django admin.
-Lookups should be defined in a `lookups.py` in your application's module. Once defined
-you must register in with django-selectable. All lookups must extend from
-``selectable.base.LookupBase`` which defines the API for every lookup.
-
- .. code-block:: python
-
- from selectable.base import LookupBase
- from selectable.registry import registry
-
- class MyLookup(LookupBase):
- def get_query(self, request, term):
- data = ['Foo', 'Bar']
- return [x for x in data if x.startswith(term)]
-
- registry.register(MyLookup)
-
-
-Lookup API
---------------------------------------
-
-.. py:method:: LookupBase.get_query(request, term)
-
- This is the main method which takes the current request
- from the user and returns the data which matches their search.
-
- :param request: The current request object.
- :param term: The search term from the widget input.
- :return: An iterable set of data of items matching the search term.
-
-.. _lookup-get-item-label:
-
-.. py:method:: LookupBase.get_item_label(item)
-
- This is first of three formatting methods. The label is shown in the
- drop down menu of search results. This defaults to ``item.__unicode__``.
-
- :param item: An item from the search results.
- :return: A string representation of the item to be shown in the search results.
- The label can include HTML. For changing the label format on the client side
- see :ref:`Advanced Label Formats <advanced-label-formats>`.
-
-
-.. py:method:: LookupBase.get_item_id(item)
-
- This is second of three formatting methods. The id is the value that will eventually
- be returned by the field/widget. This defaults to ``item.__unicode__``.
-
- :param item: An item from the search results.
- :return: A string representation of the item to be returned by the field/widget.
-
-
-.. py:method:: LookupBase.split_term(term)
-
- Split searching term into array of subterms that will be searched separately.
- You can override this function to achieve different splitting of the term.
-
- :param term: The search term.
- :return: Array with subterms
-
-.. py:method:: LookupBase.get_item_value(item)
-
- This is last of three formatting methods. The value is shown in the
- input once the item has been selected. This defaults to ``item.__unicode__``.
-
- :param item: An item from the search results.
- :return: A string representation of the item to be shown in the input.
-
-.. py:method:: LookupBase.get_item(value)
-
- ``get_item`` is the reverse of ``get_item_id``. This should take the value
- from the form initial values and return the current item. This defaults
- to simply return the value.
-
- :param value: Value from the form inital value.
- :return: The item corresponding to the initial value.
-
-.. py:method:: LookupBase.create_item(value)
-
- If you plan to use a lookup with a field or widget which allows the user
- to input new values then you must define what it means to create a new item
- for your lookup. By default this raises a ``NotImplemented`` error.
-
- :param value: The user given value.
- :return: The new item created from the item.
-
-.. _lookup-format-item:
-
-.. py:method:: LookupBase.format_item(item)
-
- By default ``format_item`` creates a dictionary with the three keys used by
- the UI plugin: id, value, label. These are generated from the calls to
- ``get_item_id``, ``get_item_value`` and ``get_item_label``. If you want to
- add additional keys you should add them here.
-
- The results of ``get_item_label`` is conditionally escaped to prevent
- Cross Site Scripting (XSS) similar to the templating language.
- If you know that the content is safe and you want to use these methods
- to include HTML should mark the content as safe with ``django.utils.safestring.mark_safe``
- inside the ``get_item_label`` method.
-
- ``get_item_id`` and ``get_item_value`` are not escapted by default. These are
- not a XSS vector with the built-in JS. If you are doing additional formating using
- these values you should be conscience of this fake and be sure to escape these
- values.
-
- :param item: An item from the search results.
- :return: A dictionary of information for this item to be sent back to the client.
-
-There are also some additional methods that you could want to use/override. These
-are for more advanced use cases such as using the lookups with JS libraries other
-than jQuery UI. Most users will not need to override these methods.
-
-.. _lookup-format-results:
-
-.. py:method:: LookupBase.format_results(self, raw_data, options)
-
- Returns a python structure that later gets serialized. This makes a call to
- :ref:`paginate_results<lookup-paginate-results>` prior to calling
- :ref:`format_item<lookup-format-item>` on each item in the current page.
-
- :param raw_data: The set of all matched results.
- :param options: Dictionary of ``cleaned_data`` from the lookup form class.
- :return: A dictionary with two keys ``meta`` and ``data``.
- The value of ``data`` is an iterable extracted from page_data.
- The value of ``meta`` is a dictionary. This is a copy of options with one additional element
- ``more`` which is a translatable "Show more" string
- (useful for indicating more results on the javascript side).
-
-.. _lookup-paginate-results:
-
-.. py:method:: LookupBase.paginate_results(results, options)
-
- If :ref:`SELECTABLE_MAX_LIMIT` is defined or ``limit`` is passed in request.GET
- then ``paginate_results`` will return the current page using Django's
- built in pagination. See the Django docs on
- `pagination <https://docs.djangoproject.com/en/stable/topics/pagination/>`_
- for more info.
-
- :param results: The set of all matched results.
- :param options: Dictionary of ``cleaned_data`` from the lookup form class.
- :return: The current `Page object <https://docs.djangoproject.com/en/stable/topics/pagination/#page-objects>`_
- of results.
-
-
-.. _ModelLookup:
-
-Lookups Based on Models
---------------------------------------
-
-Perhaps the most common use case is to define a lookup based on a given Django model.
-For this you can extend ``selectable.base.ModelLookup``. To extend ``ModelLookup`` you
-should set two class attributes: ``model`` and ``search_fields``.
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import Fruit
-
-
- class FruitLookup(ModelLookup):
- model = Fruit
- search_fields = ('name__icontains', )
-
- registry.register(FruitLookup)
-
-The syntax for ``search_fields`` is the same as the Django
-`field lookup syntax <http://docs.djangoproject.com/en/stable/ref/models/querysets/#field-lookups>`_.
-Each of these lookups are combined as OR so any one of them matching will return a
-result. You may optionally define a third class attribute ``filters`` which is a dictionary of
-filters to be applied to the model queryset. The keys should be a string defining a field lookup
-and the value should be the value for the field lookup. Filters on the other hand are
-combined with AND.
-
-
-User Lookup Example
---------------------------------------
-
-Below is a larger model lookup example using multiple search fields, filters
-and display options for the `auth.User <https://docs.djangoproject.com/en/stable/topics/auth/#users>`_
-model.
-
- .. code-block:: python
-
- from django.contrib.auth.models import User
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
-
- class UserLookup(ModelLookup):
- model = User
- search_fields = (
- 'username__icontains',
- 'first_name__icontains',
- 'last_name__icontains',
- )
- filters = {'is_active': True, }
-
- def get_item_value(self, item):
- # Display for currently selected item
- return item.username
-
- def get_item_label(self, item):
- # Display for choice listings
- return u"%s (%s)" % (item.username, item.get_full_name())
-
- registry.register(UserLookup)
-
-
-.. _lookup-decorators:
-
-Lookup Decorators
---------------------------------------
-
-Registering lookups with django-selectable creates a small API for searching the
-lookup data. While the amount of visible data is small there are times when you want
-to restrict the set of requests which can view the data. For this purpose there are
-lookup decorators. To use them you simply decorate your lookup class.
-
- .. code-block:: python
-
- from django.contrib.auth.models import User
- from selectable.base import ModelLookup
- from selectable.decorators import login_required
- from selectable.registry import registry
-
-
- @login_required
- class UserLookup(ModelLookup):
- model = User
- search_fields = ('username__icontains', )
- filters = {'is_active': True, }
-
- registry.register(UserLookup)
-
-.. note::
-
- The class decorator syntax was introduced in Python 2.6. If you are using
- django-selectable with Python 2.5 you can still make use of these decorators
- by applying the without the decorator syntax.
-
- .. code-block:: python
-
- class UserLookup(ModelLookup):
- model = User
- search_fields = ('username__icontains', )
- filters = {'is_active': True, }
-
- UserLookup = login_required(UserLookup)
-
- registry.register(UserLookup)
-
-Below are the descriptions of the available lookup decorators.
-
-
-ajax_required
-______________________________________
-
-The django-selectable javascript will always request the lookup data via
-XMLHttpRequest (AJAX) request. This decorator enforces that the lookup can only
-be accessed in this way. If the request is not an AJAX request then it will return
-a 400 Bad Request response.
-
-
-login_required
-______________________________________
-
-This decorator requires the user to be authenticated via ``request.user.is_authenticated``.
-If the user is not authenticated this will return a 401 Unauthorized response.
-``request.user`` is set by the ``django.contrib.auth.middleware.AuthenticationMiddleware``
-which is required for this decorator to work. This middleware is enabled by default.
-
-staff_member_required
-______________________________________
-
-This decorator builds from ``login_required`` and in addition requires that
-``request.user.is_staff`` is ``True``. If the user is not authenticatated this will
-continue to return at 401 response. If the user is authenticated but not a staff member
-then this will return a 403 Forbidden response.
+++ /dev/null
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=_build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^<target^>` where ^<target^> is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. singlehtml to make a single large HTML file
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. devhelp to make HTML files and a Devhelp project
- echo. epub to make an epub
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. text to make text files
- echo. man to make manual pages
- echo. changes to make an overview over all changed/added/deprecated items
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "singlehtml" (
- %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Django-Selectable.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Django-Selectable.ghc
- goto end
-)
-
-if "%1" == "devhelp" (
- %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished.
- goto end
-)
-
-if "%1" == "epub" (
- %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The epub file is in %BUILDDIR%/epub.
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "text" (
- %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The text files are in %BUILDDIR%/text.
- goto end
-)
-
-if "%1" == "man" (
- %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The manual pages are in %BUILDDIR%/man.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- if errorlevel 1 exit /b 1
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- if errorlevel 1 exit /b 1
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-:end
+++ /dev/null
-Overview
-==================
-
-Motivation
---------------------------------------
-
-There are many Django apps related to auto-completion why create another? One problem
-was varying support for the `jQuery UI auto-complete plugin <http://jqueryui.com/demos/autocomplete/>`_
-versus the now deprecated `bassistance version <http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/>`_.
-Another was support for combo-boxes and multiple selects. And lastly was a simple syntax for
-defining the related backend views for the auto-completion.
-
-This library aims to meet all of these goals:
- - Built on jQuery UI auto-complete
- - Fields and widgets for a variety of use-cases:
- - Text inputs and combo-boxes
- - Text selection
- - Value/ID/Foreign key selection
- - Multiple object selection
- - Allowing new values
- - Simple and extendable syntax for defining backend views
-
-
-Related Projects
---------------------------------------
-
-Much of the work here was inspired by things that I like (and things I don't like) about
-`django-ajax-selects <http://code.google.com/p/django-ajax-selects/>`_. To see some of the
-other Django apps for handling auto-completion see `Django-Packages <http://djangopackages.com/grids/g/auto-complete/>`_.
+++ /dev/null
-Getting Started
-==================
-
-The workflow for using `django-selectable` involves two main parts:
- - Defining your lookups
- - Defining your forms
-
-This guide assumes that you have a basic knowledge of creating Django models and
-forms. If not you should first read through the documentation on
-`defining models <http://docs.djangoproject.com/en/stable/topics/db/models/>`_
-and `using forms <http://docs.djangoproject.com/en/stable/topics/forms/>`_.
-
-.. _start-include-jquery:
-
-Including jQuery & jQuery UI
---------------------------------------
-
-The widgets in django-selectable define the media they need as described in the
-Django documentation on `Form Media <https://docs.djangoproject.com/en/stable/topics/forms/media/>`_.
-That means to include the javascript and css you need to make the widgets work you
-can include ``{{ form.media.css }}`` and ``{{ form.media.js }}`` in your template. This is
-assuming your form is called `form` in the template context. For more information
-please check out the `Django documentation <https://docs.djangoproject.com/en/stable/topics/forms/media/>`_.
-
-The jQuery and jQuery UI libraries are not included in the distribution but must be included
-in your templates. However there is a template tag to easily add these libraries from
-the from the `Google CDN <http://code.google.com/apis/libraries/devguide.html#jquery>`_.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_jquery_libs %}
-
-By default these will use jQuery v1.11.2 and jQuery UI v1.11.3. You can customize the versions
-used by pass them to the tag. The first version is the jQuery version and the second is the
-jQuery UI version.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_jquery_libs '1.11.2' '1.11.3' %}
-
-Django-Selectable should work with `jQuery <http://jquery.com/>`_ >= 1.9 and
-`jQuery UI <http://jqueryui.com/>`_ >= 1.10.
-
-You must also include a `jQuery UI theme <http://jqueryui.com/themeroller/>`_ stylesheet. There
-is also a template tag to easily add this style sheet from the Google CDN.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_ui_theme %}
-
-By default this will use the `base <http://jqueryui.com/themeroller/>`_ theme for jQuery UI v1.11.4.
-You can configure the theme and version by passing them in the tag.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_ui_theme 'ui-lightness' '1.11.4' %}
-
-Or only change the theme.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_ui_theme 'ui-lightness' %}
-
-See the the jQuery UI documentation for a full list of available stable themes: http://jqueryui.com/download#stable-themes
-
-Of course you can choose to include these rescources manually::
-
- .. code-block:: html
-
- <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/base/jquery-ui.css" type="text/css">
- <link href="{% static 'selectable/css/dj.selectable.css' %}" type="text/css" media="all" rel="stylesheet">
- <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
- <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.js"></script>
- <script type="text/javascript" src="{% static 'selectable/js/jquery.dj.selectable.js' %}"></script>
-
-.. note::
-
- jQuery UI shares a few plugin names with the popular Twitter Bootstrap framework. There
- are notes on using Bootstrap along with django-selectable in the :ref:`advanced usage
- section <advanced-bootstrap>`.
-
-
-Defining a Lookup
---------------------------------
-
-The lookup classes define the backend views. The most common case is defining a
-lookup which searchs models based on a particular field. Let's define a simple model:
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from django.db import models
- from django.utils.encoding import python_2_unicode_compatible
-
-
- @python_2_unicode_compatible
- class Fruit(models.Model):
- name = models.CharField(max_length=200)
-
- def __str__(self):
- return self.name
-
-In a `lookups.py` we will define our lookup:
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import Fruit
-
-
- class FruitLookup(ModelLookup):
- model = Fruit
- search_fields = ('name__icontains', )
-
-
-This lookups extends ``selectable.base.ModelLookup`` and defines two things: one is
-the model on which we will be searching and the other is the field which we are searching.
-This syntax should look familiar as it is the same as the `field lookup syntax <http://docs.djangoproject.com/en/stable/ref/models/querysets/#field-lookups>`_
-for making queries in Django.
-
-Below this definition we will register our lookup class.
-
- .. code-block:: python
-
- registry.register(FruitLookup)
-
-.. note::
-
- You should only register your lookup once. Attempting to register the same lookup class
- more than once will lead to ``LookupAlreadyRegistered`` errors. A common problem related to the
- ``LookupAlreadyRegistered`` error is related to inconsistant import paths in your project.
- Prior to Django 1.4 the default ``manage.py`` allows for importing both with and without
- the project name (i.e. ``from myproject.myapp import lookups`` or ``from myapp import lookups``).
- This leads to the ``lookup.py`` file being imported twice and the registration code
- executing twice. Thankfully this is no longer the default in Django 1.4. Keeping
- your import consistant to include the project name (when your app is included inside the
- project directory) will avoid these errors.
-
-
-Defining Forms
---------------------------------
-
-Now that we have a working lookup we will define a form which uses it:
-
- .. code-block:: python
-
- from django import forms
-
- from selectable.forms import AutoCompleteWidget
-
- from .lookups import FruitLookup
-
-
- class FruitForm(forms.Form):
- autocomplete = forms.CharField(
- label='Type the name of a fruit (AutoCompleteWidget)',
- widget=AutoCompleteWidget(FruitLookup),
- required=False,
- )
-
-
-This replaces the default widget for the ``CharField`` with the ``AutoCompleteWidget``.
-This will allow the user to fill this field with values taken from the names of
-existing ``Fruit`` models.
-
-And that's pretty much it. Keep on reading if you want to learn about the other
-types of fields and widgets that are available as well as defining more complicated
-lookups.
+++ /dev/null
-Release Notes
-==================
-
-
-v1.2.1 (Released 2019-02-02)
---------------------------------------
-
-Fixed compatibility issue with jQuery UI 1.12. Thanks to Christian Klus (kluchrj) for the fix.
-
-
-v1.2.0 (Released 2018-10-13)
---------------------------------------
-
-Primarily a Django support related release. This version adds support for Django 2.0 and 2.1 while
-dropping support for Django versions below 1.11. A number of deprecation warnings for future Django
-versions have also been addressed.
-
-Added the ability to search on multiple terms split by whitespace.
-
-
-Backwards Incompatible Changes
-________________________________
-
-- Dropped support for Django versions below 1.11
-
-
-v1.1.0 (Released 2018-01-12)
---------------------------------------
-
-- Updated admin docs.
-- Added support for Django 1.11
-
-Special thanks to Luke Plant for contributing the fixes to support Django 1.11.
-
-
-v1.0.0 (Released 2017-04-14)
---------------------------------------
-
-This project has been stable for quite some time and finally declaring a 1.0 release. With
-that comes new policies on official supported versions for Django, Python, jQuery, and jQuery UI.
-
-- New translations for German and Czech.
-- Various bug and compatibility fixes.
-- Updated example project.
-
-Special thanks to Raphael Merx for helping track down issues related to this release
-and an updating the example project to work on Django 1.10.
-
-Backwards Incompatible Changes
-________________________________
-
-- Dropped support Python 2.6 and 3.2
-- Dropped support for Django < 1.7. Django 1.11 is not yet supported.
-- ``LookupBase.serialize_results`` had been removed. This is now handled by the built-in ``JsonResponse`` in Django.
-- jQuery and jQuery UI versions for the ``include_jquery_libs`` and ``include_ui_theme`` template tags have been increased to 1.12.4 and 1.11.4 respectively.
-- Dropped testing support for jQuery < 1.9 and jQuery UI < 1.10. Earlier versions may continue to work but it is recommended to upgrade.
-
-
-v0.9.0 (Released 2014-10-21)
---------------------------------------
-
-This release primarily addresses incompatibility with Django 1.7. The app-loading refactor both
-broke the previous registration and at the same time provided better utilities in Django core to
-make it more robust.
-
-- Compatibility with Django 1.7. Thanks to Calvin Spealman for the fixes.
-- Fixes for Python 3 support.
-
-Backwards Incompatible Changes
-________________________________
-
-- Dropped support for jQuery < 1.7
-
-
-v0.8.0 (Released 2014-01-20)
---------------------------------------
-
-- Widget media references now include a version string for cache-busting when upgrading django-selectable. Thanks to Ustun Ozgur.
-- Added compatibility code for \*SelectWidgets to handle POST data for the default SelectWidget. Thanks to leo-the-manic.
-- Development moved from Bitbucket to Github.
-- Update test suite compatibility with new test runner in Django 1.6. Thanks to Dan Poirier for the report and fix.
-- Tests now run on Travis CI.
-- Added French and Chinese translations.
-
-Backwards Incompatible Changes
-________________________________
-
-- Support for Django < 1.5 has been dropped. Most pieces should continue to work but there was an ugly JS hack to make django-selectable work nicely in the admin which too flakey to continue to maintain. If you aren't using the selectable widgets in inline-forms in the admin you can most likely continue to use Django 1.4 without issue.
-
-
-v0.7.0 (Released 2013-03-01)
---------------------------------------
-
-This release features a large refactor of the JS plugin used by the widgets. While this
-over makes the plugin more maintainable and allowed for some of the new features in this
-release, it does introduce a few incompatible changes. For the most part places where you
-might have previously used the ``autocomplete`` namespace/plugin, those references should
-be updated to reference the ``djselectable`` plugin.
-
-This release also adds experimental support for Python 3.2+ to go along with Django's support in 1.5.
-To use Python 3 with django-selectable you will need to use Django 1.5+.
-
-- Experimental Python 3.2+ support
-- Improved the scope of ``prepareQuery`` and ``formatLabel`` options. Not fully backwards compatible. Thanks to Augusto Men.
-- Allow passing the Python path string in place of the lookup class to the fields and widgets. Thanks to Michael Manfre.
-- Allow passing JS plugin options through the widget ``attrs`` option. Thanks to Felipe Prenholato.
-- Tests for compatibility with jQuery 1.6 through 1.9 and jQuery UI 1.8 through 1.10.
-- Added notes on Bootstrap compatibility.
-- Added compatibility with Grappelli in the admin.
-- Added Spanish translation thanks to Manuel Alvarez.
-- Added documentation notes on testing.
-
-Bug Fixes
-_________________
-
-- Fixed bug with matching hidden input when the name contains '_1'. Thanks to Augusto Men for the report and fix.
-- Fixed bug where the enter button would open the combobox options rather than submit the form. Thanks to Felipe Prenholato for the report.
-- Fixed bug with using ``allow_new=True`` creating items when no data was submitted. See #91.
-- Fixed bug with widget ``has_changed`` when there is no initial data. See #92.
-
-
-Backwards Incompatible Changes
-________________________________
-
-- The JS event namespace has changed from ``autocomplete`` to ``djselectable``.
-- ``data('autocomplete')`` is no longer available on the widgets on the client-side. Use ``data('djselectable')`` instead.
-- Combobox button was changed from a ``<button>`` to ``<a>``. Any customized styles you may have should be updated.
-- Combobox no longer changes the ``minLength`` or ``delay`` options.
-
-
-v0.6.2 (Released 2012-11-07)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed bug with special characters when highlighting matches. Thanks to Chad Files for the report.
-- Fixed javascript bug with spaces in ``item.id``. Thanks to @dc for the report and fix.
-
-
-v0.6.1 (Released 2012-10-13)
---------------------------------------
-
-Features
-_________________
-
-- Added Polish translation. Thanks to Sławomir Ehlert.
-
-Bug Fixes
-_________________
-
-- Fixed incompatibility with jQuery UI 1.9.
-
-
-v0.6.0 (Released 2012-10-09)
---------------------------------------
-
-This release continues to clean up the API and JS. This was primarily motivated by
-Sławomir Ehlert (@slafs) who is working on an alternate implementation which
-uses Select2 rather than jQuery UI. This opens the door for additional apps
-which use the same lookup declaration API with a different JS library on the front
-end.
-
-Python 2.5 support has been dropped to work towards Python 3 support.
-This also drops Django 1.2 support which is no longer receiving security fixes.
-
-Features
-_________________
-
-- Initial translations (pt_BR). Thanks to Felipe Prenholato for the patch.
-- Upgraded default jQuery UI version included by the template tags from 1.8.18 to 1.8.23
-- Added ``djselectableadd`` and ``djselectableremove`` events fired when items are added or removed from a mutliple select
-
-Bug Fixes
-_________________
-
-- Cleaned up JS scoping problems when multiple jQuery versions are used on the page. Thanks Antti Kaihola for the report.
-- Fixed minor JS bug where text input was not cleared when selected via the combobox in the multiselect. Thanks Antti Kaihola for the report and Lukas Pirl for a hotfix.
-
-Backwards Incompatible Changes
-________________________________
-
-- ``get_item_value`` and ``get_item_id`` are no longer marked as safe by default.
-- Removed AutoComboboxSelectField and AutoComboboxSelectMultipleField. These were deprecated in 0.5.
-- Dropping official Python 2.5 support.
-- Dropping official Django 1.2 support.
-- ``paginate_results`` signature changed as part of the lookup refactor.
-- ``SELECTABLE_MAX_LIMIT`` can no longer be ``None``.
-
-
-v0.5.2 (Released 2012-06-27)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed XSS flaw with lookup ``get_item_*`` methods. Thanks slafs for the report.
-- Fixed bug when passing widget instance rather than widget class to ``AutoCompleteSelectField`` or ``AutoCompleteSelectMultipleField``.
-
-
-v0.5.1 (Released 2012-06-08)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fix for double ``autocompleteselect`` event firing.
-- Fix for broken pagination in search results. Thanks David Ray for report and fix.
-
-
-v0.4.2 (Released 2012-06-08)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Backported fix for double ``autocompleteselect`` event firing.
-- Backported fix for broken pagination in search results.
-
-
-v0.5.0 (Released 2012-06-02)
---------------------------------------
-
-Features
-_________________
-
-- Template tag to add necessary jQuery and jQuery UI libraries. Thanks to Rick Testore for the initial implementation
-- :ref:`Lookup decorators <lookup-decorators>` for requiring user authentication or staff access to use the lookup
-- Additional documentation
-- Minor updates to the example project
-
-Backwards Incompatible Changes
-________________________________
-
-- Previously the minimal version of jQuery was listed as 1.4.3 when it fact there was a bug a that made django-selectable require 1.4.4. Not a new incompatibility but the docs have now been updated and 1.4.3 compatibility will not be added. Thanks to Rick Testore for the report and the fix
-- Started deprecation path for AutoComboboxSelectField and AutoComboboxSelectMultipleField
-
-
-v0.4.1 (Released 2012-03-11)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Cleaned up whitespace in css/js. Thanks Dan Poirier for the report and fix.
-- Fixed issue with saving M2M field data with AutoCompleteSelectMultipleField. Thanks Raoul Thill for the report.
-
-
-v0.4.0 (Released 2012-02-25)
---------------------------------------
-
-Features
-_________________
-
-- Better compatibility with :ref:`AutoCompleteSelectWidget`/:ref:`AutoComboboxSelectWidget` and Django's ModelChoiceField
-- Better compatibility with the Django admin :ref:`add another popup <admin-basic-example>`
-- Easier passing of query parameters. See the :ref:`Additional Parameters <additional-parameters>` section
-- Additional documentation
-- QUnit tests for JS functionality
-
-
-Backwards Incompatible Changes
-________________________________
-
-- Support for ``ModelLookup.search_field`` string has been removed. You should use the ``ModelLookup.search_fields`` tuple instead.
-
-
-v0.3.1 (Released 2012-02-23)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed issue with media urls when not using staticfiles.
-
-
-v0.3.0 (Released 2012-02-15)
---------------------------------------
-
-Features
-_________________
-
-- Multiple search fields for :ref:`model based lookups <ModelLookup>`
-- Support for :ref:`highlighting term matches <javascript-highlightMatch>`
-- Support for HTML in :ref:`result labels <lookup-get-item-label>`
-- Support for :ref:`client side formatting <advanced-label-formats>`
-- Additional documentation
-- Expanded examples in example project
-
-
-Bug Fixes
-_________________
-
-- Fixed issue with Enter key removing items from select multiple widgets `#24 <https://github.com/mlavin/django-selectable/issues/24>`_
-
-
-Backwards Incompatible Changes
-________________________________
-
-- The fix for #24 changed the remove items from a button to an anchor tag. If you were previously using the button tag for additional styling then you will need to adjust your styles.
-- The static resources were moved into a `selectable` sub-directory. This makes the media more in line with the template directory conventions. If you are using the widgets in the admin there is nothing to change. If you are using ``{{ form.media }}`` then there is also nothing to change. However if you were including static media manually then you will need to adjust them to include the selectable prefix.
-
-
-v0.2.0 (Released 2011-08-13)
---------------------------------------
-
-Features
-_________________
-
-- Additional documentation
-- :ref:`Positional configuration <AutoCompleteSelectMultipleWidget>` for multiple select fields/widgets
-- :ref:`Settings/configuration <SELECTABLE_MAX_LIMIT>` for limiting/paginating result sets
-- Compatibility and examples for :ref:`Admin inlines <admin-inline-example>`
-- JS updated for jQuery 1.6 compatibility
-- :ref:`JS hooks <client-side-parameters>` for updating query parameters
-- :ref:`Chained selection example <chain-select-example>`
-
-
-v0.1.2 (Released 2011-05-25)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed issue `#17 <https://github.com/mlavin/django-selectable/issues/17>`_
-
-
-v0.1.1 (Release 2011-03-21)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed/cleaned up multiple select fields and widgets
-- Added media definitions to widgets
-
-
-Features
-_________________
-
-- Additional documentation
-- Added `update_query_parameters` to widgets
-- Refactored JS for easier configuration
-
-
-v0.1 (Released 2011-03-13)
---------------------------------------
-
-Initial public release
+++ /dev/null
-Settings
-==================
-
-
-.. _SELECTABLE_MAX_LIMIT:
-
-SELECTABLE_MAX_LIMIT
---------------------------------------
-
-This setting is used to limit the number of results returned by the auto-complete fields.
-Each field/widget can individually lower this maximum. The result sets will be
-paginated allowing the client to ask for more results. The limit is passed as a
-query parameter and validated against this value to ensure the client cannot manipulate
-the query string to retrive more values.
-
-Default: ``25``
-
-
-.. _SELECTABLE_ESCAPED_KEYS:
-
-SELECTABLE_ESCAPED_KEYS
---------------------------------------
-
-The ``LookupBase.format_item`` will conditionally escape result keys based on this
-setting. The label is escaped by default to prevent a XSS flaw when using the
-jQuery UI autocomplete. If you are using the lookup responses for a different
-autocomplete plugin then you may need to esacpe more keys by default.
-
-Default: ``('label', )``
-
-.. note::
- You probably don't want to include ``id`` in this setting.
-
-
-.. _javascript-options:
-
-Javascript Plugin Options
---------------------------------------
-
-Below the options for configuring the Javascript behavior of the django-selectable
-widgets.
-
-
-.. _javascript-removeIcon:
-
-removeIcon
-______________________________________
-
-
-This is the class name used for the remove buttons for the multiple select widgets.
-The set of icon classes built into the jQuery UI framework can be found here:
-http://jqueryui.com/themeroller/
-
-Default: ``ui-icon-close``
-
-
-.. _javascript-comboboxIcon:
-
-comboboxIcon
-______________________________________
-
-
-This is the class name used for the combobox dropdown icon. The set of icon classes built
-into the jQuery UI framework can be found here: http://jqueryui.com/themeroller/
-
-Default: ``ui-icon-triangle-1-s``
-
-
-.. _javascript-prepareQuery:
-
-prepareQuery
-______________________________________
-
-
-``prepareQuery`` is a function that is run prior to sending the search request to
-the server. It is an oppotunity to add additional parameters to the search query.
-It takes one argument which is the current search parameters as a dictionary. For
-more information on its usage see :ref:`Adding Parameters on the Client Side <client-side-parameters>`.
-
-Default: ``null``
-
-
-.. _javascript-highlightMatch:
-
-highlightMatch
-______________________________________
-
-
-If true the portions of the label which match the current search term will be wrapped
-in a span with the class ``highlight``.
-
-Default: ``true``
-
-
-.. _javascript-formatLabel:
-
-formatLabel
-______________________________________
-
-
-``formatLabel`` is a function that is run prior to rendering the search results in
-the dropdown menu. It takes two arguments: the current item label and the item data
-dictionary. It should return the label which should be used. For more information
-on its usage see :ref:`Label Formats on the Client Side <advanced-label-formats>`.
-
-Default: ``null``
-
+++ /dev/null
-Testing Forms and Lookups
-====================================
-
-django-selectable has its own test suite for testing the rendering, validation
-and server-side logic it provides. However, depending on the additional customizations
-you add to your forms and lookups you most likely will want to include tests of your
-own. This section contains some tips or techniques for testing your lookups.
-
-This guide assumes that you are reasonable familiar with the concepts of unit testing
-including Python's `unittest <http://docs.python.org/2/library/unittest.html>`_ module and
-Django's `testing guide <https://docs.djangoproject.com/en/stable/topics/testing/>`_.
-
-
-Testing Forms with django-selectable
---------------------------------------------------
-
-For the most part testing forms which use django-selectable's custom fields
-and widgets is the same as testing any Django form. One point that is slightly
-different is that the select and multi-select widgets are
-`MultiWidgets <https://docs.djangoproject.com/en/stable/ref/forms/widgets/#django.forms.MultiWidget>`_.
-The effect of this is that there are two names in the post rather than one. Take the below
-form for example.
-
- .. code-block:: python
-
- # models.py
-
- from django.db import models
-
- class Thing(models.Model):
- name = models.CharField(max_length=100)
- description = models.CharField(max_length=100)
-
- def __unicode__(self):
- return self.name
-
- .. code-block:: python
-
- # lookups.py
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import Thing
-
- class ThingLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
- registry.register(ThingLookup)
-
- .. code-block:: python
-
- # forms.py
-
- from django import forms
-
- from selectable.forms import AutoCompleteSelectField
-
- from .lookups import ThingLookup
-
- class SimpleForm(forms.Form):
- "Basic form for testing."
- thing = AutoCompleteSelectField(lookup_class=ThingLookup)
-
-This form has a single field to select a ``Thing``. It does not allow
-new items. Let's write some simple tests for this form.
-
- .. code-block:: python
-
- # tests.py
-
- from django.test import TestCase
-
- from .forms import SimpleForm
- from .models import Thing
-
- class SimpleFormTestCase(TestCase):
-
- def test_valid_form(self):
- "Submit valid data."
- thing = Thing.objects.create(name='Foo', description='Bar')
- data = {
- 'thing_0': thing.name,
- 'thing_1': thing.pk,
- }
- form = SimpleForm(data=data)
- self.assertTrue(form.is_valid())
-
- def test_invalid_form(self):
- "Thing is required but missing."
- data = {
- 'thing_0': 'Foo',
- 'thing_1': '',
- }
- form = SimpleForm(data=data)
- self.assertFalse(form.is_valid())
-
-Here you will note that while there is only one field ``thing`` it requires
-two items in the POST the first is for the text input and the second is for
-the hidden input. This is again due to the use of MultiWidget for the selection.
-
-There is compatibility code in the widgets to lookup the original name
-from the POST. This makes it easier to transition to the the selectable widgets without
-breaking existing tests.
-
-
-Testing Lookup Results
---------------------------------------------------
-
-Testing the lookups used by django-selectable is similar to testing your Django views.
-While it might be tempting to use the Django
-`test client <https://docs.djangoproject.com/en/stable/topics/testing/#module-django.test.client>`_,
-it is slightly easier to use the
-`request factory <https://docs.djangoproject.com/en/stable/topics/testing/#the-request-factory>`_.
-A simple example is given below.
-
- .. code-block:: python
-
- # tests.py
-
- import json
-
- from django.test import TestCase
- from django.test.client import RequestFactory
-
- from .lookups import ThingLookup
- from .models import Thing
-
- class ThingLookupTestCase(TestCase):
-
- def setUp(self):
- self.factory = RequestFactory()
- self.lookup = ThingLookup()
- self.test_thing = Thing.objects.create(name='Foo', description='Bar')
-
- def test_results(self):
- "Test full response."
- request = self.factory.get("/", {'term': 'Fo'})
- response = self.lookup.results(request)
- data = json.loads(response.content)['data']
- self.assertEqual(1, len(data))
- self.assertEqual(self.test_thing.pk, data[1]['id'])
-
- def test_label(self):
- "Test item label."
- label = self.lookup.get_item_label(self.test_thing)
- self.assertEqual(self.test_thing.name, label)
-
-As shown in the ``test_label`` example it is not required to test the full
-request/response. You can test each of the methods in the lookup API individually.
-When testing your lookups you should focus on testing the portions which have been
-customized by your application.
\ No newline at end of file
+++ /dev/null
-Widgets
-==========
-
-Below are the custom widgets defined by Django-Selectable. All widgets take the
-lookup class as the first required argument.
-
-These widgets all support a ``query_params`` keyword argument which is used to pass
-additional query parameters to the lookup search. See the section on
-:ref:`Adding Parameters on the Server Side <server-side-parameters>` for more
-information.
-
-You can configure the plugin options by passing the configuration dictionary in the ``data-selectable-options``
-attribute. The set of options availble include those define by the base
-`autocomplete plugin <http://api.jqueryui.com/1.9/autocomplete/>`_ as well as the
-:ref:`javascript-removeIcon`, :ref:`javascript-comboboxIcon`, and :ref:`javascript-highlightMatch` options
-which are unique to django-selectable.
-
- .. code-block:: python
-
- attrs = {'data-selectable-options': {'highlightMatch': True, 'minLength': 5}}
- selectable.AutoCompleteSelectWidget(lookup_class=FruitLookup, attrs=attrs)
-
-
-.. _AutoCompleteWidget:
-
-AutoCompleteWidget
---------------------------------------
-
-Basic widget for auto-completing text. The widget returns the item value as defined
-by the lookup ``get_item_value``. If the ``allow_new`` keyword argument is passed as
-true it will allow the user to type any text they wish.
-
-.. _AutoComboboxWidget:
-
-AutoComboboxWidget
---------------------------------------
-
-Similar to :ref:`AutoCompleteWidget` but has a button to reveal all options.
-
-
-.. _AutoCompleteSelectWidget:
-
-AutoCompleteSelectWidget
---------------------------------------
-
-Widget for selecting a value/id based on input text. Optionally allows selecting new items to be created.
-This widget should be used in conjunction with the :ref:`AutoCompleteSelectField` as it will
-return both the text entered by the user and the id (if an item was selected/matched).
-
-:ref:`AutoCompleteSelectWidget` works directly with Django's
-`ModelChoiceField <https://docs.djangoproject.com/en/stable/ref/forms/fields/#modelchoicefield>`_.
-You can simply replace the widget without replacing the entire field.
-
- .. code-block:: python
-
- class FarmAdminForm(forms.ModelForm):
-
- class Meta(object):
- model = Farm
- widgets = {
- 'owner': selectable.AutoCompleteSelectWidget(lookup_class=FruitLookup),
- }
-
-The one catch is that you must use ``allow_new=False`` which is the default.
-
-``lookup_class`` may also be a dotted path.
-
- .. code-block:: python
-
- widget = selectable.AutoCompleteWidget(lookup_class='core.lookups.FruitLookup')
-
-
-.. _AutoComboboxSelectWidget:
-
-AutoComboboxSelectWidget
---------------------------------------
-
-Similar to :ref:`AutoCompleteSelectWidget` but has a button to reveal all options.
-
-:ref:`AutoComboboxSelectWidget` works directly with Django's
-`ModelChoiceField <https://docs.djangoproject.com/en/stable/ref/forms/fields/#modelchoicefield>`_.
-You can simply replace the widget without replacing the entire field.
-
- .. code-block:: python
-
- class FarmAdminForm(forms.ModelForm):
-
- class Meta(object):
- model = Farm
- widgets = {
- 'owner': selectable.AutoComboboxSelectWidget(lookup_class=FruitLookup),
- }
-
-The one catch is that you must use ``allow_new=False`` which is the default.
-
-
-.. _AutoCompleteSelectMultipleWidget:
-
-AutoCompleteSelectMultipleWidget
---------------------------------------
-
-Builds a list of selected items from auto-completion. This widget will return a list
-of item ids as defined by the lookup ``get_item_id``. Using this widget with the
-:ref:`AutoCompleteSelectMultipleField` will clean the items to the item objects. This does
-not allow for creating new items. There is another optional keyword argument ``postion``
-which can take four possible values: `bottom`, `bottom-inline`, `top` or `top-inline`.
-This determine the position of the deck list of currently selected items as well as
-whether this list is stacked or inline. The default is `bottom`.
-
-
-.. _AutoComboboxSelectMultipleWidget:
-
-AutoComboboxSelectMultipleWidget
---------------------------------------
-
-Same as :ref:`AutoCompleteSelectMultipleWidget` but with a combobox.
+++ /dev/null
-#!/usr/bin/env python
-import os
-import sys
-
-from django.conf import settings
-
-
-if not settings.configured:
- settings.configure(
- DATABASES={
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': ':memory:',
- }
- },
- MIDDLEWARE=(),
- INSTALLED_APPS=(
- 'selectable',
- ),
- SECRET_KEY='super-secret',
- ROOT_URLCONF='selectable.tests.urls',
- TEMPLATES=[{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(os.path.normpath(os.path.join(
- os.path.dirname(__file__), 'selectable')), 'templates')]}])
-
-
-from django import setup
-from django.test.utils import get_runner
-
-
-def runtests():
- setup()
- TestRunner = get_runner(settings)
- test_runner = TestRunner(verbosity=1, interactive=True, failfast=False)
- args = sys.argv[1:] or ['selectable', ]
- failures = test_runner.run_tests(args)
- sys.exit(failures)
-
-
-if __name__ == '__main__':
- runtests()
+++ /dev/null
-"Auto-complete selection widgets using Django and jQuery UI."
-
-
-__version__ = '1.2.1'
-
-default_app_config = 'selectable.apps.SelectableConfig'
+++ /dev/null
-from django.apps import AppConfig
-
-
-class SelectableConfig(AppConfig):
- """App configuration for django-selectable."""
-
- name = 'selectable'
-
- def ready(self):
- from . import registry
- registry.autodiscover()
+++ /dev/null
-"Base classes for lookup creation."
-from __future__ import unicode_literals
-
-import operator
-import re
-from functools import reduce
-
-from django.conf import settings
-from django.core.paginator import Paginator, InvalidPage, EmptyPage
-from django.http import JsonResponse
-from django.db.models import Q, Model
-from django.urls import reverse
-from django.utils.encoding import smart_text
-from django.utils.html import conditional_escape
-from django.utils.translation import ugettext as _
-
-from .forms import BaseLookupForm
-
-__all__ = (
- 'LookupBase',
- 'ModelLookup',
-)
-
-
-class LookupBase(object):
- "Base class for all django-selectable lookups."
-
- form = BaseLookupForm
- response = JsonResponse
-
- def _name(cls):
- app_name = cls.__module__.split('.')[-2].lower()
- class_name = cls.__name__.lower()
- name = '%s-%s' % (app_name, class_name)
- return name
- name = classmethod(_name)
-
- def split_term(self, term):
- """
- Split searching term into array of subterms
- that will be searched separately.
- """
- return term.split()
-
- def _url(cls):
- return reverse('selectable-lookup', args=[cls.name()])
- url = classmethod(_url)
-
- def get_query(self, request, term):
- return []
-
- def get_item_label(self, item):
- return smart_text(item)
-
- def get_item_id(self, item):
- return smart_text(item)
-
- def get_item_value(self, item):
- return smart_text(item)
-
- def get_item(self, value):
- return value
-
- def create_item(self, value):
- raise NotImplemented()
-
- def format_item(self, item):
- "Construct result dictionary for the match item."
- result = {
- 'id': self.get_item_id(item),
- 'value': self.get_item_value(item),
- 'label': self.get_item_label(item),
- }
- for key in settings.SELECTABLE_ESCAPED_KEYS:
- if key in result:
- result[key] = conditional_escape(result[key])
- return result
-
- def paginate_results(self, results, options):
- "Return a django.core.paginator.Page of results."
- limit = options.get('limit', settings.SELECTABLE_MAX_LIMIT)
- paginator = Paginator(results, limit)
- page = options.get('page', 1)
- try:
- results = paginator.page(page)
- except (EmptyPage, InvalidPage):
- results = paginator.page(paginator.num_pages)
- return results
-
- def results(self, request):
- "Match results to given term and return the serialized HttpResponse."
- results = {}
- form = self.form(request.GET)
- if form.is_valid():
- options = form.cleaned_data
- term = options.get('term', '')
- raw_data = self.get_query(request, term)
- results = self.format_results(raw_data, options)
- return self.response(results)
-
- def format_results(self, raw_data, options):
- '''
- Returns a python structure that later gets serialized.
- raw_data
- full list of objects matching the search term
- options
- a dictionary of the given options
- '''
- page_data = self.paginate_results(raw_data, options)
- results = {}
- meta = options.copy()
- meta['more'] = _('Show more results')
- if page_data and page_data.has_next():
- meta['next_page'] = page_data.next_page_number()
- if page_data and page_data.has_previous():
- meta['prev_page'] = page_data.previous_page_number()
- results['data'] = [self.format_item(item) for item in page_data.object_list]
- results['meta'] = meta
- return results
-
-
-class ModelLookup(LookupBase):
- "Lookup class for easily defining lookups based on Django models."
-
- model = None
- filters = {}
- search_fields = ()
-
- def get_query(self, request, term):
- qs = self.get_queryset()
- if term:
- if self.search_fields:
- for t in self.split_term(term):
- search_filters = []
- for field in self.search_fields:
- search_filters.append(Q(**{field: t}))
- qs = qs.filter(reduce(operator.or_, search_filters))
- return qs
-
- def get_queryset(self):
- qs = self.model._default_manager.get_queryset()
- if self.filters:
- qs = qs.filter(**self.filters)
- return qs
-
- def get_item_id(self, item):
- return item.pk
-
- def get_item(self, value):
- item = None
- if value:
- value = value.pk if isinstance(value, Model) else value
- try:
- item = self.get_queryset().get(pk=value)
- except (ValueError, self.model.DoesNotExist):
- item = None
- return item
-
- def create_item(self, value):
- data = {}
- if self.search_fields:
- field_name = re.sub(r'__\w+$', '', self.search_fields[0])
- if field_name:
- data = {field_name: value}
- return self.model(**data)
+++ /dev/null
-"Compatibility utilites for Python versions."
-
-try:
- from urllib.parse import urlparse
-except ImportError:
- # This can be removed when Python 2.7 support is dropped
- from urlparse import urlparse
+++ /dev/null
-"Decorators for additional lookup functionality."
-from __future__ import unicode_literals
-
-from functools import wraps
-
-from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
-
-
-__all__ = (
- 'ajax_required',
- 'login_required',
- 'staff_member_required',
-)
-
-
-def results_decorator(func):
- """
- Helper for constructing simple decorators around Lookup.results.
-
- func is a function which takes a request as the first parameter. If func
- returns an HttpReponse it is returned otherwise the original Lookup.results
- is returned.
- """
- # Wrap function to maintian the original doc string, etc
- @wraps(func)
- def decorator(lookup_cls):
- # Construct a class decorator from the original function
- original = lookup_cls.results
- def inner(self, request):
- # Wrap lookup_cls.results by first calling func and checking the result
- result = func(request)
- if isinstance(result, HttpResponse):
- return result
- return original(self, request)
- # Replace original lookup_cls.results with wrapped version
- lookup_cls.results = inner
- return lookup_cls
- # Return the constructed decorator
- return decorator
-
-
-@results_decorator
-def ajax_required(request):
- "Lookup decorator to require AJAX calls to the lookup view."
- if not request.is_ajax():
- return HttpResponseBadRequest()
-
-
-@results_decorator
-def login_required(request):
- "Lookup decorator to require the user to be authenticated."
- user = getattr(request, 'user', None)
- if user is None or not user.is_authenticated:
- return HttpResponse(status=401) # Unauthorized
-
-
-@results_decorator
-def staff_member_required(request):
- "Lookup decorator to require the user is a staff member."
- user = getattr(request, 'user', None)
- if user is None or not user.is_authenticated:
- return HttpResponse(status=401) # Unauthorized
- elif not user.is_staff:
- return HttpResponseForbidden()
+++ /dev/null
-class LookupAlreadyRegistered(Exception):
- "Exception when trying to register a lookup which is already registered."
-
-
-class LookupNotRegistered(Exception):
- "Exception when trying use a lookup which is not registered."
-
-
-class LookupInvalid(Exception):
- "Exception when register an invalid lookup class."
+++ /dev/null
-from selectable.forms.base import *
-from selectable.forms.fields import *
-from selectable.forms.widgets import *
+++ /dev/null
-from __future__ import unicode_literals
-
-from importlib import import_module
-
-from django import forms
-from django.conf import settings
-from django.utils.six import string_types
-
-
-__all__ = (
- 'BaseLookupForm',
- 'import_lookup_class',
-)
-
-
-class BaseLookupForm(forms.Form):
- term = forms.CharField(required=False)
- limit = forms.IntegerField(required=False, min_value=1)
- page = forms.IntegerField(required=False, min_value=1)
-
- def clean_limit(self):
- "Ensure given limit is less than default if defined"
- limit = self.cleaned_data.get('limit', None)
- if (settings.SELECTABLE_MAX_LIMIT is not None and
- (not limit or limit > settings.SELECTABLE_MAX_LIMIT)):
- limit = settings.SELECTABLE_MAX_LIMIT
- return limit
-
- def clean_page(self):
- "Return the first page if no page or invalid number is given."
- return self.cleaned_data.get('page', 1) or 1
-
-
-def import_lookup_class(lookup_class):
- """
- Import lookup_class as a dotted base and ensure it extends LookupBase
- """
- from selectable.base import LookupBase
- if isinstance(lookup_class, string_types):
- mod_str, cls_str = lookup_class.rsplit('.', 1)
- mod = import_module(mod_str)
- lookup_class = getattr(mod, cls_str)
- if not issubclass(lookup_class, LookupBase):
- raise TypeError('lookup_class must extend from selectable.base.LookupBase')
- return lookup_class
+++ /dev/null
-from __future__ import unicode_literals
-
-from django import forms
-from django.core.exceptions import ValidationError
-from django.core.validators import EMPTY_VALUES
-from django.utils.translation import ugettext_lazy as _
-from django.db.models import Model
-
-from selectable.forms.base import import_lookup_class
-from selectable.forms.widgets import AutoCompleteSelectWidget
-from selectable.forms.widgets import AutoCompleteSelectMultipleWidget
-
-__all__ = (
- 'AutoCompleteSelectField',
- 'AutoCompleteSelectMultipleField',
-)
-
-
-def model_vars(obj):
- fields = dict(
- (field.name, getattr(obj, field.name))
- for field in obj._meta.fields
- )
- return fields
-
-
-class BaseAutoCompleteField(forms.Field):
-
- def has_changed(self, initial, data):
- "Detects if the data was changed. This is added in 1.6."
- if initial is None and data is None:
- return False
- if data and not hasattr(data, '__iter__'):
- data = self.widget.decompress(data)
- initial = self.to_python(initial)
- data = self.to_python(data)
- if hasattr(self, '_coerce'):
- data = self._coerce(data)
- if isinstance(data, Model) and isinstance(initial, Model):
- return model_vars(data) != model_vars(initial)
- else:
- return data != initial
-
-
-class AutoCompleteSelectField(BaseAutoCompleteField):
- widget = AutoCompleteSelectWidget
-
- default_error_messages = {
- 'invalid_choice': _('Select a valid choice. That choice is not one of the available choices.'),
- }
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.allow_new = kwargs.pop('allow_new', False)
- self.limit = kwargs.pop('limit', None)
- widget = kwargs.get('widget', self.widget) or self.widget
- if isinstance(widget, type):
- kwargs['widget'] = widget(lookup_class, allow_new=self.allow_new, limit=self.limit)
- super(AutoCompleteSelectField, self).__init__(*args, **kwargs)
-
- def to_python(self, value):
- if value in EMPTY_VALUES:
- return None
- lookup = self.lookup_class()
- if isinstance(value, list):
- # Input comes from an AutoComplete widget. It's two
- # components: text and id
- if len(value) != 2:
- raise ValidationError(self.error_messages['invalid_choice'])
- label, pk = value
- if pk in EMPTY_VALUES:
- if not self.allow_new:
- if label:
- raise ValidationError(self.error_messages['invalid_choice'])
- else:
- return None
- if label in EMPTY_VALUES:
- return None
- value = lookup.create_item(label)
- else:
- value = lookup.get_item(pk)
- if value is None:
- raise ValidationError(self.error_messages['invalid_choice'])
- else:
- value = lookup.get_item(value)
- if value is None:
- raise ValidationError(self.error_messages['invalid_choice'])
- return value
-
-
-class AutoCompleteSelectMultipleField(BaseAutoCompleteField):
- widget = AutoCompleteSelectMultipleWidget
-
- default_error_messages = {
- 'invalid_choice': _('Select a valid choice. That choice is not one of the available choices.'),
- }
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.limit = kwargs.pop('limit', None)
- widget = kwargs.get('widget', self.widget) or self.widget
- if isinstance(widget, type):
- kwargs['widget'] = widget(lookup_class, limit=self.limit)
- super(AutoCompleteSelectMultipleField, self).__init__(*args, **kwargs)
-
- def to_python(self, value):
- if value in EMPTY_VALUES:
- return []
- lookup = self.lookup_class()
- items = []
- for v in value:
- if v not in EMPTY_VALUES:
- item = lookup.get_item(v)
- if item is None:
- raise ValidationError(self.error_messages['invalid_choice'])
- items.append(item)
- return items
+++ /dev/null
-from __future__ import unicode_literals
-
-import json
-
-from django import forms
-from django.conf import settings
-from django.utils.encoding import force_text
-from django.utils.http import urlencode
-
-from selectable import __version__
-from selectable.forms.base import import_lookup_class
-
-__all__ = (
- 'AutoCompleteWidget',
- 'AutoCompleteSelectWidget',
- 'AutoComboboxWidget',
- 'AutoComboboxSelectWidget',
- 'AutoCompleteSelectMultipleWidget',
- 'AutoComboboxSelectMultipleWidget',
-)
-
-
-STATIC_PREFIX = '%sselectable/' % settings.STATIC_URL
-
-
-class SelectableMediaMixin(object):
-
- class Media(object):
- css = {
- 'all': ('%scss/dj.selectable.css?v=%s' % (STATIC_PREFIX, __version__),)
- }
- js = ('%sjs/jquery.dj.selectable.js?v=%s' % (STATIC_PREFIX, __version__),)
-
-
-class AutoCompleteWidget(forms.TextInput, SelectableMediaMixin):
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.allow_new = kwargs.pop('allow_new', False)
- self.qs = kwargs.pop('query_params', {})
- self.limit = kwargs.pop('limit', None)
- super(AutoCompleteWidget, self).__init__(*args, **kwargs)
-
- def update_query_parameters(self, qs_dict):
- self.qs.update(qs_dict)
-
- def build_attrs(self, base_attrs, extra_attrs=None):
- attrs = super(AutoCompleteWidget, self).build_attrs(base_attrs, extra_attrs)
- url = self.lookup_class.url()
- if self.limit and 'limit' not in self.qs:
- self.qs['limit'] = self.limit
- if self.qs:
- url = '%s?%s' % (url, urlencode(self.qs))
- if 'data-selectable-options' in attrs:
- attrs['data-selectable-options'] = json.dumps(attrs['data-selectable-options'])
- attrs['data-selectable-url'] = url
- attrs['data-selectable-type'] = 'text'
- attrs['data-selectable-allow-new'] = str(self.allow_new).lower()
- return attrs
-
-
-class SelectableMultiWidget(forms.MultiWidget):
-
- def update_query_parameters(self, qs_dict):
- self.widgets[0].update_query_parameters(qs_dict)
-
- def decompress(self, value):
- if value:
- lookup = self.lookup_class()
- model = getattr(self.lookup_class, 'model', None)
- if model and isinstance(value, model):
- item = value
- value = lookup.get_item_id(item)
- else:
- item = lookup.get_item(value)
- item_value = lookup.get_item_value(item)
- return [item_value, value]
- return [None, None]
-
- def get_compatible_postdata(self, data, name):
- """Get postdata built for a normal <select> element.
-
- Django MultiWidgets create post variables like ``foo_0`` and ``foo_1``,
- and this behavior is not cleanly overridable. Non-multiwidgets, like
- Select, get simple names like ``foo``. In order to keep this widget
- compatible with requests designed for traditional select widgets,
- search postdata for a name like ``foo`` and return that value.
-
- This will return ``None`` if a ``<select>``-compatibile post variable
- is not found.
- """
- return data.get(name, None)
-
-
-class _BaseSingleSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
- """
- Common base class for AutoCompleteSelectWidget and AutoComboboxSelectWidget
- each which use one widget (primary_widget) to select text and a single
- hidden input to hold the selected id.
- """
-
- primary_widget = None
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.allow_new = kwargs.pop('allow_new', False)
- self.limit = kwargs.pop('limit', None)
- query_params = kwargs.pop('query_params', {})
- widgets = [
- self.primary_widget(
- lookup_class, allow_new=self.allow_new,
- limit=self.limit, query_params=query_params,
- attrs=kwargs.get('attrs'),
- ),
- forms.HiddenInput(attrs={'data-selectable-type': 'hidden'})
- ]
- super(_BaseSingleSelectWidget, self).__init__(widgets, *args, **kwargs)
-
- def value_from_datadict(self, data, files, name):
- value = super(_BaseSingleSelectWidget, self).value_from_datadict(data, files, name)
- if not value[1]:
- compatible_postdata = self.get_compatible_postdata(data, name)
- if compatible_postdata:
- value[1] = compatible_postdata
- if not self.allow_new:
- return value[1]
- return value
-
-
-class AutoCompleteSelectWidget(_BaseSingleSelectWidget):
-
- primary_widget = AutoCompleteWidget
-
-
-class AutoComboboxWidget(AutoCompleteWidget, SelectableMediaMixin):
-
- def build_attrs(self, base_attrs, extra_attrs=None):
- attrs = super(AutoComboboxWidget, self).build_attrs(base_attrs, extra_attrs)
- attrs['data-selectable-type'] = 'combobox'
- return attrs
-
-
-class AutoComboboxSelectWidget(_BaseSingleSelectWidget):
-
- primary_widget = AutoComboboxWidget
-
-
-class LookupMultipleHiddenInput(forms.MultipleHiddenInput):
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- super(LookupMultipleHiddenInput, self).__init__(*args, **kwargs)
-
- def get_context(self, name, value, attrs):
- lookup = self.lookup_class()
- values = self._normalize_value(value)
- values = list(values) # force evaluation
-
- context = super(LookupMultipleHiddenInput, self).get_context(name, values, attrs)
-
- # Now override/add to what super() did:
- subwidgets = context['widget']['subwidgets']
- for widget_ctx, item in zip(subwidgets, values):
- input_value, title = self._lookup_value_and_title(lookup, item)
- widget_ctx['value'] = input_value # override what super() did
- if title:
- widget_ctx['attrs']['title'] = title
- return context
-
- def build_attrs(self, base_attrs, extra_attrs=None):
- attrs = super(LookupMultipleHiddenInput, self).build_attrs(base_attrs, extra_attrs)
- attrs['data-selectable-type'] = 'hidden-multiple'
- return attrs
-
- def _normalize_value(self, value):
- if value is None:
- value = []
- return value
-
- def _lookup_value_and_title(self, lookup, v):
- model = getattr(self.lookup_class, 'model', None)
- item = None
- if model and isinstance(v, model):
- item = v
- v = lookup.get_item_id(item)
- title = None
- if v:
- item = item or lookup.get_item(v)
- title = lookup.get_item_value(item)
- return force_text(v), title
-
-
-class _BaseMultipleSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
- """
- Common base class for AutoCompleteSelectMultipleWidget and AutoComboboxSelectMultipleWidget
- each which use one widget (primary_widget) to select text and a multiple
- hidden inputs to hold the selected ids.
- """
-
- primary_widget = None
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.limit = kwargs.pop('limit', None)
- position = kwargs.pop('position', 'bottom')
- attrs = {
- 'data-selectable-multiple': 'true',
- 'data-selectable-position': position
- }
- attrs.update(kwargs.get('attrs', {}))
- query_params = kwargs.pop('query_params', {})
- widgets = [
- self.primary_widget(
- lookup_class, allow_new=False,
- limit=self.limit, query_params=query_params, attrs=attrs
- ),
- LookupMultipleHiddenInput(lookup_class)
- ]
- super(_BaseMultipleSelectWidget, self).__init__(widgets, *args, **kwargs)
-
- def value_from_datadict(self, data, files, name):
- value = self.widgets[1].value_from_datadict(data, files, name + '_1')
- if not value:
- # Fall back to the compatible POST name
- value = self.get_compatible_postdata(data, name)
- return value
-
- def build_attrs(self, base_attrs, extra_attrs=None):
- attrs = super(_BaseMultipleSelectWidget, self).build_attrs(base_attrs, extra_attrs)
- if 'required' in attrs:
- attrs.pop('required')
- return attrs
-
- def render(self, name, value, attrs=None, renderer=None):
- if value and not hasattr(value, '__iter__'):
- value = [value]
- value = ['', value]
- return super(_BaseMultipleSelectWidget, self).render(name, value, attrs, renderer)
-
-
-class AutoCompleteSelectMultipleWidget(_BaseMultipleSelectWidget):
-
- primary_widget = AutoCompleteWidget
-
-
-class AutoComboboxSelectMultipleWidget(_BaseMultipleSelectWidget):
-
- primary_widget = AutoComboboxWidget
+++ /dev/null
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-10-21 20:14-0400\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: base.py:117
-msgid "Show more results"
-msgstr ""
-
-#: forms/fields.py:48 forms/fields.py:96
-msgid "Select a valid choice. That choice is not one of the available choices."
-msgstr ""
+++ /dev/null
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-#
-# Translators:
-msgid ""
-msgstr ""
-"Project-Id-Version: django-selectable\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-06 15:02-0400\n"
-"PO-Revision-Date: 2013-11-20 10:18+0000\n"
-"Language-Team: Spanish (http://www.transifex.com/projects/p/django-selectable/language/es/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: es\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-
-#: base.py:115
-msgid "Show more results"
-msgstr "Mostrar más resultados"
-
-#: forms/fields.py:19 forms/fields.py:63
-msgid ""
-"Select a valid choice. That choice is not one of the available choices."
-msgstr "Seleccione una opción válida. La opción seleccionada no está disponible."
+++ /dev/null
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-#
-# Translators:
-msgid ""
-msgstr ""
-"Project-Id-Version: django-selectable\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-06 15:02-0400\n"
-"PO-Revision-Date: 2014-01-21 01:00+0000\n"
-"Language-Team: French (http://www.transifex.com/projects/p/django-selectable/language/fr/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: fr\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#: base.py:115
-msgid "Show more results"
-msgstr "Afficher plus de résultats"
-
-#: forms/fields.py:19 forms/fields.py:63
-msgid ""
-"Select a valid choice. That choice is not one of the available choices."
-msgstr "Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles."
+++ /dev/null
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-#
-# Translators:
-msgid ""
-msgstr ""
-"Project-Id-Version: django-selectable\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-06 15:02-0400\n"
-"PO-Revision-Date: 2013-11-20 10:18+0000\n"
-"Language-Team: Polish (http://www.transifex.com/projects/p/django-selectable/language/pl/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: pl\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
-
-#: base.py:115
-msgid "Show more results"
-msgstr "Pokaż więcej wyników"
-
-#: forms/fields.py:19 forms/fields.py:63
-msgid ""
-"Select a valid choice. That choice is not one of the available choices."
-msgstr "Dokonaj poprawnego wyboru. Ten wybór nie jest jednym z dostępnych."
+++ /dev/null
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-#
-# Translators:
-msgid ""
-msgstr ""
-"Project-Id-Version: django-selectable\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-06 15:02-0400\n"
-"PO-Revision-Date: 2013-11-20 10:18+0000\n"
-"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/django-selectable/language/pt_BR/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: pt_BR\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#: base.py:115
-msgid "Show more results"
-msgstr "Mostrar mais resultados"
-
-#: forms/fields.py:19 forms/fields.py:63
-msgid ""
-"Select a valid choice. That choice is not one of the available choices."
-msgstr "Selecione uma escolha valida. Esta escolha não é uma das disponíveis."
+++ /dev/null
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-#
-# Translators:
-msgid ""
-msgstr ""
-"Project-Id-Version: django-selectable\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-06 15:02-0400\n"
-"PO-Revision-Date: 2013-11-21 05:08+0000\n"
-"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/django-selectable/language/zh_CN/)\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Language: zh_CN\n"
-"Plural-Forms: nplurals=1; plural=0;\n"
-
-#: base.py:115
-msgid "Show more results"
-msgstr "显示更多结果"
-
-#: forms/fields.py:19 forms/fields.py:63
-msgid ""
-"Select a valid choice. That choice is not one of the available choices."
-msgstr "请选择一个有效的选项。当前选项无效。"
+++ /dev/null
-from __future__ import unicode_literals
-
-from django.conf import settings
-
-# Set default settings
-if not hasattr(settings, 'SELECTABLE_MAX_LIMIT'):
- settings.SELECTABLE_MAX_LIMIT = 25
-
-if not hasattr(settings, 'SELECTABLE_ESCAPED_KEYS'):
- settings.SELECTABLE_ESCAPED_KEYS = ('label', )
+++ /dev/null
-from __future__ import unicode_literals
-
-from django.utils.encoding import force_text
-from django.utils.module_loading import autodiscover_modules
-
-from selectable.base import LookupBase
-from selectable.exceptions import (LookupAlreadyRegistered, LookupNotRegistered,
- LookupInvalid)
-
-
-class LookupRegistry(object):
-
- def __init__(self):
- self._registry = {}
-
- def validate(self, lookup):
- if not issubclass(lookup, LookupBase):
- raise LookupInvalid('Registered lookups must inherit from the LookupBase class')
-
- def register(self, lookup):
- self.validate(lookup)
- name = force_text(lookup.name())
- if name in self._registry:
- raise LookupAlreadyRegistered('The name %s is already registered' % name)
- self._registry[name] = lookup
-
- def unregister(self, lookup):
- self.validate(lookup)
- name = force_text(lookup.name())
- if name not in self._registry:
- raise LookupNotRegistered('The name %s is not registered' % name)
- del self._registry[name]
-
- def get(self, key):
- return self._registry.get(key, None)
-
-
-registry = LookupRegistry()
-
-
-def autodiscover():
- # Attempt to import the app's lookups module.
- autodiscover_modules('lookups', register_to=registry)
+++ /dev/null
-/*
- * django-selectable UI widget CSS
- * Source: https://github.com/mlavin/django-selectable
- * Docs: http://django-selectable.readthedocs.org/
- *
- * Copyright 2010-2014, Mark Lavin
- * BSD License
- *
-*/
-ul.selectable-deck, ul.ui-autocomplete {
- list-style: none outside none;
-}
-ul.selectable-deck li.selectable-deck-item,
-ul.ui-autocomplete li.ui-menu-item {
- margin: 0;
- list-style-type: none;
-}
-ul.selectable-deck li.selectable-deck-item .selectable-deck-remove {
- float: right;
-}
-ul.selectable-deck-bottom-inline,
-ul.selectable-deck-top-inline {
- padding: 0;
-}
-ul.selectable-deck-bottom-inline li.selectable-deck-item,
-ul.selectable-deck-top-inline li.selectable-deck-item {
- display: inline;
-}
-ul.selectable-deck-bottom-inline li.selectable-deck-item .selectable-deck-remove,
-ul.selectable-deck-top-inline li.selectable-deck-item .selectable-deck-remove {
- margin-left: 0.4em;
- display: inline;
- float: none;
-}
-ul.ui-autocomplete li.ui-menu-item span.highlight {
- font-weight: bold;
-}
-input.ui-combo-input {
- margin-right: 0;
- line-height: 1.3;
-}
-a.ui-combo-button {
- margin-left: -1px;
-}
-a.ui-combo-button .ui-button-text {
- padding: 0;
-}
+++ /dev/null
-/*jshint trailing:true, indent:4*/
-/*
- * django-selectable UI widget
- * Source: https://github.com/mlavin/django-selectable
- * Docs: http://django-selectable.readthedocs.org/
- *
- * Depends:
- * - jQuery 1.7+
- * - jQuery UI 1.8 widget factory
- *
- * Copyright 2010-2014, Mark Lavin
- * BSD License
- *
-*/
-(function ($) {
-
- $.widget("ui.djselectable", $.ui.autocomplete, {
-
- options: {
- removeIcon: "ui-icon-close",
- comboboxIcon: "ui-icon-triangle-1-s",
- defaultClasses: {
- "text": "ui-widget ui-widget-content ui-corner-all",
- "combobox": "ui-widget ui-widget-content ui-corner-left ui-combo-input"
- },
- prepareQuery: null,
- highlightMatch: true,
- formatLabel: null
- },
-
- _initDeck: function () {
- /* Create list display for currently selected items for multi-select */
- var self = this;
- var data = $(this.element).data();
- var style = data.selectablePosition || data['selectable-position'] || 'bottom';
- this.deck = $('<ul>').addClass('ui-widget selectable-deck selectable-deck-' + style);
- if (style === 'bottom' || style === 'bottom-inline') {
- $(this.element).after(this.deck);
- } else {
- $(this.element).before(this.deck);
- }
- $(self.hiddenMultipleSelector).each(function (i, input) {
- self._addDeckItem(input);
- });
- },
-
- _addDeckItem: function (input) {
- /* Add new deck list item from a given hidden input */
- var self = this,
- li = $('<li>').addClass('selectable-deck-item'),
- item = {element: self.element, input: input, wrapper: li, deck: self.deck},
- button;
- li.html($(input).attr('title'));
- if (self._trigger("add", null, item) === false) {
- input.remove();
- } else {
- button = this._removeButtonTemplate(item);
- button.click(function (e) {
- e.preventDefault();
- if (self._trigger("remove", e, item) !== false) {
- $(input).remove();
- li.remove();
- }
- });
- li.append(button).appendTo(this.deck);
- }
- },
-
- _removeButtonTemplate: function (item) {
- var options = {
- icons: {
- primary: this.options.removeIcon
- },
- text: false,
- disabled: this.disabled
- },
- button = $('<a>')
- .attr('href', '#')
- .addClass('selectable-deck-remove')
- .button(options);
- return button;
- },
-
- select: function (item, event) {
- /* Trigger selection of a given item.
- Item should contain two properties: id and value
- Event is the original select event if there is one.
- Event should not be passed if triggered manually.
- */
- var $input = $(this.element);
- $input.removeClass('ui-state-error');
- this._setHidden(item);
- if (item) {
- if (this.allowMultiple) {
- $input.val("");
- this.term = "";
- if ($(this.hiddenMultipleSelector + '[value="' + item.id + '"]').length === 0) {
- var newInput = $('<input />', {
- 'type': 'hidden',
- 'name': this.hiddenName,
- 'value': item.id,
- 'title': item.value,
- 'data-selectable-type': 'hidden-multiple'
- });
- $input.after(newInput);
- this._addDeckItem(newInput);
- }
- return false;
- } else {
- $input.val(item.value);
- var ui = {item: item};
- if (typeof(event) === 'undefined' || event.type !== "djselectableselect") {
- this.element.trigger("djselectableselect", [ui ]);
- }
- }
- }
- },
-
- _setHidden: function (item) {
- /* Set or clear single hidden input */
- var $elem = $(this.hiddenSelector);
- if (item && item.id) {
- $elem.val(item.id);
- } else {
- $elem.val("");
- }
- },
-
- _comboButtonTemplate: function (input) {
- // Add show all items button
- var options = {
- icons: {
- primary: this.options.comboboxIcon
- },
- text: false,
- disabled: this.disabled
- },
- button = $("<a>")
- .html(" ")
- .attr("tabIndex", -1)
- .attr("title", "Show All Items")
- .button(options)
- .removeClass("ui-corner-all")
- .addClass("ui-corner-right ui-button-icon ui-combo-button");
- return button;
- },
-
- _create: function () {
- /* Initialize a new selectable widget */
- var self = this,
- $input = $(this.element),
- data = $input.data(),
- options, button;
- this.url = data.selectableUrl || data['selectable-url'];
- this.allowNew = data.selectableAllowNew || data['selectable-allow-new'];
- this.allowMultiple = data.selectableMultiple || data['selectable-multiple'];
- this.textName = $input.attr('name');
- this.hiddenName = this.textName.replace(new RegExp('_0$'), '_1');
- this.hiddenSelector = ':input[data-selectable-type=hidden][name=' + this.hiddenName + ']';
- this.hiddenMultipleSelector = ':input[data-selectable-type=hidden-multiple][name=' + this.hiddenName + ']';
- this.selectableType = data.selectableType || data['selectable-type'];
- this.disabled = $input.prop('disabled');
- if (this.allowMultiple) {
- this.allowNew = false;
- $input.val("");
- this._initDeck();
- }
- options = data.selectableOptions || data['selectable-options'];
- if (options) {
- this._setOptions(options);
- }
- // Call super-create
- // This could be replaced by this._super() with jQuery UI 1.9
- $.ui.autocomplete.prototype._create.call(this);
- $input.addClass(this.options.defaultClasses[this.selectableType]);
- // Additional work for combobox widgets
- if (this.selectableType === 'combobox') {
- // Add show all items button
- button = this._comboButtonTemplate($input);
- button.insertAfter($input).click(function (e) {
- e.preventDefault();
- // close if already visible
- if (self.widget().is(":visible")) {
- self.close();
- }
- // pass empty string as value to search for, displaying all results
- self._search("");
- $input.focus();
- });
- }
- },
-
- // Override the default source creation
- _initSource: function () {
- var self = this,
- $input = $(this.element);
- this.source = function dataSource(request, response) {
- /* Custom data source to uses the lookup url with pagination
- Adds hook for adjusting query parameters.
- Includes timestamp to prevent browser caching the lookup. */
- var now = new Date().getTime(),
- query = {term: request.term, timestamp: now},
- page = $input.data("page");
- if (self.options.prepareQuery) {
- self.options.prepareQuery.apply(self, [query]);
- }
- if (page) {
- query.page = page;
- }
- function unwrapResponse(data) {
- var results = data.data,
- meta = data.meta;
- if (meta.next_page && meta.more) {
- results.push({
- id: '',
- value: '',
- label: meta.more,
- page: meta.next_page,
- term: request.term
- });
- }
- return response(results);
- }
- $.getJSON(self.url, query, unwrapResponse);
- };
- },
- // Override the default auto-complete render.
- _renderItem: function (ul, item) {
- /* Adds hook for additional formatting, allows HTML in the label,
- highlights term matches and handles pagination. */
- var label = item.label,
- self = this,
- $input = $(this.element),
- re, html, li;
- if (this.options.formatLabel && !item.page) {
- label = this.options.formatLabel.apply(this, [label, item]);
- }
- if (this.options.highlightMatch && this.term && !item.page) {
- re = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" +
- $.ui.autocomplete.escapeRegex(this.term) +
- ")(?![^<>]*>)(?![^&;]+;)", "gi");
- if (label.html) {
- html = label.html();
- html = html.replace(re, "<span class='highlight'>$1</span>");
- label.html(html);
- } else {
- label = label.replace(re, "<span class='highlight'>$1</span>");
- }
- }
- li = $("<li></li>")
- .data("item.autocomplete", item)
- .append($("<a></a>").append(label))
- .appendTo(ul);
- if (item.page) {
- li.addClass('selectable-paginator');
- }
- return li;
- },
- // Override the default auto-complete suggest.
- _suggest: function (items) {
- /* Needed for handling pagination links */
- var $input = $(this.element),
- page = $input.data('page'),
- ul = this.menu.element;
- if (page) {
- $('.selectable-paginator', ul).remove();
- } else {
- ul.empty();
- }
- $input.data('page', null);
- ul.css("zIndex", $input.css("zIndex") + 1);
- this._renderMenu(ul, items);
- // jQuery UI menu does not define deactivate
- if (this.menu.deactivate) {
- this.menu.deactivate();
- }
- this.menu.refresh();
- // size and position menu
- ul.show();
- this._resizeMenu();
- ul.position($.extend({of: this.element}, this.options.position));
- if (this.options.autoFocus) {
- this.menu.next(new $.Event("mouseover"));
- } else if (page) {
- $input.focus();
- }
- },
- // Override default trigger for additional change/select logic
- _trigger: function (type, event, data) {
- var $input = $(this.element),
- self = this;
- if (type === "select") {
- $input.removeClass('ui-state-error');
- if (data.item.page) {
- $input.data("page", data.item.page);
- this._search(data.item.term);
- return false;
- }
- return this.select(data.item, event);
- } else if (type === "change") {
- $input.removeClass('ui-state-error');
- this._setHidden(data.item);
- if ($input.val() && !data.item) {
- if (!this.allowNew) {
- $input.addClass('ui-state-error');
- }
- }
- if (this.allowMultiple && !$input.hasClass('ui-state-error')) {
- $input.val("");
- this.term = "";
- }
- }
- // Call super trigger
- // This could be replaced by this._super() with jQuery UI 1.9
- return $.ui.autocomplete.prototype._trigger.apply(this, arguments);
- },
- close: function (event) {
- var page = $(this.element).data('page');
- if (page !== null) {
- return;
- }
- // Call super trigger
- // This could be replaced by this._super() with jQuery UI 1.9
- return $.ui.autocomplete.prototype.close.apply(this, arguments);
- }
- });
-
- window.bindSelectables = function (context) {
- /* Bind all selectable widgets in a given context.
- Automatically called on document.ready.
- Additional calls can be made for dynamically added widgets.
- */
- $(":input[data-selectable-type=text]", context).djselectable();
- $(":input[data-selectable-type=combobox]", context).djselectable();
- };
-
- function djangoAdminPatches() {
- /* Listen for new rows being added to the dynamic inlines.
- Requires Django 1.5+ */
- $('body').on('click', '.add-row', function (e) {
- var wrapper = $(this).parents('.inline-related'),
- newRow = $('.form-row:not(.empty-form)', wrapper).last();
- window.bindSelectables(newRow);
- });
-
- /* Monkey-patch Django's dismissAddAnotherPopup(), if defined */
- if (typeof(dismissAddAnotherPopup) !== "undefined" &&
- typeof(windowname_to_id) !== "undefined" &&
- typeof(html_unescape) !== "undefined") {
- var django_dismissAddAnotherPopup = dismissAddAnotherPopup;
- dismissAddAnotherPopup = function (win, newId, newRepr) {
- /* See if the popup came from a selectable field.
- If not, pass control to Django's code.
- If so, handle it. */
- var fieldName = windowname_to_id(win.name); /* e.g. "id_fieldname" */
- var field = $('#' + fieldName);
- var multiField = $('#' + fieldName + '_0');
- /* Check for bound selectable */
- var singleWidget = field.data('djselectable');
- var multiWidget = multiField.data('djselectable');
- if (singleWidget || multiWidget) {
- // newId and newRepr are expected to have previously been escaped by
- // django.utils.html.escape.
- var item = {
- id: html_unescape(newId),
- value: html_unescape(newRepr)
- };
- if (singleWidget) {
- field.djselectable('select', item);
- }
- if (multiWidget) {
- multiField.djselectable('select', item);
- }
- win.close();
- } else {
- /* Not ours, pass on to original function. */
- return django_dismissAddAnotherPopup(win, newId, newRepr);
- }
- };
- }
- }
-
- $(document).ready(function () {
- // Patch the django admin JS
- if (typeof(djselectableAdminPatch) === "undefined" || djselectableAdminPatch) {
- djangoAdminPatches();
- }
- // Bind existing widgets on document ready
- if (typeof(djselectableAutoLoad) === "undefined" || djselectableAutoLoad) {
- window.bindSelectables('body');
- }
- });
-})(jQuery || grp.jQuery);
+++ /dev/null
-<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/{{ version }}/themes/{{ theme }}/jquery-ui.css" type="text/css">
+++ /dev/null
-{% if version %}<script src="//ajax.googleapis.com/ajax/libs/jquery/{{ version }}/jquery.min.js"></script>{% endif %}
-{% if ui %}<script src="//ajax.googleapis.com/ajax/libs/jqueryui/{{ ui }}/jquery-ui.js"></script>{% endif %}
+++ /dev/null
-from __future__ import unicode_literals
-
-from django import template
-
-register = template.Library()
-
-
-def include_jquery_libs(version='1.12.4', ui='1.11.4'):
- return {'version': version, 'ui': ui}
-
-
-def include_ui_theme(theme='smoothness', version='1.11.4'):
- return {'theme': theme, 'version': version}
+++ /dev/null
-from django.db import models
-from django.utils.encoding import python_2_unicode_compatible
-
-from ..base import ModelLookup
-from ..registry import registry
-
-
-@python_2_unicode_compatible
-class Thing(models.Model):
- name = models.CharField(max_length=100)
- description = models.CharField(max_length=100)
-
- def __str__(self):
- return self.name
-
- class Meta:
- ordering = ['id']
-
-
-@python_2_unicode_compatible
-class OtherThing(models.Model):
- name = models.CharField(max_length=100)
- thing = models.ForeignKey(Thing, on_delete=models.CASCADE)
-
- def __str__(self):
- return self.name
-
-
-@python_2_unicode_compatible
-class ManyThing(models.Model):
- name = models.CharField(max_length=100)
- things = models.ManyToManyField(Thing)
-
- def __str__(self):
- return self.name
-
-
-class ThingLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
-
-registry.register(ThingLookup)
+++ /dev/null
-from __future__ import unicode_literals
-
-import random
-import string
-from collections import defaultdict
-
-
-from django.test import TestCase, override_settings
-from django.test.html import parse_html
-
-from . import Thing
-from ..base import ModelLookup
-
-
-def parsed_inputs(html):
- "Returns a dictionary mapping name --> node of inputs found in the HTML."
- node = parse_html(html)
- inputs = {}
- for field in [c for c in node.children if c.name == 'input']:
- name = dict(field.attributes)['name']
- current = inputs.get(name, [])
- current.append(field)
- inputs[name] = current
- return inputs
-
-
-@override_settings(ROOT_URLCONF='selectable.tests.urls')
-class BaseSelectableTestCase(TestCase):
-
- def get_random_string(self, length=10):
- return ''.join(random.choice(string.ascii_letters) for x in range(length))
-
- def create_thing(self, data=None):
- data = data or {}
- defaults = {
- 'name': self.get_random_string(),
- 'description': self.get_random_string(),
- }
- defaults.update(data)
- return Thing.objects.create(**defaults)
-
-
-class SimpleModelLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
-
-def parsed_widget_attributes(widget):
- """
- Get a dictionary-like object containing all HTML attributes
- of the rendered widget.
-
- Lookups on this object raise ValueError if there is more than one attribute
- of the given name in the HTML, and they have different values.
- """
- # For the tests that use this, it generally doesn't matter what the value
- # is, so we supply anything.
- rendered = widget.render('a_name', 'a_value')
- return AttrMap(rendered)
-
-
-class AttrMap(object):
- def __init__(self, html):
- dom = parse_html(html)
- self._attrs = defaultdict(set)
- self._build_attr_map(dom)
-
- def _build_attr_map(self, dom):
- for node in _walk_nodes(dom):
- if node.attributes is not None:
- for (k, v) in node.attributes:
- self._attrs[k].add(v)
-
- def __contains__(self, key):
- return key in self._attrs and len(self._attrs[key]) > 0
-
- def __getitem__(self, key):
- if key not in self:
- raise KeyError(key)
- vals = self._attrs[key]
- if len(vals) > 1:
- raise ValueError("More than one value for attribute {0}: {1}".
- format(key, ", ".join(vals)))
- else:
- return list(vals)[0]
-
-
-def _walk_nodes(dom):
- yield dom
- for child in dom.children:
- for item in _walk_nodes(child):
- yield item
+++ /dev/null
-/* Test utility functions */
-(function ($) {
-
- window.createTextComplete = function (name, attrs) {
- var inputAttrs = {
- 'name': name,
- 'data-selectable-type': 'text',
- 'data-selectable-url': '/lookup/core-fruitlookup/',
- 'type': 'text'
- }, finalAttrs = $.extend({}, inputAttrs, attrs || {});
- return $('<input>', finalAttrs);
- };
-
- window.createTextCombobox = function (name, attrs) {
- // Force change of the name and type
- var inputAttrs = $.extend({
- 'data-selectable-type': 'combobox'
- }, attrs || {});
- return window.createTextComplete(name, inputAttrs);
- };
-
- window.createTextSelect = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0'
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden',
- 'type': 'hidden'
- };
- textInput = window.createTextComplete(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.createComboboxSelect = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0'
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden',
- 'type': 'hidden'
- };
- textInput = window.createTextCombobox(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.createTextSelectMultiple = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0',
- 'data-selectable-multiple': true,
- 'data-selectable-allow-new': false
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden-multiple',
- 'type': 'hidden'
- };
- textInput = window.createTextComplete(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.createComboboxSelectMultiple = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0',
- 'data-selectable-multiple': true,
- 'data-selectable-allow-new': false
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden-multiple',
- 'type': 'hidden'
- };
- textInput = window.createTextCombobox(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.simpleLookupResponse = function () {
- var meta = {
- "term": "ap",
- "limit": 25,
- "page": 1,
- "more": "Show more results"
- }, data = [
- {"id": 1, "value": "Apple", "label": "Apple"},
- {"id": 3, "value": "Grape", "label": "Grape"}
- ];
- return {"meta": meta, "data": data};
- };
-
- window.paginatedLookupResponse = function () {
- var meta = {
- "term": "ap",
- "limit": 2,
- "page": 1,
- "more": "Show more results"
- }, data = [
- {"id": 1, "value": "Apple", "label": "Apple"},
- {"id": 3, "value": "Grape", "label": "Grape"},
- {"id": null, "page": 2, "label": "Show more results"}
- ];
- return {"meta": meta, "data": data};
- };
-})(jQuery);
\ No newline at end of file
+++ /dev/null
-<!DOCTYPE html>
-<html>
-<head>
- <meta charset="utf-8">
- <title>Django Selectable Test Suite</title>
- <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.11.0.css" media="screen">
- <script src="jquery-loader.js"></script>
- <script src="http://code.jquery.com/qunit/qunit-1.11.0.js"></script>
- <script src="sinon-1.5.2.js"></script>
- <script src="helpers.js"></script>
- <script>QUnit.config.autostart = false;</script>
- <script data-main="main" src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.4/require.min.js"></script>
-</head>
-<body>
- <h1 id="qunit-header">Django Selectable Test Suite</h1>
- <h2 id="qunit-banner"></h2>
- <div id="qunit-testrunner-toolbar"></div>
- <h2 id="qunit-userAgent"></h2>
- <ol id="qunit-tests"></ol>
- <div id="qunit-fixture"></div>
-</body>
-</html>
+++ /dev/null
-(function() {
- // Get any jquery=___ param from the query string.
- var jqversion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/);
- var uiversion = location.search.match(/[?&]ui=(.*?)(?=&|$)/);
- var path;
- window.jqversion = jqversion && jqversion[1] || '1.11.2';
- window.uiversion = uiversion && uiversion[1] || '1.11.4';
- jqpath = 'http://code.jquery.com/jquery-' + window.jqversion + '.js';
- uipath = 'http://code.jquery.com/ui/' + window.uiversion + '/jquery-ui.js';
- // This is the only time I'll ever use document.write, I promise!
- document.write('<script src="' + jqpath + '"></script>');
- document.write('<script src="' + uipath + '"></script>');
-}());
+++ /dev/null
-/*global require, QUnit*/
-
-require.config({
- baseUrl: '../../static/selectable/js/',
- paths: {
- selectable: 'jquery.dj.selectable'
- },
- shim: {
- selectable: {
- exports: 'jQuery'
- }
- }
-});
-
-require(['test-methods.js', 'test-events.js', 'test-options.js'], function () {
- //Tests loaded, run Tests
- QUnit.load();
- QUnit.start();
-});
\ No newline at end of file
+++ /dev/null
-/**
- * Sinon.JS 1.5.2, 2012/11/27
- *
- * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
- *
- * (The BSD License)
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * * Neither the name of Christian Johansen nor the names of his contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-var sinon = (function () {
-"use strict";
-
-var buster = (function (setTimeout, B) {
- var isNode = typeof require == "function" && typeof module == "object";
- var div = typeof document != "undefined" && document.createElement("div");
- var F = function () {};
-
- var buster = {
- bind: function bind(obj, methOrProp) {
- var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp;
- var args = Array.prototype.slice.call(arguments, 2);
- return function () {
- var allArgs = args.concat(Array.prototype.slice.call(arguments));
- return method.apply(obj, allArgs);
- };
- },
-
- partial: function partial(fn) {
- var args = [].slice.call(arguments, 1);
- return function () {
- return fn.apply(this, args.concat([].slice.call(arguments)));
- };
- },
-
- create: function create(object) {
- F.prototype = object;
- return new F();
- },
-
- extend: function extend(target) {
- if (!target) { return; }
- for (var i = 1, l = arguments.length, prop; i < l; ++i) {
- for (prop in arguments[i]) {
- target[prop] = arguments[i][prop];
- }
- }
- return target;
- },
-
- nextTick: function nextTick(callback) {
- if (typeof process != "undefined" && process.nextTick) {
- return process.nextTick(callback);
- }
- setTimeout(callback, 0);
- },
-
- functionName: function functionName(func) {
- if (!func) return "";
- if (func.displayName) return func.displayName;
- if (func.name) return func.name;
- var matches = func.toString().match(/function\s+([^\(]+)/m);
- return matches && matches[1] || "";
- },
-
- isNode: function isNode(obj) {
- if (!div) return false;
- try {
- obj.appendChild(div);
- obj.removeChild(div);
- } catch (e) {
- return false;
- }
- return true;
- },
-
- isElement: function isElement(obj) {
- return obj && obj.nodeType === 1 && buster.isNode(obj);
- },
-
- isArray: function isArray(arr) {
- return Object.prototype.toString.call(arr) == "[object Array]";
- },
-
- flatten: function flatten(arr) {
- var result = [], arr = arr || [];
- for (var i = 0, l = arr.length; i < l; ++i) {
- result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]);
- }
- return result;
- },
-
- each: function each(arr, callback) {
- for (var i = 0, l = arr.length; i < l; ++i) {
- callback(arr[i]);
- }
- },
-
- map: function map(arr, callback) {
- var results = [];
- for (var i = 0, l = arr.length; i < l; ++i) {
- results.push(callback(arr[i]));
- }
- return results;
- },
-
- parallel: function parallel(fns, callback) {
- function cb(err, res) {
- if (typeof callback == "function") {
- callback(err, res);
- callback = null;
- }
- }
- if (fns.length == 0) { return cb(null, []); }
- var remaining = fns.length, results = [];
- function makeDone(num) {
- return function done(err, result) {
- if (err) { return cb(err); }
- results[num] = result;
- if (--remaining == 0) { cb(null, results); }
- };
- }
- for (var i = 0, l = fns.length; i < l; ++i) {
- fns[i](makeDone(i));
- }
- },
-
- series: function series(fns, callback) {
- function cb(err, res) {
- if (typeof callback == "function") {
- callback(err, res);
- }
- }
- var remaining = fns.slice();
- var results = [];
- function callNext() {
- if (remaining.length == 0) return cb(null, results);
- var promise = remaining.shift()(next);
- if (promise && typeof promise.then == "function") {
- promise.then(buster.partial(next, null), next);
- }
- }
- function next(err, result) {
- if (err) return cb(err);
- results.push(result);
- callNext();
- }
- callNext();
- },
-
- countdown: function countdown(num, done) {
- return function () {
- if (--num == 0) done();
- };
- }
- };
-
- if (typeof process === "object" &&
- typeof require === "function" && typeof module === "object") {
- var crypto = require("crypto");
- var path = require("path");
-
- buster.tmpFile = function (fileName) {
- var hashed = crypto.createHash("sha1");
- hashed.update(fileName);
- var tmpfileName = hashed.digest("hex");
-
- if (process.platform == "win32") {
- return path.join(process.env["TEMP"], tmpfileName);
- } else {
- return path.join("/tmp", tmpfileName);
- }
- };
- }
-
- if (Array.prototype.some) {
- buster.some = function (arr, fn, thisp) {
- return arr.some(fn, thisp);
- };
- } else {
- // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
- buster.some = function (arr, fun, thisp) {
- if (arr == null) { throw new TypeError(); }
- arr = Object(arr);
- var len = arr.length >>> 0;
- if (typeof fun !== "function") { throw new TypeError(); }
-
- for (var i = 0; i < len; i++) {
- if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) {
- return true;
- }
- }
-
- return false;
- };
- }
-
- if (Array.prototype.filter) {
- buster.filter = function (arr, fn, thisp) {
- return arr.filter(fn, thisp);
- };
- } else {
- // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
- buster.filter = function (fn, thisp) {
- if (this == null) { throw new TypeError(); }
-
- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fn != "function") { throw new TypeError(); }
-
- var res = [];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- var val = t[i]; // in case fun mutates this
- if (fn.call(thisp, val, i, t)) { res.push(val); }
- }
- }
-
- return res;
- };
- }
-
- if (isNode) {
- module.exports = buster;
- buster.eventEmitter = require("./buster-event-emitter");
- Object.defineProperty(buster, "defineVersionGetter", {
- get: function () {
- return require("./define-version-getter");
- }
- });
- }
-
- return buster.extend(B || {}, buster);
-}(setTimeout, buster));
-if (typeof buster === "undefined") {
- var buster = {};
-}
-
-if (typeof module === "object" && typeof require === "function") {
- buster = require("buster-core");
-}
-
-buster.format = buster.format || {};
-buster.format.excludeConstructors = ["Object", /^.$/];
-buster.format.quoteStrings = true;
-
-buster.format.ascii = (function () {
-
- var hasOwn = Object.prototype.hasOwnProperty;
-
- var specialObjects = [];
- if (typeof global != "undefined") {
- specialObjects.push({ obj: global, value: "[object global]" });
- }
- if (typeof document != "undefined") {
- specialObjects.push({ obj: document, value: "[object HTMLDocument]" });
- }
- if (typeof window != "undefined") {
- specialObjects.push({ obj: window, value: "[object Window]" });
- }
-
- function keys(object) {
- var k = Object.keys && Object.keys(object) || [];
-
- if (k.length == 0) {
- for (var prop in object) {
- if (hasOwn.call(object, prop)) {
- k.push(prop);
- }
- }
- }
-
- return k.sort();
- }
-
- function isCircular(object, objects) {
- if (typeof object != "object") {
- return false;
- }
-
- for (var i = 0, l = objects.length; i < l; ++i) {
- if (objects[i] === object) {
- return true;
- }
- }
-
- return false;
- }
-
- function ascii(object, processed, indent) {
- if (typeof object == "string") {
- var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings;
- return processed || quote ? '"' + object + '"' : object;
- }
-
- if (typeof object == "function" && !(object instanceof RegExp)) {
- return ascii.func(object);
- }
-
- processed = processed || [];
-
- if (isCircular(object, processed)) {
- return "[Circular]";
- }
-
- if (Object.prototype.toString.call(object) == "[object Array]") {
- return ascii.array.call(this, object, processed);
- }
-
- if (!object) {
- return "" + object;
- }
-
- if (buster.isElement(object)) {
- return ascii.element(object);
- }
-
- if (typeof object.toString == "function" &&
- object.toString !== Object.prototype.toString) {
- return object.toString();
- }
-
- for (var i = 0, l = specialObjects.length; i < l; i++) {
- if (object === specialObjects[i].obj) {
- return specialObjects[i].value;
- }
- }
-
- return ascii.object.call(this, object, processed, indent);
- }
-
- ascii.func = function (func) {
- return "function " + buster.functionName(func) + "() {}";
- };
-
- ascii.array = function (array, processed) {
- processed = processed || [];
- processed.push(array);
- var pieces = [];
-
- for (var i = 0, l = array.length; i < l; ++i) {
- pieces.push(ascii.call(this, array[i], processed));
- }
-
- return "[" + pieces.join(", ") + "]";
- };
-
- ascii.object = function (object, processed, indent) {
- processed = processed || [];
- processed.push(object);
- indent = indent || 0;
- var pieces = [], properties = keys(object), prop, str, obj;
- var is = "";
- var length = 3;
-
- for (var i = 0, l = indent; i < l; ++i) {
- is += " ";
- }
-
- for (i = 0, l = properties.length; i < l; ++i) {
- prop = properties[i];
- obj = object[prop];
-
- if (isCircular(obj, processed)) {
- str = "[Circular]";
- } else {
- str = ascii.call(this, obj, processed, indent + 2);
- }
-
- str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str;
- length += str.length;
- pieces.push(str);
- }
-
- var cons = ascii.constructorName.call(this, object);
- var prefix = cons ? "[" + cons + "] " : ""
-
- return (length + indent) > 80 ?
- prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" :
- prefix + "{ " + pieces.join(", ") + " }";
- };
-
- ascii.element = function (element) {
- var tagName = element.tagName.toLowerCase();
- var attrs = element.attributes, attribute, pairs = [], attrName;
-
- for (var i = 0, l = attrs.length; i < l; ++i) {
- attribute = attrs.item(i);
- attrName = attribute.nodeName.toLowerCase().replace("html:", "");
-
- if (attrName == "contenteditable" && attribute.nodeValue == "inherit") {
- continue;
- }
-
- if (!!attribute.nodeValue) {
- pairs.push(attrName + "=\"" + attribute.nodeValue + "\"");
- }
- }
-
- var formatted = "<" + tagName + (pairs.length > 0 ? " " : "");
- var content = element.innerHTML;
-
- if (content.length > 20) {
- content = content.substr(0, 20) + "[...]";
- }
-
- var res = formatted + pairs.join(" ") + ">" + content + "</" + tagName + ">";
-
- return res.replace(/ contentEditable="inherit"/, "");
- };
-
- ascii.constructorName = function (object) {
- var name = buster.functionName(object && object.constructor);
- var excludes = this.excludeConstructors || buster.format.excludeConstructors || [];
-
- for (var i = 0, l = excludes.length; i < l; ++i) {
- if (typeof excludes[i] == "string" && excludes[i] == name) {
- return "";
- } else if (excludes[i].test && excludes[i].test(name)) {
- return "";
- }
- }
-
- return name;
- };
-
- return ascii;
-}());
-
-if (typeof module != "undefined") {
- module.exports = buster.format;
-}
-/*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/
-/*global module, require, __dirname, document*/
-/**
- * Sinon core utilities. For internal use only.
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-var sinon = (function (buster) {
- var div = typeof document != "undefined" && document.createElement("div");
- var hasOwn = Object.prototype.hasOwnProperty;
-
- function isDOMNode(obj) {
- var success = false;
-
- try {
- obj.appendChild(div);
- success = div.parentNode == obj;
- } catch (e) {
- return false;
- } finally {
- try {
- obj.removeChild(div);
- } catch (e) {
- // Remove failed, not much we can do about that
- }
- }
-
- return success;
- }
-
- function isElement(obj) {
- return div && obj && obj.nodeType === 1 && isDOMNode(obj);
- }
-
- function isFunction(obj) {
- return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply);
- }
-
- function mirrorProperties(target, source) {
- for (var prop in source) {
- if (!hasOwn.call(target, prop)) {
- target[prop] = source[prop];
- }
- }
- }
-
- var sinon = {
- wrapMethod: function wrapMethod(object, property, method) {
- if (!object) {
- throw new TypeError("Should wrap property of object");
- }
-
- if (typeof method != "function") {
- throw new TypeError("Method wrapper should be function");
- }
-
- var wrappedMethod = object[property];
-
- if (!isFunction(wrappedMethod)) {
- throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
- property + " as function");
- }
-
- if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
- throw new TypeError("Attempted to wrap " + property + " which is already wrapped");
- }
-
- if (wrappedMethod.calledBefore) {
- var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
- throw new TypeError("Attempted to wrap " + property + " which is already " + verb);
- }
-
- // IE 8 does not support hasOwnProperty on the window object.
- var owned = hasOwn.call(object, property);
- object[property] = method;
- method.displayName = property;
-
- method.restore = function () {
- // For prototype properties try to reset by delete first.
- // If this fails (ex: localStorage on mobile safari) then force a reset
- // via direct assignment.
- if (!owned) {
- delete object[property];
- }
- if (object[property] === method) {
- object[property] = wrappedMethod;
- }
- };
-
- method.restore.sinon = true;
- mirrorProperties(method, wrappedMethod);
-
- return method;
- },
-
- extend: function extend(target) {
- for (var i = 1, l = arguments.length; i < l; i += 1) {
- for (var prop in arguments[i]) {
- if (arguments[i].hasOwnProperty(prop)) {
- target[prop] = arguments[i][prop];
- }
-
- // DONT ENUM bug, only care about toString
- if (arguments[i].hasOwnProperty("toString") &&
- arguments[i].toString != target.toString) {
- target.toString = arguments[i].toString;
- }
- }
- }
-
- return target;
- },
-
- create: function create(proto) {
- var F = function () {};
- F.prototype = proto;
- return new F();
- },
-
- deepEqual: function deepEqual(a, b) {
- if (sinon.match && sinon.match.isMatcher(a)) {
- return a.test(b);
- }
- if (typeof a != "object" || typeof b != "object") {
- return a === b;
- }
-
- if (isElement(a) || isElement(b)) {
- return a === b;
- }
-
- if (a === b) {
- return true;
- }
-
- if ((a === null && b !== null) || (a !== null && b === null)) {
- return false;
- }
-
- var aString = Object.prototype.toString.call(a);
- if (aString != Object.prototype.toString.call(b)) {
- return false;
- }
-
- if (aString == "[object Array]") {
- if (a.length !== b.length) {
- return false;
- }
-
- for (var i = 0, l = a.length; i < l; i += 1) {
- if (!deepEqual(a[i], b[i])) {
- return false;
- }
- }
-
- return true;
- }
-
- var prop, aLength = 0, bLength = 0;
-
- for (prop in a) {
- aLength += 1;
-
- if (!deepEqual(a[prop], b[prop])) {
- return false;
- }
- }
-
- for (prop in b) {
- bLength += 1;
- }
-
- if (aLength != bLength) {
- return false;
- }
-
- return true;
- },
-
- functionName: function functionName(func) {
- var name = func.displayName || func.name;
-
- // Use function decomposition as a last resort to get function
- // name. Does not rely on function decomposition to work - if it
- // doesn't debugging will be slightly less informative
- // (i.e. toString will say 'spy' rather than 'myFunc').
- if (!name) {
- var matches = func.toString().match(/function ([^\s\(]+)/);
- name = matches && matches[1];
- }
-
- return name;
- },
-
- functionToString: function toString() {
- if (this.getCall && this.callCount) {
- var thisValue, prop, i = this.callCount;
-
- while (i--) {
- thisValue = this.getCall(i).thisValue;
-
- for (prop in thisValue) {
- if (thisValue[prop] === this) {
- return prop;
- }
- }
- }
- }
-
- return this.displayName || "sinon fake";
- },
-
- getConfig: function (custom) {
- var config = {};
- custom = custom || {};
- var defaults = sinon.defaultConfig;
-
- for (var prop in defaults) {
- if (defaults.hasOwnProperty(prop)) {
- config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];
- }
- }
-
- return config;
- },
-
- format: function (val) {
- return "" + val;
- },
-
- defaultConfig: {
- injectIntoThis: true,
- injectInto: null,
- properties: ["spy", "stub", "mock", "clock", "server", "requests"],
- useFakeTimers: true,
- useFakeServer: true
- },
-
- timesInWords: function timesInWords(count) {
- return count == 1 && "once" ||
- count == 2 && "twice" ||
- count == 3 && "thrice" ||
- (count || 0) + " times";
- },
-
- calledInOrder: function (spies) {
- for (var i = 1, l = spies.length; i < l; i++) {
- if (!spies[i - 1].calledBefore(spies[i])) {
- return false;
- }
- }
-
- return true;
- },
-
- orderByFirstCall: function (spies) {
- return spies.sort(function (a, b) {
- // uuid, won't ever be equal
- var aCall = a.getCall(0);
- var bCall = b.getCall(0);
- var aId = aCall && aCall.callId || -1;
- var bId = bCall && bCall.callId || -1;
-
- return aId < bId ? -1 : 1;
- });
- },
-
- log: function () {},
-
- logError: function (label, err) {
- var msg = label + " threw exception: "
- sinon.log(msg + "[" + err.name + "] " + err.message);
- if (err.stack) { sinon.log(err.stack); }
-
- setTimeout(function () {
- err.message = msg + err.message;
- throw err;
- }, 0);
- },
-
- typeOf: function (value) {
- if (value === null) {
- return "null";
- }
- else if (value === undefined) {
- return "undefined";
- }
- var string = Object.prototype.toString.call(value);
- return string.substring(8, string.length - 1).toLowerCase();
- }
- };
-
- var isNode = typeof module == "object" && typeof require == "function";
-
- if (isNode) {
- try {
- buster = { format: require("buster-format") };
- } catch (e) {}
- module.exports = sinon;
- module.exports.spy = require("./sinon/spy");
- module.exports.stub = require("./sinon/stub");
- module.exports.mock = require("./sinon/mock");
- module.exports.collection = require("./sinon/collection");
- module.exports.assert = require("./sinon/assert");
- module.exports.sandbox = require("./sinon/sandbox");
- module.exports.test = require("./sinon/test");
- module.exports.testCase = require("./sinon/test_case");
- module.exports.assert = require("./sinon/assert");
- module.exports.match = require("./sinon/match");
- }
-
- if (buster) {
- var formatter = sinon.create(buster.format);
- formatter.quoteStrings = false;
- sinon.format = function () {
- return formatter.ascii.apply(formatter, arguments);
- };
- } else if (isNode) {
- try {
- var util = require("util");
- sinon.format = function (value) {
- return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value;
- };
- } catch (e) {
- /* Node, but no util module - would be very old, but better safe than
- sorry */
- }
- }
-
- return sinon;
-}(typeof buster == "object" && buster));
-
-/* @depend ../sinon.js */
-/*jslint eqeqeq: false, onevar: false, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Match functions
- *
- * @license BSD
- *
- * Copyright (c) 2012 Maximilian Antoni
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function assertType(value, type, name) {
- var actual = sinon.typeOf(value);
- if (actual !== type) {
- throw new TypeError("Expected type of " + name + " to be " +
- type + ", but was " + actual);
- }
- }
-
- var matcher = {
- toString: function () {
- return this.message;
- }
- };
-
- function isMatcher(object) {
- return matcher.isPrototypeOf(object);
- }
-
- function matchObject(expectation, actual) {
- if (actual === null || actual === undefined) {
- return false;
- }
- for (var key in expectation) {
- if (expectation.hasOwnProperty(key)) {
- var exp = expectation[key];
- var act = actual[key];
- if (match.isMatcher(exp)) {
- if (!exp.test(act)) {
- return false;
- }
- } else if (sinon.typeOf(exp) === "object") {
- if (!matchObject(exp, act)) {
- return false;
- }
- } else if (!sinon.deepEqual(exp, act)) {
- return false;
- }
- }
- }
- return true;
- }
-
- matcher.or = function (m2) {
- if (!isMatcher(m2)) {
- throw new TypeError("Matcher expected");
- }
- var m1 = this;
- var or = sinon.create(matcher);
- or.test = function (actual) {
- return m1.test(actual) || m2.test(actual);
- };
- or.message = m1.message + ".or(" + m2.message + ")";
- return or;
- };
-
- matcher.and = function (m2) {
- if (!isMatcher(m2)) {
- throw new TypeError("Matcher expected");
- }
- var m1 = this;
- var and = sinon.create(matcher);
- and.test = function (actual) {
- return m1.test(actual) && m2.test(actual);
- };
- and.message = m1.message + ".and(" + m2.message + ")";
- return and;
- };
-
- var match = function (expectation, message) {
- var m = sinon.create(matcher);
- var type = sinon.typeOf(expectation);
- switch (type) {
- case "object":
- if (typeof expectation.test === "function") {
- m.test = function (actual) {
- return expectation.test(actual) === true;
- };
- m.message = "match(" + sinon.functionName(expectation.test) + ")";
- return m;
- }
- var str = [];
- for (var key in expectation) {
- if (expectation.hasOwnProperty(key)) {
- str.push(key + ": " + expectation[key]);
- }
- }
- m.test = function (actual) {
- return matchObject(expectation, actual);
- };
- m.message = "match(" + str.join(", ") + ")";
- break;
- case "number":
- m.test = function (actual) {
- return expectation == actual;
- };
- break;
- case "string":
- m.test = function (actual) {
- if (typeof actual !== "string") {
- return false;
- }
- return actual.indexOf(expectation) !== -1;
- };
- m.message = "match(\"" + expectation + "\")";
- break;
- case "regexp":
- m.test = function (actual) {
- if (typeof actual !== "string") {
- return false;
- }
- return expectation.test(actual);
- };
- break;
- case "function":
- m.test = expectation;
- if (message) {
- m.message = message;
- } else {
- m.message = "match(" + sinon.functionName(expectation) + ")";
- }
- break;
- default:
- m.test = function (actual) {
- return sinon.deepEqual(expectation, actual);
- };
- }
- if (!m.message) {
- m.message = "match(" + expectation + ")";
- }
- return m;
- };
-
- match.isMatcher = isMatcher;
-
- match.any = match(function () {
- return true;
- }, "any");
-
- match.defined = match(function (actual) {
- return actual !== null && actual !== undefined;
- }, "defined");
-
- match.truthy = match(function (actual) {
- return !!actual;
- }, "truthy");
-
- match.falsy = match(function (actual) {
- return !actual;
- }, "falsy");
-
- match.same = function (expectation) {
- return match(function (actual) {
- return expectation === actual;
- }, "same(" + expectation + ")");
- };
-
- match.typeOf = function (type) {
- assertType(type, "string", "type");
- return match(function (actual) {
- return sinon.typeOf(actual) === type;
- }, "typeOf(\"" + type + "\")");
- };
-
- match.instanceOf = function (type) {
- assertType(type, "function", "type");
- return match(function (actual) {
- return actual instanceof type;
- }, "instanceOf(" + sinon.functionName(type) + ")");
- };
-
- function createPropertyMatcher(propertyTest, messagePrefix) {
- return function (property, value) {
- assertType(property, "string", "property");
- var onlyProperty = arguments.length === 1;
- var message = messagePrefix + "(\"" + property + "\"";
- if (!onlyProperty) {
- message += ", " + value;
- }
- message += ")";
- return match(function (actual) {
- if (actual === undefined || actual === null ||
- !propertyTest(actual, property)) {
- return false;
- }
- return onlyProperty || sinon.deepEqual(value, actual[property]);
- }, message);
- };
- }
-
- match.has = createPropertyMatcher(function (actual, property) {
- if (typeof actual === "object") {
- return property in actual;
- }
- return actual[property] !== undefined;
- }, "has");
-
- match.hasOwn = createPropertyMatcher(function (actual, property) {
- return actual.hasOwnProperty(property);
- }, "hasOwn");
-
- match.bool = match.typeOf("boolean");
- match.number = match.typeOf("number");
- match.string = match.typeOf("string");
- match.object = match.typeOf("object");
- match.func = match.typeOf("function");
- match.array = match.typeOf("array");
- match.regexp = match.typeOf("regexp");
- match.date = match.typeOf("date");
-
- if (commonJSModule) {
- module.exports = match;
- } else {
- sinon.match = match;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend match.js
- */
-/*jslint eqeqeq: false, onevar: false, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Spy functions
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var spyCall;
- var callId = 0;
- var push = [].push;
- var slice = Array.prototype.slice;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function spy(object, property) {
- if (!property && typeof object == "function") {
- return spy.create(object);
- }
-
- if (!object && !property) {
- return spy.create(function () {});
- }
-
- var method = object[property];
- return sinon.wrapMethod(object, property, spy.create(method));
- }
-
- sinon.extend(spy, (function () {
-
- function delegateToCalls(api, method, matchAny, actual, notCalled) {
- api[method] = function () {
- if (!this.called) {
- if (notCalled) {
- return notCalled.apply(this, arguments);
- }
- return false;
- }
-
- var currentCall;
- var matches = 0;
-
- for (var i = 0, l = this.callCount; i < l; i += 1) {
- currentCall = this.getCall(i);
-
- if (currentCall[actual || method].apply(currentCall, arguments)) {
- matches += 1;
-
- if (matchAny) {
- return true;
- }
- }
- }
-
- return matches === this.callCount;
- };
- }
-
- function matchingFake(fakes, args, strict) {
- if (!fakes) {
- return;
- }
-
- var alen = args.length;
-
- for (var i = 0, l = fakes.length; i < l; i++) {
- if (fakes[i].matches(args, strict)) {
- return fakes[i];
- }
- }
- }
-
- function incrementCallCount() {
- this.called = true;
- this.callCount += 1;
- this.notCalled = false;
- this.calledOnce = this.callCount == 1;
- this.calledTwice = this.callCount == 2;
- this.calledThrice = this.callCount == 3;
- }
-
- function createCallProperties() {
- this.firstCall = this.getCall(0);
- this.secondCall = this.getCall(1);
- this.thirdCall = this.getCall(2);
- this.lastCall = this.getCall(this.callCount - 1);
- }
-
- var vars = "a,b,c,d,e,f,g,h,i,j,k,l";
- function createProxy(func) {
- // Retain the function length:
- var p;
- if (func.length) {
- eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) +
- ") { return p.invoke(func, this, slice.call(arguments)); });");
- }
- else {
- p = function proxy() {
- return p.invoke(func, this, slice.call(arguments));
- };
- }
- return p;
- }
-
- var uuid = 0;
-
- // Public API
- var spyApi = {
- reset: function () {
- this.called = false;
- this.notCalled = true;
- this.calledOnce = false;
- this.calledTwice = false;
- this.calledThrice = false;
- this.callCount = 0;
- this.firstCall = null;
- this.secondCall = null;
- this.thirdCall = null;
- this.lastCall = null;
- this.args = [];
- this.returnValues = [];
- this.thisValues = [];
- this.exceptions = [];
- this.callIds = [];
- if (this.fakes) {
- for (var i = 0; i < this.fakes.length; i++) {
- this.fakes[i].reset();
- }
- }
- },
-
- create: function create(func) {
- var name;
-
- if (typeof func != "function") {
- func = function () {};
- } else {
- name = sinon.functionName(func);
- }
-
- var proxy = createProxy(func);
-
- sinon.extend(proxy, spy);
- delete proxy.create;
- sinon.extend(proxy, func);
-
- proxy.reset();
- proxy.prototype = func.prototype;
- proxy.displayName = name || "spy";
- proxy.toString = sinon.functionToString;
- proxy._create = sinon.spy.create;
- proxy.id = "spy#" + uuid++;
-
- return proxy;
- },
-
- invoke: function invoke(func, thisValue, args) {
- var matching = matchingFake(this.fakes, args);
- var exception, returnValue;
-
- incrementCallCount.call(this);
- push.call(this.thisValues, thisValue);
- push.call(this.args, args);
- push.call(this.callIds, callId++);
-
- try {
- if (matching) {
- returnValue = matching.invoke(func, thisValue, args);
- } else {
- returnValue = (this.func || func).apply(thisValue, args);
- }
- } catch (e) {
- push.call(this.returnValues, undefined);
- exception = e;
- throw e;
- } finally {
- push.call(this.exceptions, exception);
- }
-
- push.call(this.returnValues, returnValue);
-
- createCallProperties.call(this);
-
- return returnValue;
- },
-
- getCall: function getCall(i) {
- if (i < 0 || i >= this.callCount) {
- return null;
- }
-
- return spyCall.create(this, this.thisValues[i], this.args[i],
- this.returnValues[i], this.exceptions[i],
- this.callIds[i]);
- },
-
- calledBefore: function calledBefore(spyFn) {
- if (!this.called) {
- return false;
- }
-
- if (!spyFn.called) {
- return true;
- }
-
- return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1];
- },
-
- calledAfter: function calledAfter(spyFn) {
- if (!this.called || !spyFn.called) {
- return false;
- }
-
- return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1];
- },
-
- withArgs: function () {
- var args = slice.call(arguments);
-
- if (this.fakes) {
- var match = matchingFake(this.fakes, args, true);
-
- if (match) {
- return match;
- }
- } else {
- this.fakes = [];
- }
-
- var original = this;
- var fake = this._create();
- fake.matchingAguments = args;
- push.call(this.fakes, fake);
-
- fake.withArgs = function () {
- return original.withArgs.apply(original, arguments);
- };
-
- for (var i = 0; i < this.args.length; i++) {
- if (fake.matches(this.args[i])) {
- incrementCallCount.call(fake);
- push.call(fake.thisValues, this.thisValues[i]);
- push.call(fake.args, this.args[i]);
- push.call(fake.returnValues, this.returnValues[i]);
- push.call(fake.exceptions, this.exceptions[i]);
- push.call(fake.callIds, this.callIds[i]);
- }
- }
- createCallProperties.call(fake);
-
- return fake;
- },
-
- matches: function (args, strict) {
- var margs = this.matchingAguments;
-
- if (margs.length <= args.length &&
- sinon.deepEqual(margs, args.slice(0, margs.length))) {
- return !strict || margs.length == args.length;
- }
- },
-
- printf: function (format) {
- var spy = this;
- var args = slice.call(arguments, 1);
- var formatter;
-
- return (format || "").replace(/%(.)/g, function (match, specifyer) {
- formatter = spyApi.formatters[specifyer];
-
- if (typeof formatter == "function") {
- return formatter.call(null, spy, args);
- } else if (!isNaN(parseInt(specifyer), 10)) {
- return sinon.format(args[specifyer - 1]);
- }
-
- return "%" + specifyer;
- });
- }
- };
-
- delegateToCalls(spyApi, "calledOn", true);
- delegateToCalls(spyApi, "alwaysCalledOn", false, "calledOn");
- delegateToCalls(spyApi, "calledWith", true);
- delegateToCalls(spyApi, "calledWithMatch", true);
- delegateToCalls(spyApi, "alwaysCalledWith", false, "calledWith");
- delegateToCalls(spyApi, "alwaysCalledWithMatch", false, "calledWithMatch");
- delegateToCalls(spyApi, "calledWithExactly", true);
- delegateToCalls(spyApi, "alwaysCalledWithExactly", false, "calledWithExactly");
- delegateToCalls(spyApi, "neverCalledWith", false, "notCalledWith",
- function () { return true; });
- delegateToCalls(spyApi, "neverCalledWithMatch", false, "notCalledWithMatch",
- function () { return true; });
- delegateToCalls(spyApi, "threw", true);
- delegateToCalls(spyApi, "alwaysThrew", false, "threw");
- delegateToCalls(spyApi, "returned", true);
- delegateToCalls(spyApi, "alwaysReturned", false, "returned");
- delegateToCalls(spyApi, "calledWithNew", true);
- delegateToCalls(spyApi, "alwaysCalledWithNew", false, "calledWithNew");
- delegateToCalls(spyApi, "callArg", false, "callArgWith", function () {
- throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
- });
- spyApi.callArgWith = spyApi.callArg;
- delegateToCalls(spyApi, "yield", false, "yield", function () {
- throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
- });
- // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.
- spyApi.invokeCallback = spyApi.yield;
- delegateToCalls(spyApi, "yieldTo", false, "yieldTo", function (property) {
- throw new Error(this.toString() + " cannot yield to '" + property +
- "' since it was not yet invoked.");
- });
-
- spyApi.formatters = {
- "c": function (spy) {
- return sinon.timesInWords(spy.callCount);
- },
-
- "n": function (spy) {
- return spy.toString();
- },
-
- "C": function (spy) {
- var calls = [];
-
- for (var i = 0, l = spy.callCount; i < l; ++i) {
- push.call(calls, " " + spy.getCall(i).toString());
- }
-
- return calls.length > 0 ? "\n" + calls.join("\n") : "";
- },
-
- "t": function (spy) {
- var objects = [];
-
- for (var i = 0, l = spy.callCount; i < l; ++i) {
- push.call(objects, sinon.format(spy.thisValues[i]));
- }
-
- return objects.join(", ");
- },
-
- "*": function (spy, args) {
- var formatted = [];
-
- for (var i = 0, l = args.length; i < l; ++i) {
- push.call(formatted, sinon.format(args[i]));
- }
-
- return formatted.join(", ");
- }
- };
-
- return spyApi;
- }()));
-
- spyCall = (function () {
-
- function throwYieldError(proxy, text, args) {
- var msg = sinon.functionName(proxy) + text;
- if (args.length) {
- msg += " Received [" + slice.call(args).join(", ") + "]";
- }
- throw new Error(msg);
- }
-
- var callApi = {
- create: function create(spy, thisValue, args, returnValue, exception, id) {
- var proxyCall = sinon.create(spyCall);
- delete proxyCall.create;
- proxyCall.proxy = spy;
- proxyCall.thisValue = thisValue;
- proxyCall.args = args;
- proxyCall.returnValue = returnValue;
- proxyCall.exception = exception;
- proxyCall.callId = typeof id == "number" && id || callId++;
-
- return proxyCall;
- },
-
- calledOn: function calledOn(thisValue) {
- if (sinon.match && sinon.match.isMatcher(thisValue)) {
- return thisValue.test(this.thisValue);
- }
- return this.thisValue === thisValue;
- },
-
- calledWith: function calledWith() {
- for (var i = 0, l = arguments.length; i < l; i += 1) {
- if (!sinon.deepEqual(arguments[i], this.args[i])) {
- return false;
- }
- }
-
- return true;
- },
-
- calledWithMatch: function calledWithMatch() {
- for (var i = 0, l = arguments.length; i < l; i += 1) {
- var actual = this.args[i];
- var expectation = arguments[i];
- if (!sinon.match || !sinon.match(expectation).test(actual)) {
- return false;
- }
- }
- return true;
- },
-
- calledWithExactly: function calledWithExactly() {
- return arguments.length == this.args.length &&
- this.calledWith.apply(this, arguments);
- },
-
- notCalledWith: function notCalledWith() {
- return !this.calledWith.apply(this, arguments);
- },
-
- notCalledWithMatch: function notCalledWithMatch() {
- return !this.calledWithMatch.apply(this, arguments);
- },
-
- returned: function returned(value) {
- return sinon.deepEqual(value, this.returnValue);
- },
-
- threw: function threw(error) {
- if (typeof error == "undefined" || !this.exception) {
- return !!this.exception;
- }
-
- if (typeof error == "string") {
- return this.exception.name == error;
- }
-
- return this.exception === error;
- },
-
- calledWithNew: function calledWithNew(thisValue) {
- return this.thisValue instanceof this.proxy;
- },
-
- calledBefore: function (other) {
- return this.callId < other.callId;
- },
-
- calledAfter: function (other) {
- return this.callId > other.callId;
- },
-
- callArg: function (pos) {
- this.args[pos]();
- },
-
- callArgWith: function (pos) {
- var args = slice.call(arguments, 1);
- this.args[pos].apply(null, args);
- },
-
- "yield": function () {
- var args = this.args;
- for (var i = 0, l = args.length; i < l; ++i) {
- if (typeof args[i] === "function") {
- args[i].apply(null, slice.call(arguments));
- return;
- }
- }
- throwYieldError(this.proxy, " cannot yield since no callback was passed.", args);
- },
-
- yieldTo: function (prop) {
- var args = this.args;
- for (var i = 0, l = args.length; i < l; ++i) {
- if (args[i] && typeof args[i][prop] === "function") {
- args[i][prop].apply(null, slice.call(arguments, 1));
- return;
- }
- }
- throwYieldError(this.proxy, " cannot yield to '" + prop +
- "' since no callback was passed.", args);
- },
-
- toString: function () {
- var callStr = this.proxy.toString() + "(";
- var args = [];
-
- for (var i = 0, l = this.args.length; i < l; ++i) {
- push.call(args, sinon.format(this.args[i]));
- }
-
- callStr = callStr + args.join(", ") + ")";
-
- if (typeof this.returnValue != "undefined") {
- callStr += " => " + sinon.format(this.returnValue);
- }
-
- if (this.exception) {
- callStr += " !" + this.exception.name;
-
- if (this.exception.message) {
- callStr += "(" + this.exception.message + ")";
- }
- }
-
- return callStr;
- }
- };
- callApi.invokeCallback = callApi.yield;
- return callApi;
- }());
-
- spy.spyCall = spyCall;
-
- // This steps outside the module sandbox and will be removed
- sinon.spyCall = spyCall;
-
- if (commonJSModule) {
- module.exports = spy;
- } else {
- sinon.spy = spy;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend spy.js
- */
-/*jslint eqeqeq: false, onevar: false*/
-/*global module, require, sinon*/
-/**
- * Stub functions
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function stub(object, property, func) {
- if (!!func && typeof func != "function") {
- throw new TypeError("Custom stub should be function");
- }
-
- var wrapper;
-
- if (func) {
- wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
- } else {
- wrapper = stub.create();
- }
-
- if (!object && !property) {
- return sinon.stub.create();
- }
-
- if (!property && !!object && typeof object == "object") {
- for (var prop in object) {
- if (typeof object[prop] === "function") {
- stub(object, prop);
- }
- }
-
- return object;
- }
-
- return sinon.wrapMethod(object, property, wrapper);
- }
-
- function getChangingValue(stub, property) {
- var index = stub.callCount - 1;
- var prop = index in stub[property] ? stub[property][index] : stub[property + "Last"];
- stub[property + "Last"] = prop;
-
- return prop;
- }
-
- function getCallback(stub, args) {
- var callArgAt = getChangingValue(stub, "callArgAts");
-
- if (callArgAt < 0) {
- var callArgProp = getChangingValue(stub, "callArgProps");
-
- for (var i = 0, l = args.length; i < l; ++i) {
- if (!callArgProp && typeof args[i] == "function") {
- return args[i];
- }
-
- if (callArgProp && args[i] &&
- typeof args[i][callArgProp] == "function") {
- return args[i][callArgProp];
- }
- }
-
- return null;
- }
-
- return args[callArgAt];
- }
-
- var join = Array.prototype.join;
-
- function getCallbackError(stub, func, args) {
- if (stub.callArgAtsLast < 0) {
- var msg;
-
- if (stub.callArgPropsLast) {
- msg = sinon.functionName(stub) +
- " expected to yield to '" + stub.callArgPropsLast +
- "', but no object with such a property was passed."
- } else {
- msg = sinon.functionName(stub) +
- " expected to yield, but no callback was passed."
- }
-
- if (args.length > 0) {
- msg += " Received [" + join.call(args, ", ") + "]";
- }
-
- return msg;
- }
-
- return "argument at index " + stub.callArgAtsLast + " is not a function: " + func;
- }
-
- var nextTick = (function () {
- if (typeof process === "object" && typeof process.nextTick === "function") {
- return process.nextTick;
- } else if (typeof msSetImmediate === "function") {
- return msSetImmediate.bind(window);
- } else if (typeof setImmediate === "function") {
- return setImmediate;
- } else {
- return function (callback) {
- setTimeout(callback, 0);
- };
- }
- })();
-
- function callCallback(stub, args) {
- if (stub.callArgAts.length > 0) {
- var func = getCallback(stub, args);
-
- if (typeof func != "function") {
- throw new TypeError(getCallbackError(stub, func, args));
- }
-
- var index = stub.callCount - 1;
-
- var callbackArguments = getChangingValue(stub, "callbackArguments");
- var callbackContext = getChangingValue(stub, "callbackContexts");
-
- if (stub.callbackAsync) {
- nextTick(function() {
- func.apply(callbackContext, callbackArguments);
- });
- } else {
- func.apply(callbackContext, callbackArguments);
- }
- }
- }
-
- var uuid = 0;
-
- sinon.extend(stub, (function () {
- var slice = Array.prototype.slice, proto;
-
- function throwsException(error, message) {
- if (typeof error == "string") {
- this.exception = new Error(message || "");
- this.exception.name = error;
- } else if (!error) {
- this.exception = new Error("Error");
- } else {
- this.exception = error;
- }
-
- return this;
- }
-
- proto = {
- create: function create() {
- var functionStub = function () {
-
- callCallback(functionStub, arguments);
-
- if (functionStub.exception) {
- throw functionStub.exception;
- } else if (typeof functionStub.returnArgAt == 'number') {
- return arguments[functionStub.returnArgAt];
- } else if (functionStub.returnThis) {
- return this;
- }
- return functionStub.returnValue;
- };
-
- functionStub.id = "stub#" + uuid++;
- var orig = functionStub;
- functionStub = sinon.spy.create(functionStub);
- functionStub.func = orig;
-
- functionStub.callArgAts = [];
- functionStub.callbackArguments = [];
- functionStub.callbackContexts = [];
- functionStub.callArgProps = [];
-
- sinon.extend(functionStub, stub);
- functionStub._create = sinon.stub.create;
- functionStub.displayName = "stub";
- functionStub.toString = sinon.functionToString;
-
- return functionStub;
- },
-
- returns: function returns(value) {
- this.returnValue = value;
-
- return this;
- },
-
- returnsArg: function returnsArg(pos) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
-
- this.returnArgAt = pos;
-
- return this;
- },
-
- returnsThis: function returnsThis() {
- this.returnThis = true;
-
- return this;
- },
-
- "throws": throwsException,
- throwsException: throwsException,
-
- callsArg: function callsArg(pos) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push([]);
- this.callbackContexts.push(undefined);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- callsArgOn: function callsArgOn(pos, context) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push([]);
- this.callbackContexts.push(context);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- callsArgWith: function callsArgWith(pos) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push(slice.call(arguments, 1));
- this.callbackContexts.push(undefined);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- callsArgOnWith: function callsArgWith(pos, context) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push(slice.call(arguments, 2));
- this.callbackContexts.push(context);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- yields: function () {
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 0));
- this.callbackContexts.push(undefined);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- yieldsOn: function (context) {
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 1));
- this.callbackContexts.push(context);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- yieldsTo: function (prop) {
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 1));
- this.callbackContexts.push(undefined);
- this.callArgProps.push(prop);
-
- return this;
- },
-
- yieldsToOn: function (prop, context) {
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 2));
- this.callbackContexts.push(context);
- this.callArgProps.push(prop);
-
- return this;
- }
- };
-
- // create asynchronous versions of callsArg* and yields* methods
- for (var method in proto) {
- // need to avoid creating anotherasync versions of the newly added async methods
- if (proto.hasOwnProperty(method) &&
- method.match(/^(callsArg|yields|thenYields$)/) &&
- !method.match(/Async/)) {
- proto[method + 'Async'] = (function (syncFnName) {
- return function () {
- this.callbackAsync = true;
- return this[syncFnName].apply(this, arguments);
- };
- })(method);
- }
- }
-
- return proto;
-
- }()));
-
- if (commonJSModule) {
- module.exports = stub;
- } else {
- sinon.stub = stub;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- */
-/*jslint eqeqeq: false, onevar: false, nomen: false*/
-/*global module, require, sinon*/
-/**
- * Mock functions.
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var push = [].push;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function mock(object) {
- if (!object) {
- return sinon.expectation.create("Anonymous mock");
- }
-
- return mock.create(object);
- }
-
- sinon.mock = mock;
-
- sinon.extend(mock, (function () {
- function each(collection, callback) {
- if (!collection) {
- return;
- }
-
- for (var i = 0, l = collection.length; i < l; i += 1) {
- callback(collection[i]);
- }
- }
-
- return {
- create: function create(object) {
- if (!object) {
- throw new TypeError("object is null");
- }
-
- var mockObject = sinon.extend({}, mock);
- mockObject.object = object;
- delete mockObject.create;
-
- return mockObject;
- },
-
- expects: function expects(method) {
- if (!method) {
- throw new TypeError("method is falsy");
- }
-
- if (!this.expectations) {
- this.expectations = {};
- this.proxies = [];
- }
-
- if (!this.expectations[method]) {
- this.expectations[method] = [];
- var mockObject = this;
-
- sinon.wrapMethod(this.object, method, function () {
- return mockObject.invokeMethod(method, this, arguments);
- });
-
- push.call(this.proxies, method);
- }
-
- var expectation = sinon.expectation.create(method);
- push.call(this.expectations[method], expectation);
-
- return expectation;
- },
-
- restore: function restore() {
- var object = this.object;
-
- each(this.proxies, function (proxy) {
- if (typeof object[proxy].restore == "function") {
- object[proxy].restore();
- }
- });
- },
-
- verify: function verify() {
- var expectations = this.expectations || {};
- var messages = [], met = [];
-
- each(this.proxies, function (proxy) {
- each(expectations[proxy], function (expectation) {
- if (!expectation.met()) {
- push.call(messages, expectation.toString());
- } else {
- push.call(met, expectation.toString());
- }
- });
- });
-
- this.restore();
-
- if (messages.length > 0) {
- sinon.expectation.fail(messages.concat(met).join("\n"));
- } else {
- sinon.expectation.pass(messages.concat(met).join("\n"));
- }
-
- return true;
- },
-
- invokeMethod: function invokeMethod(method, thisValue, args) {
- var expectations = this.expectations && this.expectations[method];
- var length = expectations && expectations.length || 0, i;
-
- for (i = 0; i < length; i += 1) {
- if (!expectations[i].met() &&
- expectations[i].allowsCall(thisValue, args)) {
- return expectations[i].apply(thisValue, args);
- }
- }
-
- var messages = [], available, exhausted = 0;
-
- for (i = 0; i < length; i += 1) {
- if (expectations[i].allowsCall(thisValue, args)) {
- available = available || expectations[i];
- } else {
- exhausted += 1;
- }
- push.call(messages, " " + expectations[i].toString());
- }
-
- if (exhausted === 0) {
- return available.apply(thisValue, args);
- }
-
- messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({
- proxy: method,
- args: args
- }));
-
- sinon.expectation.fail(messages.join("\n"));
- }
- };
- }()));
-
- var times = sinon.timesInWords;
-
- sinon.expectation = (function () {
- var slice = Array.prototype.slice;
- var _invoke = sinon.spy.invoke;
-
- function callCountInWords(callCount) {
- if (callCount == 0) {
- return "never called";
- } else {
- return "called " + times(callCount);
- }
- }
-
- function expectedCallCountInWords(expectation) {
- var min = expectation.minCalls;
- var max = expectation.maxCalls;
-
- if (typeof min == "number" && typeof max == "number") {
- var str = times(min);
-
- if (min != max) {
- str = "at least " + str + " and at most " + times(max);
- }
-
- return str;
- }
-
- if (typeof min == "number") {
- return "at least " + times(min);
- }
-
- return "at most " + times(max);
- }
-
- function receivedMinCalls(expectation) {
- var hasMinLimit = typeof expectation.minCalls == "number";
- return !hasMinLimit || expectation.callCount >= expectation.minCalls;
- }
-
- function receivedMaxCalls(expectation) {
- if (typeof expectation.maxCalls != "number") {
- return false;
- }
-
- return expectation.callCount == expectation.maxCalls;
- }
-
- return {
- minCalls: 1,
- maxCalls: 1,
-
- create: function create(methodName) {
- var expectation = sinon.extend(sinon.stub.create(), sinon.expectation);
- delete expectation.create;
- expectation.method = methodName;
-
- return expectation;
- },
-
- invoke: function invoke(func, thisValue, args) {
- this.verifyCallAllowed(thisValue, args);
-
- return _invoke.apply(this, arguments);
- },
-
- atLeast: function atLeast(num) {
- if (typeof num != "number") {
- throw new TypeError("'" + num + "' is not number");
- }
-
- if (!this.limitsSet) {
- this.maxCalls = null;
- this.limitsSet = true;
- }
-
- this.minCalls = num;
-
- return this;
- },
-
- atMost: function atMost(num) {
- if (typeof num != "number") {
- throw new TypeError("'" + num + "' is not number");
- }
-
- if (!this.limitsSet) {
- this.minCalls = null;
- this.limitsSet = true;
- }
-
- this.maxCalls = num;
-
- return this;
- },
-
- never: function never() {
- return this.exactly(0);
- },
-
- once: function once() {
- return this.exactly(1);
- },
-
- twice: function twice() {
- return this.exactly(2);
- },
-
- thrice: function thrice() {
- return this.exactly(3);
- },
-
- exactly: function exactly(num) {
- if (typeof num != "number") {
- throw new TypeError("'" + num + "' is not a number");
- }
-
- this.atLeast(num);
- return this.atMost(num);
- },
-
- met: function met() {
- return !this.failed && receivedMinCalls(this);
- },
-
- verifyCallAllowed: function verifyCallAllowed(thisValue, args) {
- if (receivedMaxCalls(this)) {
- this.failed = true;
- sinon.expectation.fail(this.method + " already called " + times(this.maxCalls));
- }
-
- if ("expectedThis" in this && this.expectedThis !== thisValue) {
- sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " +
- this.expectedThis);
- }
-
- if (!("expectedArguments" in this)) {
- return;
- }
-
- if (!args) {
- sinon.expectation.fail(this.method + " received no arguments, expected " +
- sinon.format(this.expectedArguments));
- }
-
- if (args.length < this.expectedArguments.length) {
- sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) +
- "), expected " + sinon.format(this.expectedArguments));
- }
-
- if (this.expectsExactArgCount &&
- args.length != this.expectedArguments.length) {
- sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) +
- "), expected " + sinon.format(this.expectedArguments));
- }
-
- for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
- if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
- sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +
- ", expected " + sinon.format(this.expectedArguments));
- }
- }
- },
-
- allowsCall: function allowsCall(thisValue, args) {
- if (this.met() && receivedMaxCalls(this)) {
- return false;
- }
-
- if ("expectedThis" in this && this.expectedThis !== thisValue) {
- return false;
- }
-
- if (!("expectedArguments" in this)) {
- return true;
- }
-
- args = args || [];
-
- if (args.length < this.expectedArguments.length) {
- return false;
- }
-
- if (this.expectsExactArgCount &&
- args.length != this.expectedArguments.length) {
- return false;
- }
-
- for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
- if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
- return false;
- }
- }
-
- return true;
- },
-
- withArgs: function withArgs() {
- this.expectedArguments = slice.call(arguments);
- return this;
- },
-
- withExactArgs: function withExactArgs() {
- this.withArgs.apply(this, arguments);
- this.expectsExactArgCount = true;
- return this;
- },
-
- on: function on(thisValue) {
- this.expectedThis = thisValue;
- return this;
- },
-
- toString: function () {
- var args = (this.expectedArguments || []).slice();
-
- if (!this.expectsExactArgCount) {
- push.call(args, "[...]");
- }
-
- var callStr = sinon.spyCall.toString.call({
- proxy: this.method, args: args
- });
-
- var message = callStr.replace(", [...", "[, ...") + " " +
- expectedCallCountInWords(this);
-
- if (this.met()) {
- return "Expectation met: " + message;
- }
-
- return "Expected " + message + " (" +
- callCountInWords(this.callCount) + ")";
- },
-
- verify: function verify() {
- if (!this.met()) {
- sinon.expectation.fail(this.toString());
- } else {
- sinon.expectation.pass(this.toString());
- }
-
- return true;
- },
-
- pass: function(message) {
- sinon.assert.pass(message);
- },
- fail: function (message) {
- var exception = new Error(message);
- exception.name = "ExpectationError";
-
- throw exception;
- }
- };
- }());
-
- if (commonJSModule) {
- module.exports = mock;
- } else {
- sinon.mock = mock;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- * @depend mock.js
- */
-/*jslint eqeqeq: false, onevar: false, forin: true*/
-/*global module, require, sinon*/
-/**
- * Collections of stubs, spies and mocks.
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var push = [].push;
- var hasOwnProperty = Object.prototype.hasOwnProperty;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function getFakes(fakeCollection) {
- if (!fakeCollection.fakes) {
- fakeCollection.fakes = [];
- }
-
- return fakeCollection.fakes;
- }
-
- function each(fakeCollection, method) {
- var fakes = getFakes(fakeCollection);
-
- for (var i = 0, l = fakes.length; i < l; i += 1) {
- if (typeof fakes[i][method] == "function") {
- fakes[i][method]();
- }
- }
- }
-
- function compact(fakeCollection) {
- var fakes = getFakes(fakeCollection);
- var i = 0;
- while (i < fakes.length) {
- fakes.splice(i, 1);
- }
- }
-
- var collection = {
- verify: function resolve() {
- each(this, "verify");
- },
-
- restore: function restore() {
- each(this, "restore");
- compact(this);
- },
-
- verifyAndRestore: function verifyAndRestore() {
- var exception;
-
- try {
- this.verify();
- } catch (e) {
- exception = e;
- }
-
- this.restore();
-
- if (exception) {
- throw exception;
- }
- },
-
- add: function add(fake) {
- push.call(getFakes(this), fake);
- return fake;
- },
-
- spy: function spy() {
- return this.add(sinon.spy.apply(sinon, arguments));
- },
-
- stub: function stub(object, property, value) {
- if (property) {
- var original = object[property];
-
- if (typeof original != "function") {
- if (!hasOwnProperty.call(object, property)) {
- throw new TypeError("Cannot stub non-existent own property " + property);
- }
-
- object[property] = value;
-
- return this.add({
- restore: function () {
- object[property] = original;
- }
- });
- }
- }
- if (!property && !!object && typeof object == "object") {
- var stubbedObj = sinon.stub.apply(sinon, arguments);
-
- for (var prop in stubbedObj) {
- if (typeof stubbedObj[prop] === "function") {
- this.add(stubbedObj[prop]);
- }
- }
-
- return stubbedObj;
- }
-
- return this.add(sinon.stub.apply(sinon, arguments));
- },
-
- mock: function mock() {
- return this.add(sinon.mock.apply(sinon, arguments));
- },
-
- inject: function inject(obj) {
- var col = this;
-
- obj.spy = function () {
- return col.spy.apply(col, arguments);
- };
-
- obj.stub = function () {
- return col.stub.apply(col, arguments);
- };
-
- obj.mock = function () {
- return col.mock.apply(col, arguments);
- };
-
- return obj;
- }
- };
-
- if (commonJSModule) {
- module.exports = collection;
- } else {
- sinon.collection = collection;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/
-/*global module, require, window*/
-/**
- * Fake timer API
- * setTimeout
- * setInterval
- * clearTimeout
- * clearInterval
- * tick
- * reset
- * Date
- *
- * Inspired by jsUnitMockTimeOut from JsUnit
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- var sinon = {};
-}
-
-(function (global) {
- var id = 1;
-
- function addTimer(args, recurring) {
- if (args.length === 0) {
- throw new Error("Function requires at least 1 parameter");
- }
-
- var toId = id++;
- var delay = args[1] || 0;
-
- if (!this.timeouts) {
- this.timeouts = {};
- }
-
- this.timeouts[toId] = {
- id: toId,
- func: args[0],
- callAt: this.now + delay,
- invokeArgs: Array.prototype.slice.call(args, 2)
- };
-
- if (recurring === true) {
- this.timeouts[toId].interval = delay;
- }
-
- return toId;
- }
-
- function parseTime(str) {
- if (!str) {
- return 0;
- }
-
- var strings = str.split(":");
- var l = strings.length, i = l;
- var ms = 0, parsed;
-
- if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
- throw new Error("tick only understands numbers and 'h:m:s'");
- }
-
- while (i--) {
- parsed = parseInt(strings[i], 10);
-
- if (parsed >= 60) {
- throw new Error("Invalid time " + str);
- }
-
- ms += parsed * Math.pow(60, (l - i - 1));
- }
-
- return ms * 1000;
- }
-
- function createObject(object) {
- var newObject;
-
- if (Object.create) {
- newObject = Object.create(object);
- } else {
- var F = function () {};
- F.prototype = object;
- newObject = new F();
- }
-
- newObject.Date.clock = newObject;
- return newObject;
- }
-
- sinon.clock = {
- now: 0,
-
- create: function create(now) {
- var clock = createObject(this);
-
- if (typeof now == "number") {
- clock.now = now;
- }
-
- if (!!now && typeof now == "object") {
- throw new TypeError("now should be milliseconds since UNIX epoch");
- }
-
- return clock;
- },
-
- setTimeout: function setTimeout(callback, timeout) {
- return addTimer.call(this, arguments, false);
- },
-
- clearTimeout: function clearTimeout(timerId) {
- if (!this.timeouts) {
- this.timeouts = [];
- }
-
- if (timerId in this.timeouts) {
- delete this.timeouts[timerId];
- }
- },
-
- setInterval: function setInterval(callback, timeout) {
- return addTimer.call(this, arguments, true);
- },
-
- clearInterval: function clearInterval(timerId) {
- this.clearTimeout(timerId);
- },
-
- tick: function tick(ms) {
- ms = typeof ms == "number" ? ms : parseTime(ms);
- var tickFrom = this.now, tickTo = this.now + ms, previous = this.now;
- var timer = this.firstTimerInRange(tickFrom, tickTo);
-
- var firstException;
- while (timer && tickFrom <= tickTo) {
- if (this.timeouts[timer.id]) {
- tickFrom = this.now = timer.callAt;
- try {
- this.callTimer(timer);
- } catch (e) {
- firstException = firstException || e;
- }
- }
-
- timer = this.firstTimerInRange(previous, tickTo);
- previous = tickFrom;
- }
-
- this.now = tickTo;
-
- if (firstException) {
- throw firstException;
- }
- },
-
- firstTimerInRange: function (from, to) {
- var timer, smallest, originalTimer;
-
- for (var id in this.timeouts) {
- if (this.timeouts.hasOwnProperty(id)) {
- if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) {
- continue;
- }
-
- if (!smallest || this.timeouts[id].callAt < smallest) {
- originalTimer = this.timeouts[id];
- smallest = this.timeouts[id].callAt;
-
- timer = {
- func: this.timeouts[id].func,
- callAt: this.timeouts[id].callAt,
- interval: this.timeouts[id].interval,
- id: this.timeouts[id].id,
- invokeArgs: this.timeouts[id].invokeArgs
- };
- }
- }
- }
-
- return timer || null;
- },
-
- callTimer: function (timer) {
- if (typeof timer.interval == "number") {
- this.timeouts[timer.id].callAt += timer.interval;
- } else {
- delete this.timeouts[timer.id];
- }
-
- try {
- if (typeof timer.func == "function") {
- timer.func.apply(null, timer.invokeArgs);
- } else {
- eval(timer.func);
- }
- } catch (e) {
- var exception = e;
- }
-
- if (!this.timeouts[timer.id]) {
- if (exception) {
- throw exception;
- }
- return;
- }
-
- if (exception) {
- throw exception;
- }
- },
-
- reset: function reset() {
- this.timeouts = {};
- },
-
- Date: (function () {
- var NativeDate = Date;
-
- function ClockDate(year, month, date, hour, minute, second, ms) {
- // Defensive and verbose to avoid potential harm in passing
- // explicit undefined when user does not pass argument
- switch (arguments.length) {
- case 0:
- return new NativeDate(ClockDate.clock.now);
- case 1:
- return new NativeDate(year);
- case 2:
- return new NativeDate(year, month);
- case 3:
- return new NativeDate(year, month, date);
- case 4:
- return new NativeDate(year, month, date, hour);
- case 5:
- return new NativeDate(year, month, date, hour, minute);
- case 6:
- return new NativeDate(year, month, date, hour, minute, second);
- default:
- return new NativeDate(year, month, date, hour, minute, second, ms);
- }
- }
-
- return mirrorDateProperties(ClockDate, NativeDate);
- }())
- };
-
- function mirrorDateProperties(target, source) {
- if (source.now) {
- target.now = function now() {
- return target.clock.now;
- };
- } else {
- delete target.now;
- }
-
- if (source.toSource) {
- target.toSource = function toSource() {
- return source.toSource();
- };
- } else {
- delete target.toSource;
- }
-
- target.toString = function toString() {
- return source.toString();
- };
-
- target.prototype = source.prototype;
- target.parse = source.parse;
- target.UTC = source.UTC;
- target.prototype.toUTCString = source.prototype.toUTCString;
- return target;
- }
-
- var methods = ["Date", "setTimeout", "setInterval",
- "clearTimeout", "clearInterval"];
-
- function restore() {
- var method;
-
- for (var i = 0, l = this.methods.length; i < l; i++) {
- method = this.methods[i];
- if (global[method].hadOwnProperty) {
- global[method] = this["_" + method];
- } else {
- delete global[method];
- }
- }
-
- // Prevent multiple executions which will completely remove these props
- this.methods = [];
- }
-
- function stubGlobal(method, clock) {
- clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method);
- clock["_" + method] = global[method];
-
- if (method == "Date") {
- var date = mirrorDateProperties(clock[method], global[method]);
- global[method] = date;
- } else {
- global[method] = function () {
- return clock[method].apply(clock, arguments);
- };
-
- for (var prop in clock[method]) {
- if (clock[method].hasOwnProperty(prop)) {
- global[method][prop] = clock[method][prop];
- }
- }
- }
-
- global[method].clock = clock;
- }
-
- sinon.useFakeTimers = function useFakeTimers(now) {
- var clock = sinon.clock.create(now);
- clock.restore = restore;
- clock.methods = Array.prototype.slice.call(arguments,
- typeof now == "number" ? 1 : 0);
-
- if (clock.methods.length === 0) {
- clock.methods = methods;
- }
-
- for (var i = 0, l = clock.methods.length; i < l; i++) {
- stubGlobal(clock.methods[i], clock);
- }
-
- return clock;
- };
-}(typeof global != "undefined" && typeof global !== "function" ? global : this));
-
-sinon.timers = {
- setTimeout: setTimeout,
- clearTimeout: clearTimeout,
- setInterval: setInterval,
- clearInterval: clearInterval,
- Date: Date
-};
-
-if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon;
-}
-
-/*jslint eqeqeq: false, onevar: false*/
-/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
-/**
- * Minimal Event interface implementation
- *
- * Original implementation by Sven Fuchs: https://gist.github.com/995028
- * Modifications and tests by Christian Johansen.
- *
- * @license BSD
- *
- * Copyright (c) 2011 Sven Fuchs, Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- this.sinon = {};
-}
-
-(function () {
- var push = [].push;
-
- sinon.Event = function Event(type, bubbles, cancelable) {
- this.initEvent(type, bubbles, cancelable);
- };
-
- sinon.Event.prototype = {
- initEvent: function(type, bubbles, cancelable) {
- this.type = type;
- this.bubbles = bubbles;
- this.cancelable = cancelable;
- },
-
- stopPropagation: function () {},
-
- preventDefault: function () {
- this.defaultPrevented = true;
- }
- };
-
- sinon.EventTarget = {
- addEventListener: function addEventListener(event, listener, useCapture) {
- this.eventListeners = this.eventListeners || {};
- this.eventListeners[event] = this.eventListeners[event] || [];
- push.call(this.eventListeners[event], listener);
- },
-
- removeEventListener: function removeEventListener(event, listener, useCapture) {
- var listeners = this.eventListeners && this.eventListeners[event] || [];
-
- for (var i = 0, l = listeners.length; i < l; ++i) {
- if (listeners[i] == listener) {
- return listeners.splice(i, 1);
- }
- }
- },
-
- dispatchEvent: function dispatchEvent(event) {
- var type = event.type;
- var listeners = this.eventListeners && this.eventListeners[type] || [];
-
- for (var i = 0; i < listeners.length; i++) {
- if (typeof listeners[i] == "function") {
- listeners[i].call(this, event);
- } else {
- listeners[i].handleEvent(event);
- }
- }
-
- return !!event.defaultPrevented;
- }
- };
-}());
-
-/**
- * @depend ../../sinon.js
- * @depend event.js
- */
-/*jslint eqeqeq: false, onevar: false*/
-/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
-/**
- * Fake XMLHttpRequest object
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- this.sinon = {};
-}
-sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest };
-
-// wrapper for global
-(function(global) {
- var xhr = sinon.xhr;
- xhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
- xhr.GlobalActiveXObject = global.ActiveXObject;
- xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined";
- xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined";
- xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX
- ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false;
-
- /*jsl:ignore*/
- var unsafeHeaders = {
- "Accept-Charset": true,
- "Accept-Encoding": true,
- "Connection": true,
- "Content-Length": true,
- "Cookie": true,
- "Cookie2": true,
- "Content-Transfer-Encoding": true,
- "Date": true,
- "Expect": true,
- "Host": true,
- "Keep-Alive": true,
- "Referer": true,
- "TE": true,
- "Trailer": true,
- "Transfer-Encoding": true,
- "Upgrade": true,
- "User-Agent": true,
- "Via": true
- };
- /*jsl:end*/
-
- function FakeXMLHttpRequest() {
- this.readyState = FakeXMLHttpRequest.UNSENT;
- this.requestHeaders = {};
- this.requestBody = null;
- this.status = 0;
- this.statusText = "";
-
- if (typeof FakeXMLHttpRequest.onCreate == "function") {
- FakeXMLHttpRequest.onCreate(this);
- }
- }
-
- function verifyState(xhr) {
- if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
- throw new Error("INVALID_STATE_ERR");
- }
-
- if (xhr.sendFlag) {
- throw new Error("INVALID_STATE_ERR");
- }
- }
-
- // filtering to enable a white-list version of Sinon FakeXhr,
- // where whitelisted requests are passed through to real XHR
- function each(collection, callback) {
- if (!collection) return;
- for (var i = 0, l = collection.length; i < l; i += 1) {
- callback(collection[i]);
- }
- }
- function some(collection, callback) {
- for (var index = 0; index < collection.length; index++) {
- if(callback(collection[index]) === true) return true;
- };
- return false;
- }
- // largest arity in XHR is 5 - XHR#open
- var apply = function(obj,method,args) {
- switch(args.length) {
- case 0: return obj[method]();
- case 1: return obj[method](args[0]);
- case 2: return obj[method](args[0],args[1]);
- case 3: return obj[method](args[0],args[1],args[2]);
- case 4: return obj[method](args[0],args[1],args[2],args[3]);
- case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]);
- };
- };
-
- FakeXMLHttpRequest.filters = [];
- FakeXMLHttpRequest.addFilter = function(fn) {
- this.filters.push(fn)
- };
- var IE6Re = /MSIE 6/;
- FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) {
- var xhr = new sinon.xhr.workingXHR();
- each(["open","setRequestHeader","send","abort","getResponseHeader",
- "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"],
- function(method) {
- fakeXhr[method] = function() {
- return apply(xhr,method,arguments);
- };
- });
-
- var copyAttrs = function(args) {
- each(args, function(attr) {
- try {
- fakeXhr[attr] = xhr[attr]
- } catch(e) {
- if(!IE6Re.test(navigator.userAgent)) throw e;
- }
- });
- };
-
- var stateChange = function() {
- fakeXhr.readyState = xhr.readyState;
- if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
- copyAttrs(["status","statusText"]);
- }
- if(xhr.readyState >= FakeXMLHttpRequest.LOADING) {
- copyAttrs(["responseText"]);
- }
- if(xhr.readyState === FakeXMLHttpRequest.DONE) {
- copyAttrs(["responseXML"]);
- }
- if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr);
- };
- if(xhr.addEventListener) {
- for(var event in fakeXhr.eventListeners) {
- if(fakeXhr.eventListeners.hasOwnProperty(event)) {
- each(fakeXhr.eventListeners[event],function(handler) {
- xhr.addEventListener(event, handler);
- });
- }
- }
- xhr.addEventListener("readystatechange",stateChange);
- } else {
- xhr.onreadystatechange = stateChange;
- }
- apply(xhr,"open",xhrArgs);
- };
- FakeXMLHttpRequest.useFilters = false;
-
- function verifyRequestSent(xhr) {
- if (xhr.readyState == FakeXMLHttpRequest.DONE) {
- throw new Error("Request done");
- }
- }
-
- function verifyHeadersReceived(xhr) {
- if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {
- throw new Error("No headers received");
- }
- }
-
- function verifyResponseBodyType(body) {
- if (typeof body != "string") {
- var error = new Error("Attempted to respond to fake XMLHttpRequest with " +
- body + ", which is not a string.");
- error.name = "InvalidBodyException";
- throw error;
- }
- }
-
- sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {
- async: true,
-
- open: function open(method, url, async, username, password) {
- this.method = method;
- this.url = url;
- this.async = typeof async == "boolean" ? async : true;
- this.username = username;
- this.password = password;
- this.responseText = null;
- this.responseXML = null;
- this.requestHeaders = {};
- this.sendFlag = false;
- if(sinon.FakeXMLHttpRequest.useFilters === true) {
- var xhrArgs = arguments;
- var defake = some(FakeXMLHttpRequest.filters,function(filter) {
- return filter.apply(this,xhrArgs)
- });
- if (defake) {
- return sinon.FakeXMLHttpRequest.defake(this,arguments);
- }
- }
- this.readyStateChange(FakeXMLHttpRequest.OPENED);
- },
-
- readyStateChange: function readyStateChange(state) {
- this.readyState = state;
-
- if (typeof this.onreadystatechange == "function") {
- try {
- this.onreadystatechange();
- } catch (e) {
- sinon.logError("Fake XHR onreadystatechange handler", e);
- }
- }
-
- this.dispatchEvent(new sinon.Event("readystatechange"));
- },
-
- setRequestHeader: function setRequestHeader(header, value) {
- verifyState(this);
-
- if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
- throw new Error("Refused to set unsafe header \"" + header + "\"");
- }
-
- if (this.requestHeaders[header]) {
- this.requestHeaders[header] += "," + value;
- } else {
- this.requestHeaders[header] = value;
- }
- },
-
- // Helps testing
- setResponseHeaders: function setResponseHeaders(headers) {
- this.responseHeaders = {};
-
- for (var header in headers) {
- if (headers.hasOwnProperty(header)) {
- this.responseHeaders[header] = headers[header];
- }
- }
-
- if (this.async) {
- this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
- }
- },
-
- // Currently treats ALL data as a DOMString (i.e. no Document)
- send: function send(data) {
- verifyState(this);
-
- if (!/^(get|head)$/i.test(this.method)) {
- if (this.requestHeaders["Content-Type"]) {
- var value = this.requestHeaders["Content-Type"].split(";");
- this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8";
- } else {
- this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
- }
-
- this.requestBody = data;
- }
-
- this.errorFlag = false;
- this.sendFlag = this.async;
- this.readyStateChange(FakeXMLHttpRequest.OPENED);
-
- if (typeof this.onSend == "function") {
- this.onSend(this);
- }
- },
-
- abort: function abort() {
- this.aborted = true;
- this.responseText = null;
- this.errorFlag = true;
- this.requestHeaders = {};
-
- if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) {
- this.readyStateChange(sinon.FakeXMLHttpRequest.DONE);
- this.sendFlag = false;
- }
-
- this.readyState = sinon.FakeXMLHttpRequest.UNSENT;
- },
-
- getResponseHeader: function getResponseHeader(header) {
- if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
- return null;
- }
-
- if (/^Set-Cookie2?$/i.test(header)) {
- return null;
- }
-
- header = header.toLowerCase();
-
- for (var h in this.responseHeaders) {
- if (h.toLowerCase() == header) {
- return this.responseHeaders[h];
- }
- }
-
- return null;
- },
-
- getAllResponseHeaders: function getAllResponseHeaders() {
- if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
- return "";
- }
-
- var headers = "";
-
- for (var header in this.responseHeaders) {
- if (this.responseHeaders.hasOwnProperty(header) &&
- !/^Set-Cookie2?$/i.test(header)) {
- headers += header + ": " + this.responseHeaders[header] + "\r\n";
- }
- }
-
- return headers;
- },
-
- setResponseBody: function setResponseBody(body) {
- verifyRequestSent(this);
- verifyHeadersReceived(this);
- verifyResponseBodyType(body);
-
- var chunkSize = this.chunkSize || 10;
- var index = 0;
- this.responseText = "";
-
- do {
- if (this.async) {
- this.readyStateChange(FakeXMLHttpRequest.LOADING);
- }
-
- this.responseText += body.substring(index, index + chunkSize);
- index += chunkSize;
- } while (index < body.length);
-
- var type = this.getResponseHeader("Content-Type");
-
- if (this.responseText &&
- (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {
- try {
- this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
- } catch (e) {
- // Unable to parse XML - no biggie
- }
- }
-
- if (this.async) {
- this.readyStateChange(FakeXMLHttpRequest.DONE);
- } else {
- this.readyState = FakeXMLHttpRequest.DONE;
- }
- },
-
- respond: function respond(status, headers, body) {
- this.setResponseHeaders(headers || {});
- this.status = typeof status == "number" ? status : 200;
- this.statusText = FakeXMLHttpRequest.statusCodes[this.status];
- this.setResponseBody(body || "");
- }
- });
-
- sinon.extend(FakeXMLHttpRequest, {
- UNSENT: 0,
- OPENED: 1,
- HEADERS_RECEIVED: 2,
- LOADING: 3,
- DONE: 4
- });
-
- // Borrowed from JSpec
- FakeXMLHttpRequest.parseXML = function parseXML(text) {
- var xmlDoc;
-
- if (typeof DOMParser != "undefined") {
- var parser = new DOMParser();
- xmlDoc = parser.parseFromString(text, "text/xml");
- } else {
- xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
- xmlDoc.async = "false";
- xmlDoc.loadXML(text);
- }
-
- return xmlDoc;
- };
-
- FakeXMLHttpRequest.statusCodes = {
- 100: "Continue",
- 101: "Switching Protocols",
- 200: "OK",
- 201: "Created",
- 202: "Accepted",
- 203: "Non-Authoritative Information",
- 204: "No Content",
- 205: "Reset Content",
- 206: "Partial Content",
- 300: "Multiple Choice",
- 301: "Moved Permanently",
- 302: "Found",
- 303: "See Other",
- 304: "Not Modified",
- 305: "Use Proxy",
- 307: "Temporary Redirect",
- 400: "Bad Request",
- 401: "Unauthorized",
- 402: "Payment Required",
- 403: "Forbidden",
- 404: "Not Found",
- 405: "Method Not Allowed",
- 406: "Not Acceptable",
- 407: "Proxy Authentication Required",
- 408: "Request Timeout",
- 409: "Conflict",
- 410: "Gone",
- 411: "Length Required",
- 412: "Precondition Failed",
- 413: "Request Entity Too Large",
- 414: "Request-URI Too Long",
- 415: "Unsupported Media Type",
- 416: "Requested Range Not Satisfiable",
- 417: "Expectation Failed",
- 422: "Unprocessable Entity",
- 500: "Internal Server Error",
- 501: "Not Implemented",
- 502: "Bad Gateway",
- 503: "Service Unavailable",
- 504: "Gateway Timeout",
- 505: "HTTP Version Not Supported"
- };
-
- sinon.useFakeXMLHttpRequest = function () {
- sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
- if (xhr.supportsXHR) {
- global.XMLHttpRequest = xhr.GlobalXMLHttpRequest;
- }
-
- if (xhr.supportsActiveX) {
- global.ActiveXObject = xhr.GlobalActiveXObject;
- }
-
- delete sinon.FakeXMLHttpRequest.restore;
-
- if (keepOnCreate !== true) {
- delete sinon.FakeXMLHttpRequest.onCreate;
- }
- };
- if (xhr.supportsXHR) {
- global.XMLHttpRequest = sinon.FakeXMLHttpRequest;
- }
-
- if (xhr.supportsActiveX) {
- global.ActiveXObject = function ActiveXObject(objId) {
- if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {
-
- return new sinon.FakeXMLHttpRequest();
- }
-
- return new xhr.GlobalActiveXObject(objId);
- };
- }
-
- return sinon.FakeXMLHttpRequest;
- };
-
- sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;
-})(this);
-
-if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon;
-}
-
-/**
- * @depend fake_xml_http_request.js
- */
-/*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/
-/*global module, require, window*/
-/**
- * The Sinon "server" mimics a web server that receives requests from
- * sinon.FakeXMLHttpRequest and provides an API to respond to those requests,
- * both synchronously and asynchronously. To respond synchronuously, canned
- * answers have to be provided upfront.
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- var sinon = {};
-}
-
-sinon.fakeServer = (function () {
- var push = [].push;
- function F() {}
-
- function create(proto) {
- F.prototype = proto;
- return new F();
- }
-
- function responseArray(handler) {
- var response = handler;
-
- if (Object.prototype.toString.call(handler) != "[object Array]") {
- response = [200, {}, handler];
- }
-
- if (typeof response[2] != "string") {
- throw new TypeError("Fake server response body should be string, but was " +
- typeof response[2]);
- }
-
- return response;
- }
-
- var wloc = typeof window !== "undefined" ? window.location : {};
- var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);
-
- function matchOne(response, reqMethod, reqUrl) {
- var rmeth = response.method;
- var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase();
- var url = response.url;
- var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl));
-
- return matchMethod && matchUrl;
- }
-
- function match(response, request) {
- var requestMethod = this.getHTTPMethod(request);
- var requestUrl = request.url;
-
- if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {
- requestUrl = requestUrl.replace(rCurrLoc, "");
- }
-
- if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {
- if (typeof response.response == "function") {
- var ru = response.url;
- var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1));
- return response.response.apply(response, args);
- }
-
- return true;
- }
-
- return false;
- }
-
- return {
- create: function () {
- var server = create(this);
- this.xhr = sinon.useFakeXMLHttpRequest();
- server.requests = [];
-
- this.xhr.onCreate = function (xhrObj) {
- server.addRequest(xhrObj);
- };
-
- return server;
- },
-
- addRequest: function addRequest(xhrObj) {
- var server = this;
- push.call(this.requests, xhrObj);
-
- xhrObj.onSend = function () {
- server.handleRequest(this);
- };
-
- if (this.autoRespond && !this.responding) {
- setTimeout(function () {
- server.responding = false;
- server.respond();
- }, this.autoRespondAfter || 10);
-
- this.responding = true;
- }
- },
-
- getHTTPMethod: function getHTTPMethod(request) {
- if (this.fakeHTTPMethods && /post/i.test(request.method)) {
- var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);
- return !!matches ? matches[1] : request.method;
- }
-
- return request.method;
- },
-
- handleRequest: function handleRequest(xhr) {
- if (xhr.async) {
- if (!this.queue) {
- this.queue = [];
- }
-
- push.call(this.queue, xhr);
- } else {
- this.processRequest(xhr);
- }
- },
-
- respondWith: function respondWith(method, url, body) {
- if (arguments.length == 1 && typeof method != "function") {
- this.response = responseArray(method);
- return;
- }
-
- if (!this.responses) { this.responses = []; }
-
- if (arguments.length == 1) {
- body = method;
- url = method = null;
- }
-
- if (arguments.length == 2) {
- body = url;
- url = method;
- method = null;
- }
-
- push.call(this.responses, {
- method: method,
- url: url,
- response: typeof body == "function" ? body : responseArray(body)
- });
- },
-
- respond: function respond() {
- if (arguments.length > 0) this.respondWith.apply(this, arguments);
- var queue = this.queue || [];
- var request;
-
- while(request = queue.shift()) {
- this.processRequest(request);
- }
- },
-
- processRequest: function processRequest(request) {
- try {
- if (request.aborted) {
- return;
- }
-
- var response = this.response || [404, {}, ""];
-
- if (this.responses) {
- for (var i = 0, l = this.responses.length; i < l; i++) {
- if (match.call(this, this.responses[i], request)) {
- response = this.responses[i].response;
- break;
- }
- }
- }
-
- if (request.readyState != 4) {
- request.respond(response[0], response[1], response[2]);
- }
- } catch (e) {
- sinon.logError("Fake server request processing", e);
- }
- },
-
- restore: function restore() {
- return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
- }
- };
-}());
-
-if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon;
-}
-
-/**
- * @depend fake_server.js
- * @depend fake_timers.js
- */
-/*jslint browser: true, eqeqeq: false, onevar: false*/
-/*global sinon*/
-/**
- * Add-on for sinon.fakeServer that automatically handles a fake timer along with
- * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery
- * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead,
- * it polls the object for completion with setInterval. Dispite the direct
- * motivation, there is nothing jQuery-specific in this file, so it can be used
- * in any environment where the ajax implementation depends on setInterval or
- * setTimeout.
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function () {
- function Server() {}
- Server.prototype = sinon.fakeServer;
-
- sinon.fakeServerWithClock = new Server();
-
- sinon.fakeServerWithClock.addRequest = function addRequest(xhr) {
- if (xhr.async) {
- if (typeof setTimeout.clock == "object") {
- this.clock = setTimeout.clock;
- } else {
- this.clock = sinon.useFakeTimers();
- this.resetClock = true;
- }
-
- if (!this.longestTimeout) {
- var clockSetTimeout = this.clock.setTimeout;
- var clockSetInterval = this.clock.setInterval;
- var server = this;
-
- this.clock.setTimeout = function (fn, timeout) {
- server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
-
- return clockSetTimeout.apply(this, arguments);
- };
-
- this.clock.setInterval = function (fn, timeout) {
- server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
-
- return clockSetInterval.apply(this, arguments);
- };
- }
- }
-
- return sinon.fakeServer.addRequest.call(this, xhr);
- };
-
- sinon.fakeServerWithClock.respond = function respond() {
- var returnVal = sinon.fakeServer.respond.apply(this, arguments);
-
- if (this.clock) {
- this.clock.tick(this.longestTimeout || 0);
- this.longestTimeout = 0;
-
- if (this.resetClock) {
- this.clock.restore();
- this.resetClock = false;
- }
- }
-
- return returnVal;
- };
-
- sinon.fakeServerWithClock.restore = function restore() {
- if (this.clock) {
- this.clock.restore();
- }
-
- return sinon.fakeServer.restore.apply(this, arguments);
- };
-}());
-
-/**
- * @depend ../sinon.js
- * @depend collection.js
- * @depend util/fake_timers.js
- * @depend util/fake_server_with_clock.js
- */
-/*jslint eqeqeq: false, onevar: false, plusplus: false*/
-/*global require, module*/
-/**
- * Manages fake collections as well as fake utilities such as Sinon's
- * timers and fake XHR implementation in one convenient object.
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof module == "object" && typeof require == "function") {
- var sinon = require("../sinon");
- sinon.extend(sinon, require("./util/fake_timers"));
-}
-
-(function () {
- var push = [].push;
-
- function exposeValue(sandbox, config, key, value) {
- if (!value) {
- return;
- }
-
- if (config.injectInto) {
- config.injectInto[key] = value;
- } else {
- push.call(sandbox.args, value);
- }
- }
-
- function prepareSandboxFromConfig(config) {
- var sandbox = sinon.create(sinon.sandbox);
-
- if (config.useFakeServer) {
- if (typeof config.useFakeServer == "object") {
- sandbox.serverPrototype = config.useFakeServer;
- }
-
- sandbox.useFakeServer();
- }
-
- if (config.useFakeTimers) {
- if (typeof config.useFakeTimers == "object") {
- sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers);
- } else {
- sandbox.useFakeTimers();
- }
- }
-
- return sandbox;
- }
-
- sinon.sandbox = sinon.extend(sinon.create(sinon.collection), {
- useFakeTimers: function useFakeTimers() {
- this.clock = sinon.useFakeTimers.apply(sinon, arguments);
-
- return this.add(this.clock);
- },
-
- serverPrototype: sinon.fakeServer,
-
- useFakeServer: function useFakeServer() {
- var proto = this.serverPrototype || sinon.fakeServer;
-
- if (!proto || !proto.create) {
- return null;
- }
-
- this.server = proto.create();
- return this.add(this.server);
- },
-
- inject: function (obj) {
- sinon.collection.inject.call(this, obj);
-
- if (this.clock) {
- obj.clock = this.clock;
- }
-
- if (this.server) {
- obj.server = this.server;
- obj.requests = this.server.requests;
- }
-
- return obj;
- },
-
- create: function (config) {
- if (!config) {
- return sinon.create(sinon.sandbox);
- }
-
- var sandbox = prepareSandboxFromConfig(config);
- sandbox.args = sandbox.args || [];
- var prop, value, exposed = sandbox.inject({});
-
- if (config.properties) {
- for (var i = 0, l = config.properties.length; i < l; i++) {
- prop = config.properties[i];
- value = exposed[prop] || prop == "sandbox" && sandbox;
- exposeValue(sandbox, config, prop, value);
- }
- } else {
- exposeValue(sandbox, config, "sandbox", value);
- }
-
- return sandbox;
- }
- });
-
- sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer;
-
- if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon.sandbox;
- }
-}());
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- * @depend mock.js
- * @depend sandbox.js
- */
-/*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Test function, sandboxes fakes
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function test(callback) {
- var type = typeof callback;
-
- if (type != "function") {
- throw new TypeError("sinon.test needs to wrap a test function, got " + type);
- }
-
- return function () {
- var config = sinon.getConfig(sinon.config);
- config.injectInto = config.injectIntoThis && this || config.injectInto;
- var sandbox = sinon.sandbox.create(config);
- var exception, result;
- var args = Array.prototype.slice.call(arguments).concat(sandbox.args);
-
- try {
- result = callback.apply(this, args);
- } catch (e) {
- exception = e;
- }
-
- if (typeof exception !== "undefined") {
- sandbox.restore();
- throw exception;
- }
- else {
- sandbox.verifyAndRestore();
- }
-
- return result;
- };
- }
-
- test.config = {
- injectIntoThis: true,
- injectInto: null,
- properties: ["spy", "stub", "mock", "clock", "server", "requests"],
- useFakeTimers: true,
- useFakeServer: true
- };
-
- if (commonJSModule) {
- module.exports = test;
- } else {
- sinon.test = test;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend test.js
- */
-/*jslint eqeqeq: false, onevar: false, eqeqeq: false*/
-/*global module, require, sinon*/
-/**
- * Test case, sandboxes all test functions
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon || !Object.prototype.hasOwnProperty) {
- return;
- }
-
- function createTest(property, setUp, tearDown) {
- return function () {
- if (setUp) {
- setUp.apply(this, arguments);
- }
-
- var exception, result;
-
- try {
- result = property.apply(this, arguments);
- } catch (e) {
- exception = e;
- }
-
- if (tearDown) {
- tearDown.apply(this, arguments);
- }
-
- if (exception) {
- throw exception;
- }
-
- return result;
- };
- }
-
- function testCase(tests, prefix) {
- /*jsl:ignore*/
- if (!tests || typeof tests != "object") {
- throw new TypeError("sinon.testCase needs an object with test functions");
- }
- /*jsl:end*/
-
- prefix = prefix || "test";
- var rPrefix = new RegExp("^" + prefix);
- var methods = {}, testName, property, method;
- var setUp = tests.setUp;
- var tearDown = tests.tearDown;
-
- for (testName in tests) {
- if (tests.hasOwnProperty(testName)) {
- property = tests[testName];
-
- if (/^(setUp|tearDown)$/.test(testName)) {
- continue;
- }
-
- if (typeof property == "function" && rPrefix.test(testName)) {
- method = property;
-
- if (setUp || tearDown) {
- method = createTest(property, setUp, tearDown);
- }
-
- methods[testName] = sinon.test(method);
- } else {
- methods[testName] = tests[testName];
- }
- }
- }
-
- return methods;
- }
-
- if (commonJSModule) {
- module.exports = testCase;
- } else {
- sinon.testCase = testCase;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- */
-/*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Assertions matching the test spy retrieval interface.
- *
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon, global) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var slice = Array.prototype.slice;
- var assert;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function verifyIsStub() {
- var method;
-
- for (var i = 0, l = arguments.length; i < l; ++i) {
- method = arguments[i];
-
- if (!method) {
- assert.fail("fake is not a spy");
- }
-
- if (typeof method != "function") {
- assert.fail(method + " is not a function");
- }
-
- if (typeof method.getCall != "function") {
- assert.fail(method + " is not stubbed");
- }
- }
- }
-
- function failAssertion(object, msg) {
- object = object || global;
- var failMethod = object.fail || assert.fail;
- failMethod.call(object, msg);
- }
-
- function mirrorPropAsAssertion(name, method, message) {
- if (arguments.length == 2) {
- message = method;
- method = name;
- }
-
- assert[name] = function (fake) {
- verifyIsStub(fake);
-
- var args = slice.call(arguments, 1);
- var failed = false;
-
- if (typeof method == "function") {
- failed = !method(fake);
- } else {
- failed = typeof fake[method] == "function" ?
- !fake[method].apply(fake, args) : !fake[method];
- }
-
- if (failed) {
- failAssertion(this, fake.printf.apply(fake, [message].concat(args)));
- } else {
- assert.pass(name);
- }
- };
- }
-
- function exposedName(prefix, prop) {
- return !prefix || /^fail/.test(prop) ? prop :
- prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1);
- };
-
- assert = {
- failException: "AssertError",
-
- fail: function fail(message) {
- var error = new Error(message);
- error.name = this.failException || assert.failException;
-
- throw error;
- },
-
- pass: function pass(assertion) {},
-
- callOrder: function assertCallOrder() {
- verifyIsStub.apply(null, arguments);
- var expected = "", actual = "";
-
- if (!sinon.calledInOrder(arguments)) {
- try {
- expected = [].join.call(arguments, ", ");
- actual = sinon.orderByFirstCall(slice.call(arguments)).join(", ");
- } catch (e) {
- // If this fails, we'll just fall back to the blank string
- }
-
- failAssertion(this, "expected " + expected + " to be " +
- "called in order but were called as " + actual);
- } else {
- assert.pass("callOrder");
- }
- },
-
- callCount: function assertCallCount(method, count) {
- verifyIsStub(method);
-
- if (method.callCount != count) {
- var msg = "expected %n to be called " + sinon.timesInWords(count) +
- " but was called %c%C";
- failAssertion(this, method.printf(msg));
- } else {
- assert.pass("callCount");
- }
- },
-
- expose: function expose(target, options) {
- if (!target) {
- throw new TypeError("target is null or undefined");
- }
-
- var o = options || {};
- var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix;
- var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail;
-
- for (var method in this) {
- if (method != "export" && (includeFail || !/^(fail)/.test(method))) {
- target[exposedName(prefix, method)] = this[method];
- }
- }
-
- return target;
- }
- };
-
- mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called");
- mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; },
- "expected %n to not have been called but was called %c%C");
- mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C");
- mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C");
- mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C");
- mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t");
- mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t");
- mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new");
- mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new");
- mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C");
- mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C");
- mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C");
- mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C");
- mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C");
- mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C");
- mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C");
- mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C");
- mirrorPropAsAssertion("threw", "%n did not throw exception%C");
- mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C");
-
- if (commonJSModule) {
- module.exports = assert;
- } else {
- sinon.assert = assert;
- }
-}(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : global));
-
-return sinon;}.call(typeof window != 'undefined' && window || {}));
\ No newline at end of file
+++ /dev/null
-/*global define, module, test, expect, equal, ok*/
-
-define(['selectable'], function ($) {
-
- module("Basic Event Tests", {
- setup: function () {
- // Patch AJAX requests
- var self = this;
- this.xhr = sinon.useFakeXMLHttpRequest();
- this.requests = [];
- this.xhr.onCreate = function (xhr) {
- self.requests.push(xhr);
- };
- this.inputs = createTextSelect('autocompleteselect');
- this.textInput = this.inputs[0];
- this.hiddenInput = this.inputs[1];
- $('#qunit-fixture').append(this.textInput);
- $('#qunit-fixture').append(this.hiddenInput);
- bindSelectables('#qunit-fixture');
- },
- teardown: function () {
- this.xhr.restore();
- this.textInput.djselectable('destroy');
- }
- });
-
- test("Manual Selection", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'};
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- var item = {id: "1", value: 'foo'};
- this.textInput.djselectable('select', item);
- equal(count, 1, "djselectableselect should fire once when manually selected.");
- });
-
- test("Manual Selection with Double Bind", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'};
- bindSelectables('#qunit-fixture');
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.djselectable('select', item);
- equal(count, 1, "djselectableselect should fire once when manually selected.");
- });
-
- test("Menu Selection", function() {
- expect(2);
- var count = 0,
- down = jQuery.Event("keydown"),
- enter = jQuery.Event("keydown"),
- response = simpleLookupResponse(),
- self = this;
- down.keyCode = $.ui.keyCode.DOWN;
- enter.keyCode = $.ui.keyCode.ENTER;
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- equal(self.requests.length, 1, "AJAX request should be triggered.");
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- self.textInput.trigger(down);
- self.textInput.trigger(enter);
- equal(count, 1, "djselectableselect should only fire once.");
- start();
- }, 300);
- });
-
- test("Pagination Click", function() {
- expect(3);
- var count = 0,
- response = paginatedLookupResponse(),
- self = this;
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- equal(self.requests.length, 1, "AJAX request should be triggered.");
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- $('.ui-menu-item.selectable-paginator:visible a').trigger("mouseenter");
- $('.ui-menu-item.selectable-paginator:visible a').trigger("click");
- equal(self.requests.length, 2, "Another AJAX request should be triggered.");
- equal(count, 0, "djselectableselect should not fire for new page.");
- start();
- }, 300);
- });
-
- test("Pagination Enter", function() {
- expect(3);
- var count = 0,
- down = jQuery.Event("keydown"),
- enter = jQuery.Event("keydown"),
- response = paginatedLookupResponse(),
- self = this;
- down.keyCode = $.ui.keyCode.DOWN;
- enter.keyCode = $.ui.keyCode.ENTER;
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- equal(self.requests.length, 1, "AJAX request should be triggered.");
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(enter);
- equal(self.requests.length, 2, "Another AJAX request should be triggered.");
- equal(count, 0, "djselectableselect should not fire for new page.");
- start();
- }, 300);
- });
-
- test("Pagination Render", function() {
- expect(2);
- var count = 0,
- down = jQuery.Event("keydown"),
- enter = jQuery.Event("keydown"),
- response = paginatedLookupResponse(),
- self = this;
- down.keyCode = $.ui.keyCode.DOWN;
- enter.keyCode = $.ui.keyCode.ENTER;
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- var options;
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- options = $('li.ui-menu-item:visible');
- equal(options.length, 3, "Currently 3 menu items.");
- // $('.selectable-paginator:visible').click();
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(enter);
- self.requests[1].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- options = $('li.ui-menu-item:visible');
- equal(options.length, 5, "Now 5 menu items.");
- start();
- }, 300);
- });
-
- module("Custom Event Tests", {
- setup: function () {
- this.inputs = createTextSelectMultiple('autocompleteselectmultiple');
- this.textInput = this.inputs[0];
- this.hiddenInput = this.inputs[1];
- $('#qunit-fixture').append(this.textInput);
- bindSelectables('#qunit-fixture');
- }
- });
-
- test("Add Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'};
- this.textInput.bind('djselectableadd', function(e, item) {
- count = count + 1;
- });
- this.textInput.djselectable('select', item);
- equal(count, 1, "djselectableadd should fire once when manually selected.");
- });
-
- test("Prevent Add Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'},
- deck = $('.selectable-deck', '#qunit-fixture');
- this.textInput.bind('djselectableadd', function(e, item) {
- return false;
- });
- this.textInput.djselectable('select', item);
- deck = $('.selectable-deck', '#qunit-fixture');
- equal($('li', deck).length, 0, "Item should not be added.");
- });
-
- test("Remove Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'},
- deck = $('.selectable-deck', '#qunit-fixture');
- this.textInput.bind('djselectableremove', function(e, item) {
- count = count + 1;
- });
- this.textInput.djselectable('select', item);
- $('.selectable-deck-remove', deck).click();
- equal(count, 1, "djselectableremove should fire once when item is removed.");
- });
-
- test("Prevent Remove Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'},
- deck = $('.selectable-deck', '#qunit-fixture');
- this.textInput.bind('djselectableremove', function(e, item) {
- return false;
- });
- var item = {id: "1", value: 'foo'};
- this.textInput.djselectable('select', item);
- $('.selectable-deck-remove', deck).click();
- equal($('li', deck).length, 1, "Item should not be removed.");
- });
-});
\ No newline at end of file
+++ /dev/null
-/*global define, module, test, expect, equal, ok*/
-
-define(['selectable'], function ($) {
-
- var expectedNamespace = 'djselectable',
- useData = true;
- if (window.uiversion.lastIndexOf('1.10', 0) === 0) {
- // jQuery UI 1.10 introduces a namespace change to include ui-prefix
- expectedNamespace = 'ui-' + expectedNamespace;
- }
- if (window.uiversion.lastIndexOf('1.11', 0) === 0) {
- // jQuery UI 1.11 introduces an instance method to get the current instance
- useData = false;
- }
-
- module("Autocomplete Text Methods Tests");
-
- test("Bind Input", function () {
- expect(2);
- var input = createTextComplete('autocomplete');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- ok(input.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(input.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(input.djselectable('instance'), "input should be bound with djselecable widget");
- }
- });
-
- test("Manual Selection", function () {
- expect(1);
- var item = {id: "1", value: 'foo'},
- input = createTextComplete('autocomplete');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- input.djselectable('select', item);
- equal(input.val(), item.value, "input should get item value");
- });
-
- test("Initial Data", function () {
- expect(1);
- var input = createTextComplete('autocomplete');
- input.val('Foo');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- equal(input.val(), 'Foo', "initial text value should not be lost");
- });
-
-
- module("Autocombobox Text Methods Tests");
-
- test("Bind Input", function () {
- expect(3);
- var input = createTextCombobox('autocombobox'), button;
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- button = $('.ui-combo-button', '#qunit-fixture');
- ok(input.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(input.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(input.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal(button.length, 1, "combobox button should be created");
- });
-
- test("Manual Selection", function () {
- expect(1);
- var item = {id: "1", value: 'foo'},
- input = createTextCombobox('autocombobox');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- input.djselectable('select', item);
- equal(input.val(), item.value, "input should get item value");
- });
-
- test("Initial Data", function () {
- expect(1);
- var input = createTextCombobox('autocombobox');
- input.val('Foo');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- equal(input.val(), 'Foo', "initial text value should not be lost");
- });
-
- module("Autocomplete Select Methods Tests");
-
- test("Bind Input", function () {
- expect(2);
- var inputs = createTextSelect('autocompleteselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createTextSelect('autocompleteselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- equal(textInput.val(), item.value, "input should get item value");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(2);
- var inputs = createTextSelect('autocompleteselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- equal(textInput.val(), 'Foo', "initial text value should not be lost");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- });
-
- module("Autocombobox Select Methods Tests");
-
- test("Bind Input", function () {
- expect(3);
- var inputs = createComboboxSelect('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1], button;
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- button = $('.ui-combo-button', '#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal(button.length, 1, "combobox button should be created");
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createComboboxSelect('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- equal(textInput.val(), item.value, "input should get item value");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(2);
- var inputs = createComboboxSelect('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- equal(textInput.val(), 'Foo', "initial text value should not be lost");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- });
-
- module("Autocomplete Select Multiple Methods Tests");
-
- test("Bind Input", function () {
- expect(3);
- var inputs = createTextSelectMultiple('autocompletemultiple'),
- textInput = inputs[0], deck;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal($('li', deck).length, 0, "no initial deck items");
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createTextSelectMultiple('autocompletemultiple'),
- textInput = inputs[0], hiddenInput;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- hiddenInput = $(':input[type=hidden][name=autocompletemultiple_1]', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(3);
- var inputs = createTextSelectMultiple('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1], deck;
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- equal($('li', deck).length, 1, "one initial deck items");
- });
-
- module("Autocombobox Select Multiple Methods Tests");
-
- test("Bind Input", function () {
- expect(4);
- var inputs = createComboboxSelectMultiple('autocomboboxmultiple'),
- textInput = inputs[0], deck, button;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- button = $('.ui-combo-button', '#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal($('li', deck).length, 0, "no initial deck items");
- equal(button.length, 1, "combobox button should be created");
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createComboboxSelectMultiple('autocomboboxmultiple'),
- textInput = inputs[0], hiddenInput;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- hiddenInput = $(':input[type=hidden][name=autocomboboxmultiple_1]', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(3);
- var inputs = createComboboxSelectMultiple('autocomboboxmultiple'),
- textInput = inputs[0], hiddenInput = inputs[1], deck;
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- equal($('li', deck).length, 1, "one initial deck items");
- });
-});
\ No newline at end of file
+++ /dev/null
-/*global define, module, test, expect, equal, ok*/
-
-define(['selectable'], function ($) {
-
- module("Plugin Options Tests", {
- setup: function () {
- // Patch AJAX requests
- var self = this;
- this.xhr = sinon.useFakeXMLHttpRequest();
- this.requests = [];
- this.xhr.onCreate = function (xhr) {
- self.requests.push(xhr);
- };
- this.input = createTextComplete('autocomplete');
- $('#qunit-fixture').append(this.input);
- bindSelectables('#qunit-fixture');
- },
- teardown: function () {
- this.xhr.restore();
- this.input.djselectable('destroy');
- }
- });
-
- test("Highlight Match On", function () {
- expect(2);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, highlight;
- this.input.djselectable("option", "highlightMatch", true);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- highlight = $('.highlight', item);
- equal(highlight.length, 1, "Highlight should be present");
- equal(highlight.text(), "Ap", "Highlight text should match");
- start();
- }, 300);
- });
-
- test("Highlight Match Off", function () {
- expect(1);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, highlight;
- this.input.djselectable("option", "highlightMatch", false);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- highlight = $('.highlight', item);
- equal(highlight.length, 0, "Highlight should not be present");
- start();
- }, 300);
- });
-
- test("Format Label String (No Highlight)", function () {
- expect(3);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight;
- function customFormat(label, item) {
- return "<span class='custom'>" + label + "</span>";
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", false);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', item);
- equal(highlight.length, 0, "Highlight should not be present");
- start();
- }, 300);
- });
-
- test("Format Label jQuery Object (No Highlight)", function () {
- expect(3);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight
- function customFormat(label, item) {
- return $("<span>").addClass("custom").text(label);
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", false);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', item);
- equal(highlight.length, 0, "Highlight should not be present");
- start();
- }, 300);
- });
-
- test("Format Label String (With Highlight)", function () {
- expect(4);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight;
- function customFormat(label, item) {
- return "<span class='custom'>" + label + "</span>";
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", true);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', custom);
- equal(highlight.length, 1, "Highlight should be present");
- equal(highlight.text(), "Ap", "Highlight text should match");
- start();
- }, 300);
- });
-
- test("Format Label jQuery Object (With Highlight)", function () {
- expect(4);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight;
- function customFormat(label, item) {
- return $("<span>").addClass("custom").text(label);
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", true);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', custom);
- equal(highlight.length, 1, "Highlight should be present");
- equal(highlight.text(), "Ap", "Highlight text should match");
- start();
- }, 300);
- });
-});
\ No newline at end of file
+++ /dev/null
-from __future__ import unicode_literals
-
-from django.urls import reverse
-from django.utils.html import escape
-from django.utils.safestring import SafeData, mark_safe
-
-from ..base import ModelLookup
-from . import Thing
-from .base import BaseSelectableTestCase, SimpleModelLookup
-
-__all__ = (
- 'ModelLookupTestCase',
- 'MultiFieldLookupTestCase',
- 'LookupEscapingTestCase',
-)
-
-
-class ModelLookupTestCase(BaseSelectableTestCase):
- lookup_cls = SimpleModelLookup
-
- def get_lookup_instance(self):
- return self.__class__.lookup_cls()
-
- def test_get_name(self):
- name = self.__class__.lookup_cls.name()
- self.assertEqual(name, 'tests-simplemodellookup')
-
- def test_get_url(self):
- url = self.__class__.lookup_cls.url()
- test_url = reverse('selectable-lookup', args=['tests-simplemodellookup'])
- self.assertEqual(url, test_url)
-
- def test_format_item(self):
- lookup = self.get_lookup_instance()
- thing = Thing()
- item_info = lookup.format_item(thing)
- self.assertTrue('id' in item_info)
- self.assertTrue('value' in item_info)
- self.assertTrue('label' in item_info)
-
- def test_get_query(self):
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'name': 'Thing'})
- other_thing = self.create_thing(data={'name': 'Other Thing'})
- qs = lookup.get_query(request=None, term='other')
- self.assertTrue(thing.pk not in qs.values_list('id', flat=True))
- self.assertTrue(other_thing.pk in qs.values_list('id', flat=True))
-
- def test_create_item(self):
- value = self.get_random_string()
- lookup = self.get_lookup_instance()
- thing = lookup.create_item(value)
- self.assertEqual(thing.__class__, Thing)
- self.assertEqual(thing.name, value)
- self.assertFalse(thing.pk)
-
- def test_get_item(self):
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'name': 'Thing'})
- item = lookup.get_item(thing.pk)
- self.assertEqual(thing, item)
-
- def test_format_item_escaping(self):
- "Id, value and label should be escaped."
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'name': 'Thing'})
- item_info = lookup.format_item(thing)
- self.assertFalse(isinstance(item_info['id'], SafeData))
- self.assertFalse(isinstance(item_info['value'], SafeData))
- self.assertTrue(isinstance(item_info['label'], SafeData))
-
-
-class MultiFieldLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', 'description__icontains', )
-
-
-class MultiFieldLookupTestCase(ModelLookupTestCase):
- lookup_cls = MultiFieldLookup
-
- def test_get_name(self):
- name = self.__class__.lookup_cls.name()
- self.assertEqual(name, 'tests-multifieldlookup')
-
- def test_get_url(self):
- url = self.__class__.lookup_cls.url()
- test_url = reverse('selectable-lookup', args=['tests-multifieldlookup'])
- self.assertEqual(url, test_url)
-
- def test_description_search(self):
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'description': 'Thing'})
- other_thing = self.create_thing(data={'description': 'Other Thing'})
- qs = lookup.get_query(request=None, term='other')
- self.assertTrue(thing.pk not in qs.values_list('id', flat=True))
- self.assertTrue(other_thing.pk in qs.values_list('id', flat=True))
-
-
-class HTMLLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
-
-class SafeHTMLLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
- def get_item_label(self, item):
- "Mark label as safe."
- return mark_safe(item.name)
-
-
-class LookupEscapingTestCase(BaseSelectableTestCase):
-
- def test_escape_html(self):
- "HTML should be escaped by default."
- lookup = HTMLLookup()
- bad_name = "<script>alert('hacked');</script>"
- escaped_name = escape(bad_name)
- thing = self.create_thing(data={'name': bad_name})
- item_info = lookup.format_item(thing)
- self.assertEqual(item_info['label'], escaped_name)
-
- def test_conditional_escape(self):
- "Methods should be able to mark values as safe."
- lookup = SafeHTMLLookup()
- bad_name = "<script>alert('hacked');</script>"
- escaped_name = escape(bad_name)
- thing = self.create_thing(data={'name': bad_name})
- item_info = lookup.format_item(thing)
- self.assertEqual(item_info['label'], bad_name)
+++ /dev/null
-try:
- from unittest.mock import Mock
-except ImportError:
- from mock import Mock
-
-from ..decorators import ajax_required, login_required, staff_member_required
-from .base import BaseSelectableTestCase, SimpleModelLookup
-
-
-__all__ = (
- 'AjaxRequiredLookupTestCase',
- 'LoginRequiredLookupTestCase',
- 'StaffRequiredLookupTestCase',
-)
-
-
-class AjaxRequiredLookupTestCase(BaseSelectableTestCase):
-
- def setUp(self):
- self.lookup = ajax_required(SimpleModelLookup)()
-
- def test_ajax_call(self):
- "Ajax call should yield a successful response."
- request = Mock()
- request.is_ajax = lambda: True
- response = self.lookup.results(request)
- self.assertTrue(response.status_code, 200)
-
- def test_non_ajax_call(self):
- "Non-Ajax call should yield a bad request response."
- request = Mock()
- request.is_ajax = lambda: False
- response = self.lookup.results(request)
- self.assertEqual(response.status_code, 400)
-
-
-class LoginRequiredLookupTestCase(BaseSelectableTestCase):
-
- def setUp(self):
- self.lookup = login_required(SimpleModelLookup)()
-
- def test_authenicated_call(self):
- "Authenicated call should yield a successful response."
- request = Mock()
- user = Mock()
- user.is_authenticated = True
- request.user = user
- response = self.lookup.results(request)
- self.assertTrue(response.status_code, 200)
-
- def test_non_authenicated_call(self):
- "Non-Authenicated call should yield an unauthorized response."
- request = Mock()
- user = Mock()
- user.is_authenticated = False
- request.user = user
- response = self.lookup.results(request)
- self.assertEqual(response.status_code, 401)
-
-
-class StaffRequiredLookupTestCase(BaseSelectableTestCase):
-
- def setUp(self):
- self.lookup = staff_member_required(SimpleModelLookup)()
-
- def test_staff_member_call(self):
- "Staff member call should yield a successful response."
- request = Mock()
- user = Mock()
- user.is_authenticated = True
- user.is_staff = True
- request.user = user
- response = self.lookup.results(request)
- self.assertTrue(response.status_code, 200)
-
- def test_authenicated_but_not_staff(self):
- "Authenicated but non staff call should yield a forbidden response."
- request = Mock()
- user = Mock()
- user.is_authenticated = True
- user.is_staff = False
- request.user = user
- response = self.lookup.results(request)
- self.assertTrue(response.status_code, 403)
-
- def test_non_authenicated_call(self):
- "Non-Authenicated call should yield an unauthorized response."
- request = Mock()
- user = Mock()
- user.is_authenticated = False
- user.is_staff = False
- request.user = user
- response = self.lookup.results(request)
- self.assertEqual(response.status_code, 401)
+++ /dev/null
-from django import forms
-
-from selectable.forms import fields, widgets
-from selectable.tests import ThingLookup
-from selectable.tests.base import BaseSelectableTestCase
-
-
-__all__ = (
- 'AutoCompleteSelectFieldTestCase',
- 'AutoCompleteSelectMultipleFieldTestCase',
-)
-
-class FieldTestMixin(object):
- field_cls = None
- lookup_cls = None
-
- def get_field_instance(self, allow_new=False, limit=None, widget=None):
- return self.field_cls(self.lookup_cls, allow_new=allow_new, limit=limit, widget=widget)
-
- def test_init(self):
- field = self.get_field_instance()
- self.assertEqual(field.lookup_class, self.lookup_cls)
-
- def test_init_with_limit(self):
- field = self.get_field_instance(limit=10)
- self.assertEqual(field.limit, 10)
- self.assertEqual(field.widget.limit, 10)
-
- def test_clean(self):
- self.fail('This test has not yet been written')
-
- def test_dotted_path(self):
- """
- Ensure lookup_class can be imported from a dotted path.
- """
- dotted_path = '.'.join([self.lookup_cls.__module__, self.lookup_cls.__name__])
- field = self.field_cls(dotted_path)
- self.assertEqual(field.lookup_class, self.lookup_cls)
-
- def test_invalid_dotted_path(self):
- """
- An invalid lookup_class dotted path should raise an ImportError.
- """
- with self.assertRaises(ImportError):
- self.field_cls('that.is.an.invalid.path')
-
- def test_dotted_path_wrong_type(self):
- """
- lookup_class must be a subclass of LookupBase.
- """
- dotted_path = 'selectable.forms.fields.AutoCompleteSelectField'
- with self.assertRaises(TypeError):
- self.field_cls(dotted_path)
-
-class AutoCompleteSelectFieldTestCase(BaseSelectableTestCase, FieldTestMixin):
- field_cls = fields.AutoCompleteSelectField
- lookup_cls = ThingLookup
-
- def test_clean(self):
- thing = self.create_thing()
- field = self.get_field_instance()
- value = field.clean([thing.name, thing.id])
- self.assertEqual(thing, value)
-
- def test_new_not_allowed(self):
- field = self.get_field_instance()
- value = self.get_random_string()
- self.assertRaises(forms.ValidationError, field.clean, [value, ''])
-
- def test_new_allowed(self):
- field = self.get_field_instance(allow_new=True)
- value = self.get_random_string()
- value = field.clean([value, ''])
- self.assertTrue(isinstance(value, ThingLookup.model))
-
- def test_default_widget(self):
- field = self.get_field_instance()
- self.assertTrue(isinstance(field.widget, widgets.AutoCompleteSelectWidget))
-
- def test_alternate_widget(self):
- widget_cls = widgets.AutoComboboxWidget
- field = self.get_field_instance(widget=widget_cls)
- self.assertTrue(isinstance(field.widget, widget_cls))
-
- def test_alternate_widget_instance(self):
- widget = widgets.AutoComboboxWidget(self.lookup_cls)
- field = self.get_field_instance(widget=widget)
- self.assertTrue(isinstance(field.widget, widgets.AutoComboboxWidget))
-
- def test_invalid_pk(self):
- field = self.get_field_instance()
- value = self.get_random_string()
- self.assertRaises(forms.ValidationError, field.clean, [value, 'XXX'])
-
-
-class AutoCompleteSelectMultipleFieldTestCase(BaseSelectableTestCase, FieldTestMixin):
- field_cls = fields.AutoCompleteSelectMultipleField
- lookup_cls = ThingLookup
-
- def get_field_instance(self, limit=None, widget=None):
- return self.field_cls(self.lookup_cls, limit=limit, widget=widget)
-
- def test_clean(self):
- thing = self.create_thing()
- field = self.get_field_instance()
- value = field.clean([thing.id])
- self.assertEqual([thing], value)
-
- def test_clean_multiple(self):
- thing = self.create_thing()
- other_thing = self.create_thing()
- field = self.get_field_instance()
- ids = [thing.id, other_thing.id]
- value = field.clean(ids)
- self.assertEqual([thing, other_thing], value)
-
- def test_default_widget(self):
- field = self.get_field_instance()
- self.assertTrue(isinstance(field.widget, widgets.AutoCompleteSelectMultipleWidget))
-
- def test_alternate_widget(self):
- widget_cls = widgets.AutoComboboxSelectMultipleWidget
- field = self.get_field_instance(widget=widget_cls)
- self.assertTrue(isinstance(field.widget, widget_cls))
-
- def test_alternate_widget_instance(self):
- widget = widgets.AutoComboboxSelectMultipleWidget(self.lookup_cls)
- field = self.get_field_instance(widget=widget)
- self.assertTrue(isinstance(field.widget, widgets.AutoComboboxSelectMultipleWidget))
-
- def test_invalid_pk(self):
- field = self.get_field_instance()
- value = self.get_random_string()
- self.assertRaises(forms.ValidationError, field.clean, ['XXX', ])
+++ /dev/null
-from ..forms import BaseLookupForm
-from .base import BaseSelectableTestCase
-
-
-__all__ = (
- 'BaseLookupFormTestCase',
-)
-
-
-class BaseLookupFormTestCase(BaseSelectableTestCase):
-
- def get_valid_data(self):
- data = {
- 'term': 'foo',
- 'limit': 10,
- }
- return data
-
- def test_valid_data(self):
- data = self.get_valid_data()
- form = BaseLookupForm(data)
- self.assertTrue(form.is_valid(), "%s" % form.errors)
-
- def test_invalid_limit(self):
- """
- Test giving the form an invalid limit.
- """
-
- data = self.get_valid_data()
- data['limit'] = 'bar'
- form = BaseLookupForm(data)
- self.assertFalse(form.is_valid())
-
- def test_no_limit(self):
- """
- If SELECTABLE_MAX_LIMIT is set and limit is not given then
- the form will return SELECTABLE_MAX_LIMIT.
- """
-
- with self.settings(SELECTABLE_MAX_LIMIT=25):
- data = self.get_valid_data()
- if 'limit' in data:
- del data['limit']
- form = BaseLookupForm(data)
- self.assertTrue(form.is_valid(), "%s" % form.errors)
- self.assertEqual(form.cleaned_data['limit'], 25)
-
- def test_no_max_set(self):
- """
- If SELECTABLE_MAX_LIMIT is not set but given then the form
- will return the given limit.
- """
-
- with self.settings(SELECTABLE_MAX_LIMIT=None):
- data = self.get_valid_data()
- form = BaseLookupForm(data)
- self.assertTrue(form.is_valid(), "%s" % form.errors)
- if 'limit' in data:
- self.assertTrue(form.cleaned_data['limit'], data['limit'])
-
- def test_no_max_set_not_given(self):
- """
- If SELECTABLE_MAX_LIMIT is not set and not given then the form
- will return no limit.
- """
-
- with self.settings(SELECTABLE_MAX_LIMIT=None):
- data = self.get_valid_data()
- if 'limit' in data:
- del data['limit']
- form = BaseLookupForm(data)
- self.assertTrue(form.is_valid(), "%s" % form.errors)
- self.assertFalse(form.cleaned_data.get('limit'))
-
- def test_over_limit(self):
- """
- If SELECTABLE_MAX_LIMIT is set and limit given is greater then
- the form will return SELECTABLE_MAX_LIMIT.
- """
-
- with self.settings(SELECTABLE_MAX_LIMIT=25):
- data = self.get_valid_data()
- data['limit'] = 125
- form = BaseLookupForm(data)
- self.assertTrue(form.is_valid(), "%s" % form.errors)
- self.assertEqual(form.cleaned_data['limit'], 25)
+++ /dev/null
-"""
-Larger functional tests for fields and widgets.
-"""
-from __future__ import unicode_literals
-
-from django import forms
-
-from ..forms import AutoCompleteSelectField, AutoCompleteSelectMultipleField
-from ..forms import AutoCompleteSelectWidget, AutoComboboxSelectWidget
-from . import ManyThing, OtherThing, ThingLookup
-from .base import BaseSelectableTestCase
-
-
-__all__ = (
- 'FuncAutoCompleteSelectTestCase',
- 'FuncSelectModelChoiceTestCase',
- 'FuncComboboxModelChoiceTestCase',
- 'FuncManytoManyMultipleSelectTestCase',
- 'FuncFormTestCase',
-)
-
-
-class OtherThingForm(forms.ModelForm):
-
- thing = AutoCompleteSelectField(lookup_class=ThingLookup)
-
- class Meta(object):
- model = OtherThing
- fields = ('name', 'thing', )
-
-
-class FuncAutoCompleteSelectTestCase(BaseSelectableTestCase):
-
- def setUp(self):
- self.test_thing = self.create_thing()
-
- def test_valid_form(self):
- "Valid form using an AutoCompleteSelectField."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': self.test_thing.pk, # Hidden input
- }
- form = OtherThingForm(data=data)
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_invalid_form_missing_selected_pk(self):
- "Invalid form using an AutoCompleteSelectField."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': '', # Hidden input
- }
- form = OtherThingForm(data=data)
- self.assertFalse(form.is_valid(), 'Form should not be valid')
- self.assertFalse('name' in form.errors)
- self.assertTrue('thing' in form.errors)
-
- def test_invalid_form_missing_name(self):
- "Invalid form using an AutoCompleteSelectField."
- data = {
- 'name': '',
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': self.test_thing.pk, # Hidden input
- }
- form = OtherThingForm(data=data)
- self.assertFalse(form.is_valid(), 'Form should not be valid')
- self.assertTrue('name' in form.errors)
- self.assertFalse('thing' in form.errors)
-
- def test_invalid_but_still_selected(self):
- "Invalid form should keep selected item."
- data = {
- 'name': '',
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': self.test_thing.pk, # Hidden input
- }
- form = OtherThingForm(data=data)
- self.assertFalse(form.is_valid(), 'Form should not be valid')
- rendered_form = form.as_p()
- # Selected text should be populated
- self.assertInHTML(
- '''
- <input data-selectable-allow-new="false" data-selectable-type="text"
- data-selectable-url="/selectable-tests/selectable-thinglookup/"
- id="id_thing_0" name="thing_0" type="text" value="{}" {} />
- '''.format(self.test_thing.name,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form
- )
- # Selected pk should be populated
- self.assertInHTML(
- '''
- <input data-selectable-type="hidden" name="thing_1" id="id_thing_1"
- type="hidden" value="{}" {} />
- '''.format(self.test_thing.pk,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form,
- )
-
- def test_populate_from_model(self):
- "Populate from existing model."
- other_thing = OtherThing.objects.create(thing=self.test_thing, name='a')
- form = OtherThingForm(instance=other_thing)
- rendered_form = form.as_p()
- # Selected text should be populated
- self.assertInHTML(
- '''
- <input data-selectable-allow-new="false" data-selectable-type="text"
- data-selectable-url="/selectable-tests/selectable-thinglookup/"
- id="id_thing_0" name="thing_0" type="text" value="{}" {} />
- '''.format(self.test_thing.name,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form
- )
- # Selected pk should be populated
- self.assertInHTML(
- '''
- <input data-selectable-type="hidden" name="thing_1" id="id_thing_1"
- type="hidden" value="{}" {} />
- '''.format(self.test_thing.pk,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form
- )
-
-
-class SelectWidgetForm(forms.ModelForm):
-
- class Meta(object):
- model = OtherThing
- fields = ('name', 'thing', )
- widgets = {
- 'thing': AutoCompleteSelectWidget(lookup_class=ThingLookup)
- }
-
-
-class FuncSelectModelChoiceTestCase(BaseSelectableTestCase):
- """
- Functional tests for AutoCompleteSelectWidget compatibility
- with a ModelChoiceField.
- """
-
- def setUp(self):
- self.test_thing = self.create_thing()
-
- def test_valid_form(self):
- "Valid form using an AutoCompleteSelectWidget."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': self.test_thing.pk, # Hidden input
- }
- form = SelectWidgetForm(data=data)
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_missing_pk(self):
- "Invalid form (missing required pk) using an AutoCompleteSelectWidget."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': '', # Hidden input missing
- }
- form = SelectWidgetForm(data=data)
- self.assertFalse(form.is_valid())
- self.assertTrue('thing' in form.errors)
-
- def test_invalid_pk(self):
- "Invalid form (invalid pk value) using an AutoCompleteSelectWidget."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': 'XXX', # Hidden input doesn't match a PK
- }
- form = SelectWidgetForm(data=data)
- self.assertFalse(form.is_valid())
- self.assertTrue('thing' in form.errors)
-
- def test_post_compatibility(self):
- """
- If new items are not allowed then the original field
- name can be included in the POST with the selected id.
- """
- data = {
- 'name': self.get_random_string(),
- 'thing': self.test_thing.pk,
- }
- form = SelectWidgetForm(data=data)
- self.assertTrue(form.is_valid(), str(form.errors))
-
-
-class ComboboxSelectWidgetForm(forms.ModelForm):
-
- class Meta(object):
- model = OtherThing
- fields = ('name', 'thing', )
- widgets = {
- 'thing': AutoComboboxSelectWidget(lookup_class=ThingLookup)
- }
-
-
-class FuncComboboxModelChoiceTestCase(BaseSelectableTestCase):
- """
- Functional tests for AutoComboboxSelectWidget compatibility
- with a ModelChoiceField.
- """
-
- def setUp(self):
- self.test_thing = self.create_thing()
-
- def test_valid_form(self):
- "Valid form using an AutoComboboxSelectWidget."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': self.test_thing.pk, # Hidden input
- }
- form = ComboboxSelectWidgetForm(data=data)
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_missing_pk(self):
- "Invalid form (missing required pk) using an AutoComboboxSelectWidget."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': '', # Hidden input missing
- }
- form = ComboboxSelectWidgetForm(data=data)
- self.assertFalse(form.is_valid())
- self.assertTrue('thing' in form.errors)
-
- def test_invalid_pk(self):
- "Invalid form (invalid pk value) using an AutoComboboxSelectWidget."
- data = {
- 'name': self.get_random_string(),
- 'thing_0': self.test_thing.name, # Text input
- 'thing_1': 'XXX', # Hidden input doesn't match a PK
- }
- form = ComboboxSelectWidgetForm(data=data)
- self.assertFalse(form.is_valid())
- self.assertTrue('thing' in form.errors)
-
- def test_post_compatibility(self):
- """
- If new items are not allowed then the original field
- name can be included in the POST with the selected id.
- """
- data = {
- 'name': self.get_random_string(),
- 'thing': self.test_thing.pk,
- }
- form = ComboboxSelectWidgetForm(data=data)
- self.assertTrue(form.is_valid(), str(form.errors))
-
-
-class ManyThingForm(forms.ModelForm):
-
- things = AutoCompleteSelectMultipleField(lookup_class=ThingLookup)
-
- class Meta(object):
- model = ManyThing
- fields = ('name', 'things', )
-
-
-class FuncManytoManyMultipleSelectTestCase(BaseSelectableTestCase):
- """
- Functional tests for AutoCompleteSelectMultipleField compatibility
- with a ManyToManyField.
- """
-
- def setUp(self):
- self.test_thing = self.create_thing()
-
- def test_valid_form(self):
- "Valid form using an AutoCompleteSelectMultipleField."
- data = {
- 'name': self.get_random_string(),
- 'things_0': '', # Text input
- 'things_1': [self.test_thing.pk, ], # Hidden inputs
- }
- form = ManyThingForm(data=data)
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_valid_save(self):
- "Saving data from a valid form."
- data = {
- 'name': self.get_random_string(),
- 'things_0': '', # Text input
- 'things_1': [self.test_thing.pk, ], # Hidden inputs
- }
- form = ManyThingForm(data=data)
- manything = form.save()
- self.assertEqual(manything.name, data['name'])
- things = manything.things.all()
- self.assertEqual(things.count(), 1)
- self.assertTrue(self.test_thing in things)
-
- def test_not_required(self):
- "Valid form where many to many is not required."
- data = {
- 'name': self.get_random_string(),
- 'things_0': '', # Text input
- 'things_1': [], # Hidden inputs
- }
- form = ManyThingForm(data=data)
- form.fields['things'].required = False
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_not_required_save(self):
- "Saving data when many to many is not required."
- data = {
- 'name': self.get_random_string(),
- 'things_0': '', # Text input
- 'things_1': [], # Hidden inputs
- }
- form = ManyThingForm(data=data)
- form.fields['things'].required = False
- manything = form.save()
- self.assertEqual(manything.name, data['name'])
- things = manything.things.all()
- self.assertEqual(things.count(), 0)
-
- def test_has_changed(self):
- "Populate intial data from a model."
- manything = ManyThing.objects.create(name='Foo')
- thing_1 = self.create_thing()
- manything.things.add(thing_1)
- data = {
- 'name': manything.name,
- 'things_0': '', # Text input
- 'things_1': [thing_1.pk], # Hidden inputs
- }
- form = ManyThingForm(data=data, instance=manything)
- self.assertFalse(form.has_changed(), str(form.changed_data))
-
- def test_post_compatibility(self):
- """
- If new items are not allowed then the original field
- name can be included in the POST with the selected ids.
- """
- data = {
- 'name': self.get_random_string(),
- 'things': [self.test_thing.pk, ],
- }
- form = ManyThingForm(data=data)
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_render_form(self):
- thing_1 = self.create_thing()
- manything = ManyThing.objects.create(name='Foo')
- manything.things.add(thing_1)
- form = ManyThingForm(instance=manything)
- rendered = form.as_p()
- self.assertIn('title="{0}"'.format(thing_1.name),
- rendered)
-
-
-class SimpleForm(forms.Form):
- "Non-model form usage."
- thing = AutoCompleteSelectField(lookup_class=ThingLookup)
- new_thing = AutoCompleteSelectField(lookup_class=ThingLookup, allow_new=True)
- things = AutoCompleteSelectMultipleField(lookup_class=ThingLookup)
-
-
-class FuncFormTestCase(BaseSelectableTestCase):
- """
- Functional tests for using AutoCompleteSelectField
- and AutoCompleteSelectMultipleField outside the context
- of a ModelForm.
- """
-
- def setUp(self):
- self.test_thing = self.create_thing()
-
- def test_blank_new_item(self):
- "Regression test for #91. new_thing is required but both are blank."
- data = {
- 'thing_0': self.test_thing.name,
- 'thing_1': self.test_thing.pk,
- 'new_thing_0': '',
- 'new_thing_1': '',
- 'things_0': '',
- 'things_1': [self.test_thing.pk, ]
- }
- form = SimpleForm(data=data)
- self.assertFalse(form.is_valid())
- self.assertTrue('new_thing' in form.errors)
-
- def test_has_changed_with_empty_permitted(self):
- """
- Regression test for #92. has_changed fails when there is no initial and
- allow_new=False.
- """
- data = {
- 'thing_0': '',
- 'thing_1': self.test_thing.pk,
- 'new_thing_0': self.test_thing.name,
- 'new_thing_1': self.test_thing.pk,
- 'things_0': '',
- 'things_1': [self.test_thing.pk, ]
- }
- form = SimpleForm(data=data, empty_permitted=True, use_required_attribute=False)
- self.assertTrue(form.has_changed())
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_not_changed(self):
- """
- Regression test for #92. has_changed fails when there is no initial and
- allow_new=False.
- """
- data = {
- 'thing_0': self.test_thing.name,
- 'thing_1': self.test_thing.pk,
- 'new_thing_0': self.test_thing.name,
- 'new_thing_1': self.test_thing.pk,
- 'things_0': '',
- 'things_1': [self.test_thing.pk, ]
- }
- initial = {
- 'thing': self.test_thing.pk,
- 'new_thing': self.test_thing.pk,
- 'things': [self.test_thing.pk, ]
- }
- form = SimpleForm(data=data, initial=initial)
- self.assertFalse(form.has_changed())
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_not_changed_with_empty_permitted(self):
- """
- Regression test for #92. has_changed fails when there is no initial and
- allow_new=False.
- """
- data = {
- 'thing_0': '',
- 'thing_1': '',
- 'new_thing_0': '',
- 'new_thing_1': '',
- 'things_0': '',
- 'things_1': '',
- }
- initial = {
- 'thing': '',
- 'new_thing': '',
- 'things': '',
- }
- form = SimpleForm(data=data, initial=initial, empty_permitted=True, use_required_attribute=False)
- self.assertFalse(form.has_changed(), str(form.changed_data))
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_no_initial_with_empty_permitted(self):
- """
- If empty data is submitted and allowed with no initial then
- the form should not be seen as changed.
- """
- data = {
- 'thing_0': '',
- 'thing_1': '',
- 'new_thing_0': '',
- 'new_thing_1': '',
- 'things_0': '',
- 'things_1': '',
- }
- form = SimpleForm(data=data, empty_permitted=True, use_required_attribute=False)
- self.assertFalse(form.has_changed(), str(form.changed_data))
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_no_data_with_empty_permitted(self):
- """
- If no data is submitted and allowed with no initial then
- the form should not be seen as changed.
- """
- form = SimpleForm(data={}, empty_permitted=True, use_required_attribute=False)
- self.assertFalse(form.has_changed(), str(form.changed_data))
- self.assertTrue(form.is_valid(), str(form.errors))
-
- def test_select_multiple_changed(self):
- """
- Detect changes for a multiple select input with and without
- initial data.
- """
- data = {
- 'thing_0': '',
- 'thing_1': '',
- 'new_thing_0': '',
- 'new_thing_1': '',
- 'things_0': '',
- 'things_1': [self.test_thing.pk, ]
- }
- form = SimpleForm(data=data)
- self.assertTrue(form.has_changed())
- self.assertTrue('things' in form.changed_data)
-
- initial = {
- 'thing': '',
- 'new_thing': '',
- 'things': [self.test_thing.pk, ],
- }
- form = SimpleForm(data=data, initial=initial)
- self.assertFalse(form.has_changed(), str(form.changed_data))
-
- initial = {
- 'thing': '',
- 'new_thing': '',
- 'things': [],
- }
- form = SimpleForm(data=data, initial=initial)
- self.assertTrue(form.has_changed())
- self.assertTrue('things' in form.changed_data)
-
- def test_single_select_changed(self):
- """
- Detect changes for a single select input with and without
- initial data.
- """
- data = {
- 'thing_0': '',
- 'thing_1': self.test_thing.pk,
- 'new_thing_0': '',
- 'new_thing_1': '',
- 'things_0': '',
- 'things_1': ''
- }
- form = SimpleForm(data=data)
- self.assertTrue(form.has_changed())
- self.assertTrue('thing' in form.changed_data)
-
- initial = {
- 'thing': self.test_thing.pk,
- 'new_thing': '',
- 'things': '',
- }
- form = SimpleForm(data=data, initial=initial)
- self.assertFalse(form.has_changed(), str(form.changed_data))
-
- initial = {
- 'thing': '',
- 'new_thing': '',
- 'things': '',
- }
- form = SimpleForm(data=data, initial=initial)
- self.assertTrue(form.has_changed())
- self.assertTrue('thing' in form.changed_data)
-
- def test_new_select_changed(self):
- """
- Detect changes for a single select input which allows new items
- with and without initial data.
- """
- data = {
- 'thing_0': '',
- 'thing_1': '',
- 'new_thing_0': 'Foo',
- 'new_thing_1': '',
- 'things_0': '',
- 'things_1': ''
- }
- form = SimpleForm(data=data)
- self.assertTrue(form.has_changed())
- self.assertTrue('new_thing' in form.changed_data)
-
- initial = {
- 'thing': '',
- 'new_thing': ['Foo', None],
- 'things': '',
- }
- form = SimpleForm(data=data, initial=initial)
- self.assertFalse(form.has_changed(), str(form.changed_data))
-
- initial = {
- 'thing': '',
- 'new_thing': '',
- 'things': '',
- }
- form = SimpleForm(data=data, initial=initial)
- self.assertTrue(form.has_changed())
- self.assertTrue('new_thing' in form.changed_data)
+++ /dev/null
-from django.template import Template, Context
-
-from .base import BaseSelectableTestCase
-
-__all__ = (
- 'JqueryTagTestCase',
- 'ThemeTagTestCase',
-)
-
-
-class JqueryTagTestCase(BaseSelectableTestCase):
-
- def assertJQueryVersion(self, result, version):
- expected = "//ajax.googleapis.com/ajax/libs/jquery/%s/jquery.min.js" % version
- self.assertTrue(expected in result)
-
- def assertUIVersion(self, result, version):
- expected = "//ajax.googleapis.com/ajax/libs/jqueryui/%s/jquery-ui.js" % version
- self.assertTrue(expected in result)
-
- def test_render(self):
- "Render template tag with default versions."
- template = Template("{% load selectable_tags %}{% include_jquery_libs %}")
- context = Context({})
- result = template.render(context)
- self.assertJQueryVersion(result, '1.12.4')
- self.assertUIVersion(result, '1.11.4')
-
- def test_render_jquery_version(self):
- "Render template tag with specified jQuery version."
- template = Template("{% load selectable_tags %}{% include_jquery_libs '1.4.3' %}")
- context = Context({})
- result = template.render(context)
- self.assertJQueryVersion(result, '1.4.3')
-
- def test_render_variable_jquery_version(self):
- "Render using jQuery version from the template context."
- version = '1.4.3'
- template = Template("{% load selectable_tags %}{% include_jquery_libs version %}")
- context = Context({'version': version})
- result = template.render(context)
- self.assertJQueryVersion(result, '1.4.3')
-
- def test_render_jquery_ui_version(self):
- "Render template tag with specified jQuery UI version."
- template = Template("{% load selectable_tags %}{% include_jquery_libs '1.4.3' '1.8.13' %}")
- context = Context({})
- result = template.render(context)
- self.assertUIVersion(result, '1.8.13')
-
- def test_render_variable_jquery_ui_version(self):
- "Render using jQuery UI version from the template context."
- version = '1.8.13'
- template = Template("{% load selectable_tags %}{% include_jquery_libs '1.4.3' version %}")
- context = Context({'version': version})
- result = template.render(context)
- self.assertUIVersion(result, '1.8.13')
-
- def test_render_no_jquery(self):
- "Render template tag without jQuery."
- template = Template("{% load selectable_tags %}{% include_jquery_libs '' %}")
- context = Context({})
- result = template.render(context)
- self.assertTrue('jquery.min.js' not in result)
-
- def test_render_no_jquery_ui(self):
- "Render template tag without jQuery UI."
- template = Template("{% load selectable_tags %}{% include_jquery_libs '1.7.2' '' %}")
- context = Context({})
- result = template.render(context)
- self.assertTrue('jquery-ui.js' not in result)
-
-
-class ThemeTagTestCase(BaseSelectableTestCase):
-
- def assertUICSS(self, result, theme, version):
- expected = "//ajax.googleapis.com/ajax/libs/jqueryui/%s/themes/%s/jquery-ui.css" % (version, theme)
- self.assertTrue(expected in result)
-
- def test_render(self):
- "Render template tag with default settings."
- template = Template("{% load selectable_tags %}{% include_ui_theme %}")
- context = Context({})
- result = template.render(context)
- self.assertUICSS(result, 'smoothness', '1.11.4')
-
- def test_render_version(self):
- "Render template tag with alternate version."
- template = Template("{% load selectable_tags %}{% include_ui_theme 'base' '1.8.13' %}")
- context = Context({})
- result = template.render(context)
- self.assertUICSS(result, 'base', '1.8.13')
-
- def test_variable_version(self):
- "Render using version from content variable."
- version = '1.8.13'
- template = Template("{% load selectable_tags %}{% include_ui_theme 'base' version %}")
- context = Context({'version': version})
- result = template.render(context)
- self.assertUICSS(result, 'base', version)
-
- def test_render_theme(self):
- "Render template tag with alternate theme."
- template = Template("{% load selectable_tags %}{% include_ui_theme 'ui-lightness' %}")
- context = Context({})
- result = template.render(context)
- self.assertUICSS(result, 'ui-lightness', '1.11.4')
-
- def test_variable_theme(self):
- "Render using theme from content variable."
- theme = 'ui-lightness'
- template = Template("{% load selectable_tags %}{% include_ui_theme theme %}")
- context = Context({'theme': theme})
- result = template.render(context)
- self.assertUICSS(result, theme, '1.11.4')
+++ /dev/null
-from __future__ import division
-
-import json
-
-from django.conf import settings
-from django.urls import reverse
-from django.test import override_settings
-
-from . import ThingLookup
-from .base import BaseSelectableTestCase
-
-
-__all__ = (
- 'SelectableViewTest',
-)
-
-
-@override_settings(SELECTABLE_MAX_LIMIT=25)
-class SelectableViewTest(BaseSelectableTestCase):
-
- def setUp(self):
- super(SelectableViewTest, self).setUp()
- self.url = ThingLookup.url()
- self.lookup = ThingLookup()
- self.thing = self.create_thing()
- self.other_thing = self.create_thing()
-
- def test_response_type(self):
- response = self.client.get(self.url)
- self.assertEqual(response['Content-Type'], 'application/json')
-
- def test_response_keys(self):
- response = self.client.get(self.url)
- data = json.loads(response.content.decode('utf-8'))
- for result in data.get('data'):
- self.assertTrue('id' in result)
- self.assertTrue('value' in result)
- self.assertTrue('label' in result)
-
- def test_no_term_lookup(self):
- data = {}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data), 2)
-
- def test_simple_term_lookup(self):
- data = {'term': self.thing.name}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data), 2)
- self.assertEqual(len(data.get('data')), 1)
-
- def test_unknown_lookup(self):
- unknown_url = reverse('selectable-lookup', args=["XXXXXXX"])
- response = self.client.get(unknown_url)
- self.assertEqual(response.status_code, 404)
-
- def test_basic_limit(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT):
- self.create_thing(data={'name': 'Thing%s' % i})
- response = self.client.get(self.url)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
- meta = data.get('meta')
- self.assertTrue('next_page' in meta)
-
- def test_get_next_page(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT * 2):
- self.create_thing(data={'name': 'Thing%s' % i})
- data = {'term': 'Thing', 'page': 2}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
- # No next page
- meta = data.get('meta')
- self.assertFalse('next_page' in meta)
-
- def test_request_more_than_max(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT):
- self.create_thing(data={'name': 'Thing%s' % i})
- data = {'term': '', 'limit': settings.SELECTABLE_MAX_LIMIT * 2}
- response = self.client.get(self.url)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
-
- def test_request_less_than_max(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT):
- self.create_thing(data={'name': 'Thing%s' % i})
- new_limit = settings.SELECTABLE_MAX_LIMIT // 2
- data = {'term': '', 'limit': new_limit}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), new_limit)
+++ /dev/null
-import json
-
-from django import forms
-from django.utils.http import urlencode
-
-from . import Thing, ThingLookup
-from ..compat import urlparse
-from ..forms import widgets
-from .base import BaseSelectableTestCase, parsed_inputs, parsed_widget_attributes
-
-__all__ = (
- 'AutoCompleteWidgetTestCase',
- 'AutoCompleteSelectWidgetTestCase',
- 'AutoComboboxWidgetTestCase',
- 'AutoComboboxSelectWidgetTestCase',
- 'AutoCompleteSelectMultipleWidgetTestCase',
- 'AutoComboboxSelectMultipleWidgetTestCase',
-)
-
-
-class WidgetTestMixin(object):
- widget_cls = None
- lookup_cls = None
-
- def get_widget_instance(self, **kwargs):
- return self.__class__.widget_cls(self.__class__.lookup_cls, **kwargs)
-
- def test_init(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.lookup_class, self.__class__.lookup_cls)
-
- def test_dotted_path(self):
- """
- Ensure lookup_class can be imported from a dotted path.
- """
- dotted_path = '.'.join([self.__class__.lookup_cls.__module__, self.__class__.lookup_cls.__name__])
- widget = self.__class__.widget_cls(dotted_path)
- self.assertEqual(widget.lookup_class, self.__class__.lookup_cls)
-
- def test_invalid_dotted_path(self):
- """
- An invalid lookup_class dotted path should raise an ImportError.
- """
- with self.assertRaises(ImportError):
- self.__class__.widget_cls('that.is.an.invalid.path')
-
- def test_dotted_path_wrong_type(self):
- """
- lookup_class must be a subclass of LookupBase.
- """
- dotted_path = 'selectable.forms.widgets.AutoCompleteWidget'
- with self.assertRaises(TypeError):
- self.__class__.widget_cls(dotted_path)
-
-
-class AutoCompleteWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
- widget_cls = widgets.AutoCompleteWidget
- lookup_cls = ThingLookup
-
- def test_rendered_attrs(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget)
- self.assertTrue('data-selectable-url' in attrs)
- self.assertTrue('data-selectable-type' in attrs)
- self.assertTrue('data-selectable-allow-new' in attrs)
-
- def test_update_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance()
- widget.update_query_parameters(params)
- attrs = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertTrue('limit=10' in query)
-
- def test_initial_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance(query_params=params)
- attrs = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_build_selectable_options(self):
- "Serialize selectable options as json in data attribute."
- options = {'autoFocus': True}
- widget = self.get_widget_instance(attrs={'data-selectable-options': options})
- attrs = parsed_widget_attributes(widget)
- self.assertTrue('data-selectable-options' in attrs)
- self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
-
-
-class AutoCompleteSelectWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
- widget_cls = widgets.AutoCompleteSelectWidget
- lookup_cls = ThingLookup
-
- def test_has_complete_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[0].__class__, widgets.AutoCompleteWidget)
-
- def test_has_hidden_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[1].__class__, forms.HiddenInput)
-
- def test_hidden_type(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget.widgets[1])
- self.assertTrue('data-selectable-type' in attrs)
- self.assertEqual(attrs['data-selectable-type'], 'hidden')
-
- def test_update_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance()
- widget.update_query_parameters(params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertTrue('limit=10' in query)
-
- def test_initial_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance(query_params=params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_build_selectable_options(self):
- "Serialize selectable options as json in data attribute."
- options = {'autoFocus': True}
- widget = self.get_widget_instance(attrs={'data-selectable-options': options})
- attrs = parsed_widget_attributes(widget.widgets[0])
- self.assertTrue('data-selectable-options' in attrs)
- self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
-
- def test_postdata_compatible_with_select(self):
- "Checks postdata for values that a select widget would generate."
- postdata = {'fruit': '1'}
- widget = self.get_widget_instance()
- widget_val = widget.value_from_datadict(postdata, [], 'fruit')
- self.assertEqual(widget_val, '1')
-
-
-class AutoComboboxWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
- widget_cls = widgets.AutoComboboxWidget
- lookup_cls = ThingLookup
-
- def test_rendered_attrs(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget)
- self.assertTrue('data-selectable-url' in attrs)
- self.assertTrue('data-selectable-type' in attrs)
- self.assertTrue('data-selectable-allow-new' in attrs)
-
- def test_update_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance()
- widget.update_query_parameters(params)
- attrs = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertTrue('limit=10' in query)
-
- def test_initial_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance(query_params=params)
- attrs = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_build_selectable_options(self):
- "Serialize selectable options as json in data attribute."
- options = {'autoFocus': True}
- widget = self.get_widget_instance(attrs={'data-selectable-options': options})
- attrs = parsed_widget_attributes(widget)
- self.assertTrue('data-selectable-options' in attrs)
- self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
-
-
-class AutoComboboxSelectWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
- widget_cls = widgets.AutoComboboxSelectWidget
- lookup_cls = ThingLookup
-
- def test_has_complete_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[0].__class__, widgets.AutoComboboxWidget)
-
- def test_has_hidden_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[1].__class__, forms.HiddenInput)
-
- def test_hidden_type(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget.widgets[1])
- self.assertTrue('data-selectable-type' in attrs)
- self.assertEqual(attrs['data-selectable-type'], 'hidden')
-
- def test_update_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance()
- widget.update_query_parameters(params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertTrue('limit=10' in query)
-
- def test_initial_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance(query_params=params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_build_selectable_options(self):
- "Serialize selectable options as json in data attribute."
- options = {'autoFocus': True}
- widget = self.get_widget_instance(attrs={'data-selectable-options': options})
- attrs = parsed_widget_attributes(widget.widgets[0])
- self.assertTrue('data-selectable-options' in attrs)
- self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
-
-
-class AutoCompleteSelectMultipleWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
- widget_cls = widgets.AutoCompleteSelectMultipleWidget
- lookup_cls = ThingLookup
-
- def test_has_complete_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[0].__class__, widgets.AutoCompleteWidget)
-
- def test_multiple_attr(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget.widgets[0])
- self.assertTrue('data-selectable-multiple' in attrs)
- self.assertEqual(attrs['data-selectable-multiple'], 'true')
-
- def test_has_hidden_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[1].__class__, widgets.LookupMultipleHiddenInput)
-
- def test_hidden_type(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget.widgets[1])
- self.assertTrue('data-selectable-type' in attrs)
- self.assertEqual(attrs['data-selectable-type'], 'hidden-multiple')
-
- def test_render_single(self):
- widget = self.get_widget_instance()
- val = 4
- rendered_value = widget.render('field_name', val)
- inputs = parsed_inputs(rendered_value)
- field = inputs['field_name_1'][0]
- attributes = dict(field.attributes)
- self.assertEqual(attributes['data-selectable-type'], 'hidden-multiple')
- self.assertEqual(attributes['type'], 'hidden')
- self.assertEqual(int(attributes['value']), val)
-
- def test_render_list(self):
- widget = self.get_widget_instance()
- list_val = [8, 5]
- rendered_value = widget.render('field_name', list_val)
- inputs = parsed_inputs(rendered_value)
- found_values = []
- for field in inputs['field_name_1']:
- attributes = dict(field.attributes)
- self.assertEqual(attributes['data-selectable-type'], 'hidden-multiple')
- self.assertEqual(attributes['type'], 'hidden')
- found_values.append(int(attributes['value']))
- self.assertListEqual(found_values, list_val)
-
- def test_render_qs(self):
- widget = self.get_widget_instance()
- t1 = self.create_thing()
- t2 = self.create_thing()
- qs_val = Thing.objects.filter(pk__in=[t1.pk, t2.pk])
- rendered_value = widget.render('field_name', qs_val)
- inputs = parsed_inputs(rendered_value)
- found_values = []
- found_titles = []
- for field in inputs['field_name_1']:
- attributes = dict(field.attributes)
- self.assertEqual(attributes['data-selectable-type'], 'hidden-multiple')
- self.assertEqual(attributes['type'], 'hidden')
- found_titles.append(attributes['title'])
- found_values.append(attributes['value'])
- self.assertListEqual(found_values, [str(t1.pk), str(t2.pk)])
- self.assertListEqual(found_titles, [t1.name, t2.name])
-
- def test_update_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance()
- widget.update_query_parameters(params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertTrue('limit=10' in query)
-
- def test_initial_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance(query_params=params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_build_selectable_options(self):
- "Serialize selectable options as json in data attribute."
- options = {'autoFocus': True}
- widget = self.get_widget_instance(attrs={'data-selectable-options': options})
- attrs = parsed_widget_attributes(widget.widgets[0])
- self.assertTrue('data-selectable-options' in attrs)
- self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
-
-
-class AutoComboboxSelectMultipleWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
- widget_cls = widgets.AutoComboboxSelectMultipleWidget
- lookup_cls = ThingLookup
-
- def test_has_complete_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[0].__class__, widgets.AutoComboboxWidget)
-
- def test_multiple_attr(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget.widgets[0])
- self.assertTrue('data-selectable-multiple' in attrs)
- self.assertEqual(attrs['data-selectable-multiple'], 'true')
-
- def test_has_hidden_widget(self):
- widget = self.get_widget_instance()
- self.assertEqual(widget.widgets[1].__class__, widgets.LookupMultipleHiddenInput)
-
- def test_hidden_type(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget.widgets[1])
- self.assertTrue('data-selectable-type' in attrs)
- self.assertEqual(attrs['data-selectable-type'], 'hidden-multiple')
-
- def test_render_single(self):
- widget = self.get_widget_instance()
- val = 4
- rendered_value = widget.render('field_name', val)
- inputs = parsed_inputs(rendered_value)
- field = inputs['field_name_1'][0]
- attributes = dict(field.attributes)
- self.assertEqual(attributes['data-selectable-type'], 'hidden-multiple')
- self.assertEqual(attributes['type'], 'hidden')
- self.assertEqual(attributes['value'], str(val))
-
- def test_render_list(self):
- widget = self.get_widget_instance()
- list_val = [8, 5]
- rendered_value = widget.render('field_name', list_val)
- inputs = parsed_inputs(rendered_value)
- found_values = []
- for field in inputs['field_name_1']:
- attributes = dict(field.attributes)
- self.assertEqual(attributes['data-selectable-type'], 'hidden-multiple')
- self.assertEqual(attributes['type'], 'hidden')
- found_values.append(int(attributes['value']))
- self.assertListEqual(found_values, list_val)
-
- def test_render_qs(self):
- widget = self.get_widget_instance()
- t1 = self.create_thing()
- t2 = self.create_thing()
- qs_val = Thing.objects.filter(pk__in=[t1.pk, t2.pk])
- rendered_value = widget.render('field_name', qs_val)
- inputs = parsed_inputs(rendered_value)
- found_values = []
- for field in inputs['field_name_1']:
- attributes = dict(field.attributes)
- self.assertEqual(attributes['data-selectable-type'], 'hidden-multiple')
- self.assertEqual(attributes['type'], 'hidden')
- found_values.append(int(attributes['value']))
- self.assertListEqual(found_values, [t1.pk, t2.pk])
-
- def test_update_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance()
- widget.update_query_parameters(params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertTrue('limit=10' in query)
-
- def test_initial_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance(query_params=params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_build_selectable_options(self):
- "Serialize selectable options as json in data attribute."
- options = {'autoFocus': True}
- widget = self.get_widget_instance(attrs={'data-selectable-options': options})
- attrs = parsed_widget_attributes(widget.widgets[0])
- self.assertTrue('data-selectable-options' in attrs)
- self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
+++ /dev/null
-from django.conf.urls import handler404, handler500, include, url
-
-
-handler404 = 'selectable.tests.views.test_404'
-handler500 = 'selectable.tests.views.test_500'
-
-urlpatterns = [
- url(r'^selectable-tests/', include('selectable.urls')),
-]
+++ /dev/null
-from django.http import HttpResponseNotFound, HttpResponseServerError
-
-
-def test_404(request, *args, **kwargs):
- return HttpResponseNotFound()
-
-
-def test_500(request, *args, **kwargs):
- return HttpResponseServerError()
+++ /dev/null
-from django.conf.urls import url
-
-from . import views
-
-
-urlpatterns = [
- url(r'^(?P<lookup_name>[-\w]+)/$', views.get_lookup, name="selectable-lookup"),
-]
+++ /dev/null
-from __future__ import unicode_literals
-
-from django.http import Http404
-
-from selectable.registry import registry
-
-
-def get_lookup(request, lookup_name):
-
- lookup_cls = registry.get(lookup_name)
- if lookup_cls is None:
- raise Http404('Lookup %s not found' % lookup_name)
-
- lookup = lookup_cls()
- return lookup.results(request)
-
+++ /dev/null
-[coverage:run]
-branch = true
-omit = */tests/*, example/*, .tox/*, setup.py, runtests.py
-source = .
-
-[coverage:report]
-show_missing = true
-
-
-[bdist_wheel]
-universal = 1
+++ /dev/null
-#!/usr/bin/env python
-import os
-from setuptools import setup, find_packages
-
-
-def read_file(filename):
- """Read a file into a string"""
- path = os.path.abspath(os.path.dirname(__file__))
- filepath = os.path.join(path, filename)
- try:
- return open(filepath).read()
- except IOError:
- return ''
-
-
-setup(
- name='django-selectable',
- version=__import__('selectable').__version__,
- author='Mark Lavin',
- packages=find_packages(exclude=['example']),
- include_package_data=True,
- url='https://github.com/mlavin/django-selectable',
- license='BSD',
- description=' '.join(__import__('selectable').__doc__.splitlines()).strip(),
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Intended Audience :: Developers',
- 'Framework :: Django',
- 'Framework :: Django :: 1.11',
- 'Framework :: Django :: 2.0',
- 'Framework :: Django :: 2.1',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
- ],
- long_description=read_file('README.rst'),
- test_suite="runtests.runtests",
- tests_require=['mock'],
- zip_safe=False, # because we're including media that Django needs
-)
+++ /dev/null
-[tox]
-envlist = py{27,34,35,36}-django{111},py{34,35,36}-django{20,21},py35-django_master,docs
-
-[testenv]
-basepython =
- py27: python2.7
- py34: python3.4
- py35: python3.5
- py36: python3.6
-deps =
- coverage>=4.0,<4.1
- django111: Django>=1.11,<2.0
- django20: Django>=2.0,<2.1
- django21: Django>=2.1,<2.2
- django_master: https://github.com/django/django/archive/master.tar.gz
- py27: mock
-commands = coverage run runtests.py
-
-[testenv:docs]
-basepython = python3.5
-deps =
- Sphinx
- Django
-commands =
- {envbindir}/sphinx-build -a -n -b html -d docs/_build/doctrees docs docs/_build/html
from django.contrib.auth.models import User
from django.http import Http404
-from selectable.forms.widgets import AutoCompleteSelectMultipleWidget
-
from .models import Patch, MailThread, PatchOnCommitFest, TargetVersion
-from .lookups import UserLookup
from .widgets import ThreadPickWidget
from .ajax import _archivesAPI
class PatchForm(forms.ModelForm):
+ selectize_multiple_fields = {
+ 'authors': '/lookups/user',
+ 'reviewers': '/lookups/user',
+ }
+
class Meta:
model = Patch
exclude = ('commitfests', 'mailthreads', 'modified', 'lastmail', 'subscribers', )
- widgets = {
- 'authors': AutoCompleteSelectMultipleWidget(lookup_class=UserLookup, position='top'),
- 'reviewers': AutoCompleteSelectMultipleWidget(lookup_class=UserLookup, position='top'),
- }
def __init__(self, *args, **kwargs):
super(PatchForm, self).__init__(*args, **kwargs)
self.fields['reviewers'].help_text = 'Enter part of name to see list'
self.fields['committer'].label_from_instance = lambda x: '%s %s (%s)' % (x.user.first_name, x.user.last_name, x.user.username)
+ # Selectize multiple fields -- don't pre-populate everything
+ for field, url in list(self.selectize_multiple_fields.items()):
+ # If this is a postback of a selectize field, it may contain ids that are not currently
+ # stored in the field. They must still be among the *allowed* values of course, which
+ # are handled by the existing queryset on the field.
+ if self.instance.pk:
+ # If this object isn't created yet, then it by definition has no related
+ # objects, so just bypass the collection of values since it will cause
+ # errors.
+ vals = [o.pk for o in getattr(self.instance, field).all()]
+ else:
+ vals = []
+ if 'data' in kwargs and str(field) in kwargs['data']:
+ vals.extend([x for x in kwargs['data'].getlist(field)])
+ self.fields[field].widget.attrs['data-selecturl'] = url
+ self.fields[field].queryset = self.fields[field].queryset.filter(pk__in=set(vals))
+ self.fields[field].label_from_instance = lambda u: '{} ({})'.format(u.username, u.get_full_name())
+
class NewPatchForm(forms.ModelForm):
threadmsgid = forms.CharField(max_length=200, required=True, label='Specify thread msgid', widget=ThreadPickWidget)
+from django.http import HttpResponse, Http404
+from django.db.models import Q
+from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
-from selectable.base import ModelLookup
-from selectable.registry import registry
-from selectable.decorators import login_required
+import json
-@login_required
-class UserLookup(ModelLookup):
- model = User
- search_fields = (
- 'username__icontains',
- 'first_name__icontains',
- 'last_name__icontains',
- )
- filters = {'is_active': True, }
-
- def get_item_value(self, item):
- # Display for currently selected item
- return "%s (%s)" % (item.username, item.get_full_name())
- def get_item_label(self, item):
- # Display for choice listings
- return "%s (%s)" % (item.username, item.get_full_name())
+@login_required
+def userlookup(request):
+ query = request.GET.get('query', None)
+ if not query:
+ return Http404()
+ users = User.objects.filter(
+ Q(is_active=True),
+ Q(username__icontains=query) | Q(first_name__icontains=query) | Q(last_name__icontains=query),
+ )
-registry.register(UserLookup)
+ return HttpResponse(json.dumps({
+ 'values': [{'id': u.id, 'value': '{} ({})'.format(u.username, u.get_full_name())} for u in users],
+ }), content_type='application/json')
width: 10px;
}
-div.controls ul.selectable-deck li.selectable-deck-item {
- display: block;
-}
-
-div.controls ul.selectable-deck li.selectable-deck-item a.selectable-deck-remove {
- float: none;
- margin-left: 10px;
-}
-
div.form-group div.controls input.threadpick-input {
width: 80%;
display: inline;
}
+
/*
* Attach thread dialog
*/
--- /dev/null
+/**
+ * selectize.css (v0.12.2)
+ * Copyright (c) 2013–2015 Brian Reavis & contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ * ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ *
+ */
+
+.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
+ visibility: visible !important;
+ background: #f2f2f2 !important;
+ background: rgba(0, 0, 0, 0.06) !important;
+ border: 0 none !important;
+ -webkit-box-shadow: inset 0 0 12px 4px #ffffff;
+ box-shadow: inset 0 0 12px 4px #ffffff;
+}
+.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
+ content: '!';
+ visibility: hidden;
+}
+.selectize-control.plugin-drag_drop .ui-sortable-helper {
+ -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+.selectize-dropdown-header {
+ position: relative;
+ padding: 5px 8px;
+ border-bottom: 1px solid #d0d0d0;
+ background: #f8f8f8;
+ -webkit-border-radius: 3px 3px 0 0;
+ -moz-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+}
+.selectize-dropdown-header-close {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ color: #303030;
+ opacity: 0.4;
+ margin-top: -12px;
+ line-height: 20px;
+ font-size: 20px !important;
+}
+.selectize-dropdown-header-close:hover {
+ color: #000000;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup {
+ border-right: 1px solid #f2f2f2;
+ border-top: 0 none;
+ float: left;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
+ border-right: 0 none;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
+ display: none;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
+ border-top: 0 none;
+}
+.selectize-control.plugin-remove_button [data-value] {
+ position: relative;
+ padding-right: 24px !important;
+}
+.selectize-control.plugin-remove_button [data-value] .remove {
+ z-index: 1;
+ /* fixes ie bug (see #392) */
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 17px;
+ text-align: center;
+ font-weight: bold;
+ font-size: 12px;
+ color: inherit;
+ text-decoration: none;
+ vertical-align: middle;
+ display: inline-block;
+ padding: 2px 0 0 0;
+ border-left: 1px solid #d0d0d0;
+ -webkit-border-radius: 0 2px 2px 0;
+ -moz-border-radius: 0 2px 2px 0;
+ border-radius: 0 2px 2px 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.selectize-control.plugin-remove_button [data-value] .remove:hover {
+ background: rgba(0, 0, 0, 0.05);
+}
+.selectize-control.plugin-remove_button [data-value].active .remove {
+ border-left-color: #cacaca;
+}
+.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
+ background: none;
+}
+.selectize-control.plugin-remove_button .disabled [data-value] .remove {
+ border-left-color: #ffffff;
+}
+.selectize-control.plugin-remove_button .remove-single {
+ position: absolute;
+ right: 28px;
+ top: 6px;
+ font-size: 23px;
+}
+.selectize-control {
+ position: relative;
+}
+.selectize-dropdown,
+.selectize-input,
+.selectize-input input {
+ color: #303030;
+ font-family: inherit;
+ font-size: 13px;
+ line-height: 18px;
+ -webkit-font-smoothing: inherit;
+}
+.selectize-input,
+.selectize-control.single .selectize-input.input-active {
+ background: #ffffff;
+ cursor: text;
+ display: inline-block;
+}
+.selectize-input {
+ border: 1px solid #d0d0d0;
+ padding: 8px 8px;
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+.selectize-control.multi .selectize-input.has-items {
+ padding: 6px 8px 3px;
+}
+.selectize-input.full {
+ background-color: #ffffff;
+}
+.selectize-input.disabled,
+.selectize-input.disabled * {
+ cursor: default !important;
+}
+.selectize-input.focus {
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
+}
+.selectize-input.dropdown-active {
+ -webkit-border-radius: 3px 3px 0 0;
+ -moz-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+}
+.selectize-input > * {
+ vertical-align: baseline;
+ display: -moz-inline-stack;
+ display: inline-block;
+ zoom: 1;
+ *display: inline;
+}
+.selectize-control.multi .selectize-input > div {
+ cursor: pointer;
+ margin: 0 3px 3px 0;
+ padding: 2px 6px;
+ background: #f2f2f2;
+ color: #303030;
+ border: 0 solid #d0d0d0;
+}
+.selectize-control.multi .selectize-input > div.active {
+ background: #e8e8e8;
+ color: #303030;
+ border: 0 solid #cacaca;
+}
+.selectize-control.multi .selectize-input.disabled > div,
+.selectize-control.multi .selectize-input.disabled > div.active {
+ color: #7d7d7d;
+ background: #ffffff;
+ border: 0 solid #ffffff;
+}
+.selectize-input > input {
+ display: inline-block !important;
+ padding: 0 !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ max-width: 100% !important;
+ margin: 0 2px 0 0 !important;
+ text-indent: 0 !important;
+ border: 0 none !important;
+ background: none !important;
+ line-height: inherit !important;
+ -webkit-user-select: auto !important;
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important;
+}
+.selectize-input > input::-ms-clear {
+ display: none;
+}
+.selectize-input > input:focus {
+ outline: none !important;
+}
+.selectize-input::after {
+ content: ' ';
+ display: block;
+ clear: left;
+}
+.selectize-input.dropdown-active::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ background: #f0f0f0;
+ height: 1px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+.selectize-dropdown {
+ position: absolute;
+ z-index: 10;
+ border: 1px solid #d0d0d0;
+ background: #ffffff;
+ margin: -1px 0 0 0;
+ border-top: 0 none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -webkit-border-radius: 0 0 3px 3px;
+ -moz-border-radius: 0 0 3px 3px;
+ border-radius: 0 0 3px 3px;
+}
+.selectize-dropdown [data-selectable] {
+ cursor: pointer;
+ overflow: hidden;
+}
+.selectize-dropdown [data-selectable] .highlight {
+ background: rgba(125, 168, 208, 0.2);
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+}
+.selectize-dropdown [data-selectable],
+.selectize-dropdown .optgroup-header {
+ padding: 5px 8px;
+}
+.selectize-dropdown .optgroup:first-child .optgroup-header {
+ border-top: 0 none;
+}
+.selectize-dropdown .optgroup-header {
+ color: #303030;
+ background: #ffffff;
+ cursor: default;
+}
+.selectize-dropdown .active {
+ background-color: #f5fafd;
+ color: #495c68;
+}
+.selectize-dropdown .active.create {
+ color: #495c68;
+}
+.selectize-dropdown .create {
+ color: rgba(48, 48, 48, 0.5);
+}
+.selectize-dropdown-content {
+ overflow-y: auto;
+ overflow-x: hidden;
+ max-height: 200px;
+}
+.selectize-control.single .selectize-input,
+.selectize-control.single .selectize-input input {
+ cursor: pointer;
+}
+.selectize-control.single .selectize-input.input-active,
+.selectize-control.single .selectize-input.input-active input {
+ cursor: text;
+}
+.selectize-control.single .selectize-input:after {
+ content: ' ';
+ display: block;
+ position: absolute;
+ top: 50%;
+ right: 15px;
+ margin-top: -3px;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 5px 5px 0 5px;
+ border-color: #808080 transparent transparent transparent;
+}
+.selectize-control.single .selectize-input.dropdown-active:after {
+ margin-top: -4px;
+ border-width: 0 5px 5px 5px;
+ border-color: transparent transparent #808080 transparent;
+}
+.selectize-control.rtl.single .selectize-input:after {
+ left: 15px;
+ right: auto;
+}
+.selectize-control.rtl .selectize-input > input {
+ margin: 0 4px 0 -2px !important;
+}
+.selectize-control .selectize-input.disabled {
+ opacity: 0.5;
+ background-color: #fafafa;
+}
--- /dev/null
+/**
+ * selectize.default.css (v0.12.2) - Default Theme
+ * Copyright (c) 2013–2015 Brian Reavis & contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ * ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ *
+ */
+.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
+ visibility: visible !important;
+ background: #f2f2f2 !important;
+ background: rgba(0, 0, 0, 0.06) !important;
+ border: 0 none !important;
+ -webkit-box-shadow: inset 0 0 12px 4px #ffffff;
+ box-shadow: inset 0 0 12px 4px #ffffff;
+}
+.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
+ content: '!';
+ visibility: hidden;
+}
+.selectize-control.plugin-drag_drop .ui-sortable-helper {
+ -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+.selectize-dropdown-header {
+ position: relative;
+ padding: 5px 8px;
+ border-bottom: 1px solid #d0d0d0;
+ background: #f8f8f8;
+ -webkit-border-radius: 3px 3px 0 0;
+ -moz-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+}
+.selectize-dropdown-header-close {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ color: #303030;
+ opacity: 0.4;
+ margin-top: -12px;
+ line-height: 20px;
+ font-size: 20px !important;
+}
+.selectize-dropdown-header-close:hover {
+ color: #000000;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup {
+ border-right: 1px solid #f2f2f2;
+ border-top: 0 none;
+ float: left;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
+ border-right: 0 none;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
+ display: none;
+}
+.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
+ border-top: 0 none;
+}
+.selectize-control.plugin-remove_button [data-value] {
+ position: relative;
+ padding-right: 24px !important;
+}
+.selectize-control.plugin-remove_button [data-value] .remove {
+ z-index: 1;
+ /* fixes ie bug (see #392) */
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 17px;
+ text-align: center;
+ font-weight: bold;
+ font-size: 12px;
+ color: inherit;
+ text-decoration: none;
+ vertical-align: middle;
+ display: inline-block;
+ padding: 2px 0 0 0;
+ border-left: 1px solid #0073bb;
+ -webkit-border-radius: 0 2px 2px 0;
+ -moz-border-radius: 0 2px 2px 0;
+ border-radius: 0 2px 2px 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.selectize-control.plugin-remove_button [data-value] .remove:hover {
+ background: rgba(0, 0, 0, 0.05);
+}
+.selectize-control.plugin-remove_button [data-value].active .remove {
+ border-left-color: #00578d;
+}
+.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
+ background: none;
+}
+.selectize-control.plugin-remove_button .disabled [data-value] .remove {
+ border-left-color: #aaaaaa;
+}
+.selectize-control.plugin-remove_button .remove-single {
+ position: absolute;
+ right: 28px;
+ top: 6px;
+ font-size: 23px;
+}
+.selectize-control {
+ position: relative;
+}
+.selectize-dropdown,
+.selectize-input,
+.selectize-input input {
+ color: #303030;
+ font-family: inherit;
+ font-size: 13px;
+ line-height: 18px;
+ -webkit-font-smoothing: inherit;
+}
+.selectize-input,
+.selectize-control.single .selectize-input.input-active {
+ background: #ffffff;
+ cursor: text;
+ display: inline-block;
+}
+.selectize-input {
+ border: 1px solid #d0d0d0;
+ padding: 8px 8px;
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+.selectize-control.multi .selectize-input.has-items {
+ padding: 5px 8px 2px;
+}
+.selectize-input.full {
+ background-color: #ffffff;
+}
+.selectize-input.disabled,
+.selectize-input.disabled * {
+ cursor: default !important;
+}
+.selectize-input.focus {
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
+}
+.selectize-input.dropdown-active {
+ -webkit-border-radius: 3px 3px 0 0;
+ -moz-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+}
+.selectize-input > * {
+ vertical-align: baseline;
+ display: -moz-inline-stack;
+ display: inline-block;
+ zoom: 1;
+ *display: inline;
+}
+.selectize-control.multi .selectize-input > div {
+ cursor: pointer;
+ margin: 0 3px 3px 0;
+ padding: 2px 6px;
+ background: #1da7ee;
+ color: #ffffff;
+ border: 1px solid #0073bb;
+}
+.selectize-control.multi .selectize-input > div.active {
+ background: #92c836;
+ color: #ffffff;
+ border: 1px solid #00578d;
+}
+.selectize-control.multi .selectize-input.disabled > div,
+.selectize-control.multi .selectize-input.disabled > div.active {
+ color: #ffffff;
+ background: #d2d2d2;
+ border: 1px solid #aaaaaa;
+}
+.selectize-input > input {
+ display: inline-block !important;
+ padding: 0 !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ max-width: 100% !important;
+ margin: 0 1px !important;
+ text-indent: 0 !important;
+ border: 0 none !important;
+ background: none !important;
+ line-height: inherit !important;
+ -webkit-user-select: auto !important;
+ -webkit-box-shadow: none !important;
+ box-shadow: none !important;
+}
+.selectize-input > input::-ms-clear {
+ display: none;
+}
+.selectize-input > input:focus {
+ outline: none !important;
+}
+.selectize-input::after {
+ content: ' ';
+ display: block;
+ clear: left;
+}
+.selectize-input.dropdown-active::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ background: #f0f0f0;
+ height: 1px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+.selectize-dropdown {
+ position: absolute;
+ z-index: 10;
+ border: 1px solid #d0d0d0;
+ background: #ffffff;
+ margin: -1px 0 0 0;
+ border-top: 0 none;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ -webkit-border-radius: 0 0 3px 3px;
+ -moz-border-radius: 0 0 3px 3px;
+ border-radius: 0 0 3px 3px;
+}
+.selectize-dropdown [data-selectable] {
+ cursor: pointer;
+ overflow: hidden;
+}
+.selectize-dropdown [data-selectable] .highlight {
+ background: rgba(125, 168, 208, 0.2);
+ -webkit-border-radius: 1px;
+ -moz-border-radius: 1px;
+ border-radius: 1px;
+}
+.selectize-dropdown [data-selectable],
+.selectize-dropdown .optgroup-header {
+ padding: 5px 8px;
+}
+.selectize-dropdown .optgroup:first-child .optgroup-header {
+ border-top: 0 none;
+}
+.selectize-dropdown .optgroup-header {
+ color: #303030;
+ background: #ffffff;
+ cursor: default;
+}
+.selectize-dropdown .active {
+ background-color: #f5fafd;
+ color: #495c68;
+}
+.selectize-dropdown .active.create {
+ color: #495c68;
+}
+.selectize-dropdown .create {
+ color: rgba(48, 48, 48, 0.5);
+}
+.selectize-dropdown-content {
+ overflow-y: auto;
+ overflow-x: hidden;
+ max-height: 200px;
+}
+.selectize-control.single .selectize-input,
+.selectize-control.single .selectize-input input {
+ cursor: pointer;
+}
+.selectize-control.single .selectize-input.input-active,
+.selectize-control.single .selectize-input.input-active input {
+ cursor: text;
+}
+.selectize-control.single .selectize-input:after {
+ content: ' ';
+ display: block;
+ position: absolute;
+ top: 50%;
+ right: 15px;
+ margin-top: -3px;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 5px 5px 0 5px;
+ border-color: #808080 transparent transparent transparent;
+}
+.selectize-control.single .selectize-input.dropdown-active:after {
+ margin-top: -4px;
+ border-width: 0 5px 5px 5px;
+ border-color: transparent transparent #808080 transparent;
+}
+.selectize-control.rtl.single .selectize-input:after {
+ left: 15px;
+ right: auto;
+}
+.selectize-control.rtl .selectize-input > input {
+ margin: 0 4px 0 -2px !important;
+}
+.selectize-control .selectize-input.disabled {
+ opacity: 0.5;
+ background-color: #fafafa;
+}
+.selectize-control.multi .selectize-input.has-items {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+.selectize-control.multi .selectize-input.disabled [data-value] {
+ color: #999;
+ text-shadow: none;
+ background: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+.selectize-control.multi .selectize-input.disabled [data-value],
+.selectize-control.multi .selectize-input.disabled [data-value] .remove {
+ border-color: #e6e6e6;
+}
+.selectize-control.multi .selectize-input.disabled [data-value] .remove {
+ background: none;
+}
+.selectize-control.multi .selectize-input [data-value] {
+ text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ background-color: #1b9dec;
+ background-image: -moz-linear-gradient(top, #1da7ee, #178ee9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#1da7ee), to(#178ee9));
+ background-image: -webkit-linear-gradient(top, #1da7ee, #178ee9);
+ background-image: -o-linear-gradient(top, #1da7ee, #178ee9);
+ background-image: linear-gradient(to bottom, #1da7ee, #178ee9);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1da7ee', endColorstr='#ff178ee9', GradientType=0);
+ -webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03);
+ box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03);
+}
+.selectize-control.multi .selectize-input [data-value].active {
+ background-color: #0085d4;
+ background-image: -moz-linear-gradient(top, #008fd8, #0075cf);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#008fd8), to(#0075cf));
+ background-image: -webkit-linear-gradient(top, #008fd8, #0075cf);
+ background-image: -o-linear-gradient(top, #008fd8, #0075cf);
+ background-image: linear-gradient(to bottom, #008fd8, #0075cf);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff008fd8', endColorstr='#ff0075cf', GradientType=0);
+}
+.selectize-control.single .selectize-input {
+ -webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8);
+ box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8);
+ background-color: #f9f9f9;
+ background-image: -moz-linear-gradient(top, #fefefe, #f2f2f2);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f2f2f2));
+ background-image: -webkit-linear-gradient(top, #fefefe, #f2f2f2);
+ background-image: -o-linear-gradient(top, #fefefe, #f2f2f2);
+ background-image: linear-gradient(to bottom, #fefefe, #f2f2f2);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffefefe', endColorstr='#fff2f2f2', GradientType=0);
+}
+.selectize-control.single .selectize-input,
+.selectize-dropdown.single {
+ border-color: #b8b8b8;
+}
+.selectize-dropdown .optgroup-header {
+ padding-top: 7px;
+ font-weight: bold;
+ font-size: 0.85em;
+}
+.selectize-dropdown .optgroup {
+ border-top: 1px solid #f0f0f0;
+}
+.selectize-dropdown .optgroup:first-child {
+ border-top: 0 none;
+}
--- /dev/null
+/*! selectize.js - v0.12.2 | https://github.com/selectize/selectize.js | Apache License (v2) */
+!function(a,b){"function"==typeof define&&define.amd?define("sifter",b):"object"==typeof exports?module.exports=b():a.Sifter=b()}(this,function(){var a=function(a,b){this.items=a,this.settings=b||{diacritics:!0}};a.prototype.tokenize=function(a){if(a=e(String(a||"").toLowerCase()),!a||!a.length)return[];var b,c,d,g,i=[],j=a.split(/ +/);for(b=0,c=j.length;b<c;b++){if(d=f(j[b]),this.settings.diacritics)for(g in h)h.hasOwnProperty(g)&&(d=d.replace(new RegExp(g,"g"),h[g]));i.push({string:j[b],regex:new RegExp(d,"i")})}return i},a.prototype.iterator=function(a,b){var c;c=g(a)?Array.prototype.forEach||function(a){for(var b=0,c=this.length;b<c;b++)a(this[b],b,this)}:function(a){for(var b in this)this.hasOwnProperty(b)&&a(this[b],b,this)},c.apply(a,[b])},a.prototype.getScoreFunction=function(a,b){var c,e,f,g,h;c=this,a=c.prepareSearch(a,b),f=a.tokens,e=a.options.fields,g=f.length,h=a.options.nesting;var i=function(a,b){var c,d;return a?(a=String(a||""),d=a.search(b.regex),d===-1?0:(c=b.string.length/a.length,0===d&&(c+=.5),c)):0},j=function(){var a=e.length;return a?1===a?function(a,b){return i(d(b,e[0],h),a)}:function(b,c){for(var f=0,g=0;f<a;f++)g+=i(d(c,e[f],h),b);return g/a}:function(){return 0}}();return g?1===g?function(a){return j(f[0],a)}:"and"===a.options.conjunction?function(a){for(var b,c=0,d=0;c<g;c++){if(b=j(f[c],a),b<=0)return 0;d+=b}return d/g}:function(a){for(var b=0,c=0;b<g;b++)c+=j(f[b],a);return c/g}:function(){return 0}},a.prototype.getSortFunction=function(a,c){var e,f,g,h,i,j,k,l,m,n,o;if(g=this,a=g.prepareSearch(a,c),o=!a.query&&c.sort_empty||c.sort,m=function(a,b){return"$score"===a?b.score:d(g.items[b.id],a,c.nesting)},i=[],o)for(e=0,f=o.length;e<f;e++)(a.query||"$score"!==o[e].field)&&i.push(o[e]);if(a.query){for(n=!0,e=0,f=i.length;e<f;e++)if("$score"===i[e].field){n=!1;break}n&&i.unshift({field:"$score",direction:"desc"})}else for(e=0,f=i.length;e<f;e++)if("$score"===i[e].field){i.splice(e,1);break}for(l=[],e=0,f=i.length;e<f;e++)l.push("desc"===i[e].direction?-1:1);return j=i.length,j?1===j?(h=i[0].field,k=l[0],function(a,c){return k*b(m(h,a),m(h,c))}):function(a,c){var d,e,f;for(d=0;d<j;d++)if(f=i[d].field,e=l[d]*b(m(f,a),m(f,c)))return e;return 0}:null},a.prototype.prepareSearch=function(a,b){if("object"==typeof a)return a;b=c({},b);var d=b.fields,e=b.sort,f=b.sort_empty;return d&&!g(d)&&(b.fields=[d]),e&&!g(e)&&(b.sort=[e]),f&&!g(f)&&(b.sort_empty=[f]),{options:b,query:String(a||"").toLowerCase(),tokens:this.tokenize(a),total:0,items:[]}},a.prototype.search=function(a,b){var c,d,e,f,g=this;return d=this.prepareSearch(a,b),b=d.options,a=d.query,f=b.score||g.getScoreFunction(d),a.length?g.iterator(g.items,function(a,e){c=f(a),(b.filter===!1||c>0)&&d.items.push({score:c,id:e})}):g.iterator(g.items,function(a,b){d.items.push({score:1,id:b})}),e=g.getSortFunction(d,b),e&&d.items.sort(e),d.total=d.items.length,"number"==typeof b.limit&&(d.items=d.items.slice(0,b.limit)),d};var b=function(a,b){return"number"==typeof a&&"number"==typeof b?a>b?1:a<b?-1:0:(a=i(String(a||"")),b=i(String(b||"")),a>b?1:b>a?-1:0)},c=function(a,b){var c,d,e,f;for(c=1,d=arguments.length;c<d;c++)if(f=arguments[c])for(e in f)f.hasOwnProperty(e)&&(a[e]=f[e]);return a},d=function(a,b,c){if(a&&b){if(!c)return a[b];for(var d=b.split(".");d.length&&(a=a[d.shift()]););return a}},e=function(a){return(a+"").replace(/^\s+|\s+$|/g,"")},f=function(a){return(a+"").replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")},g=Array.isArray||"undefined"!=typeof $&&$.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},h={a:"[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]",b:"[b␢βΒB฿𐌁ᛒ]",c:"[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]",d:"[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]",e:"[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]",f:"[fƑƒḞḟ]",g:"[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]",h:"[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]",i:"[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]",j:"[jȷĴĵɈɉʝɟʲ]",k:"[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]",l:"[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]",n:"[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]",o:"[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]",p:"[pṔṕṖṗⱣᵽƤƥᵱ]",q:"[qꝖꝗʠɊɋꝘꝙq̃]",r:"[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]",s:"[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]",t:"[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]",u:"[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]",v:"[vṼṽṾṿƲʋꝞꝟⱱʋ]",w:"[wẂẃẀẁŴŵẄẅẆẇẈẉ]",x:"[xẌẍẊẋχ]",y:"[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]",z:"[zŹźẐẑŽžŻżẒẓẔẕƵƶ]"},i=function(){var a,b,c,d,e="",f={};for(c in h)if(h.hasOwnProperty(c))for(d=h[c].substring(2,h[c].length-1),e+=d,a=0,b=d.length;a<b;a++)f[d.charAt(a)]=c;var g=new RegExp("["+e+"]","g");return function(a){return a.replace(g,function(a){return f[a]}).toLowerCase()}}();return a}),function(a,b){"function"==typeof define&&define.amd?define("microplugin",b):"object"==typeof exports?module.exports=b():a.MicroPlugin=b()}(this,function(){var a={};a.mixin=function(a){a.plugins={},a.prototype.initializePlugins=function(a){var c,d,e,f=this,g=[];if(f.plugins={names:[],settings:{},requested:{},loaded:{}},b.isArray(a))for(c=0,d=a.length;c<d;c++)"string"==typeof a[c]?g.push(a[c]):(f.plugins.settings[a[c].name]=a[c].options,g.push(a[c].name));else if(a)for(e in a)a.hasOwnProperty(e)&&(f.plugins.settings[e]=a[e],g.push(e));for(;g.length;)f.require(g.shift())},a.prototype.loadPlugin=function(b){var c=this,d=c.plugins,e=a.plugins[b];if(!a.plugins.hasOwnProperty(b))throw new Error('Unable to find "'+b+'" plugin');d.requested[b]=!0,d.loaded[b]=e.fn.apply(c,[c.plugins.settings[b]||{}]),d.names.push(b)},a.prototype.require=function(a){var b=this,c=b.plugins;if(!b.plugins.loaded.hasOwnProperty(a)){if(c.requested[a])throw new Error('Plugin has circular dependency ("'+a+'")');b.loadPlugin(a)}return c.loaded[a]},a.define=function(b,c){a.plugins[b]={name:b,fn:c}}};var b={isArray:Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)}};return a}),function(a,b){"function"==typeof define&&define.amd?define("selectize",["jquery","sifter","microplugin"],b):"object"==typeof exports?module.exports=b(require("jquery"),require("sifter"),require("microplugin")):a.Selectize=b(a.jQuery,a.Sifter,a.MicroPlugin)}(this,function(a,b,c){"use strict";var d=function(a,b){if("string"!=typeof b||b.length){var c="string"==typeof b?new RegExp(b,"i"):b,d=function(a){var b=0;if(3===a.nodeType){var e=a.data.search(c);if(e>=0&&a.data.length>0){var f=a.data.match(c),g=document.createElement("span");g.className="highlight";var h=a.splitText(e),i=(h.splitText(f[0].length),h.cloneNode(!0));g.appendChild(i),h.parentNode.replaceChild(g,h),b=1}}else if(1===a.nodeType&&a.childNodes&&!/(script|style)/i.test(a.tagName))for(var j=0;j<a.childNodes.length;++j)j+=d(a.childNodes[j]);return b};return a.each(function(){d(this)})}},e=function(){};e.prototype={on:function(a,b){this._events=this._events||{},this._events[a]=this._events[a]||[],this._events[a].push(b)},off:function(a,b){var c=arguments.length;return 0===c?delete this._events:1===c?delete this._events[a]:(this._events=this._events||{},void(a in this._events!=!1&&this._events[a].splice(this._events[a].indexOf(b),1)))},trigger:function(a){if(this._events=this._events||{},a in this._events!=!1)for(var b=0;b<this._events[a].length;b++)this._events[a][b].apply(this,Array.prototype.slice.call(arguments,1))}},e.mixin=function(a){for(var b=["on","off","trigger"],c=0;c<b.length;c++)a.prototype[b[c]]=e.prototype[b[c]]};var f=/Mac/.test(navigator.userAgent),g=65,h=13,i=27,j=37,k=38,l=80,m=39,n=40,o=78,p=8,q=46,r=16,s=f?91:17,t=f?18:17,u=9,v=1,w=2,x=!/android/i.test(window.navigator.userAgent)&&!!document.createElement("form").validity,y=function(a){return"undefined"!=typeof a},z=function(a){return"undefined"==typeof a||null===a?null:"boolean"==typeof a?a?"1":"0":a+""},A=function(a){return(a+"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},B={};B.before=function(a,b,c){var d=a[b];a[b]=function(){return c.apply(a,arguments),d.apply(a,arguments)}},B.after=function(a,b,c){var d=a[b];a[b]=function(){var b=d.apply(a,arguments);return c.apply(a,arguments),b}};var C=function(a){var b=!1;return function(){b||(b=!0,a.apply(this,arguments))}},D=function(a,b){var c;return function(){var d=this,e=arguments;window.clearTimeout(c),c=window.setTimeout(function(){a.apply(d,e)},b)}},E=function(a,b,c){var d,e=a.trigger,f={};a.trigger=function(){var c=arguments[0];return b.indexOf(c)===-1?e.apply(a,arguments):void(f[c]=arguments)},c.apply(a,[]),a.trigger=e;for(d in f)f.hasOwnProperty(d)&&e.apply(a,f[d])},F=function(a,b,c,d){a.on(b,c,function(b){for(var c=b.target;c&&c.parentNode!==a[0];)c=c.parentNode;return b.currentTarget=c,d.apply(this,[b])})},G=function(a){var b={};if("selectionStart"in a)b.start=a.selectionStart,b.length=a.selectionEnd-b.start;else if(document.selection){a.focus();var c=document.selection.createRange(),d=document.selection.createRange().text.length;c.moveStart("character",-a.value.length),b.start=c.text.length-d,b.length=d}return b},H=function(a,b,c){var d,e,f={};if(c)for(d=0,e=c.length;d<e;d++)f[c[d]]=a.css(c[d]);else f=a.css();b.css(f)},I=function(b,c){if(!b)return 0;var d=a("<test>").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"pre"}).text(b).appendTo("body");H(c,d,["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"]);var e=d.width();return d.remove(),e},J=function(a){var b=null,c=function(c,d){var e,f,g,h,i,j,k,l;c=c||window.event||{},d=d||{},c.metaKey||c.altKey||(d.force||a.data("grow")!==!1)&&(e=a.val(),c.type&&"keydown"===c.type.toLowerCase()&&(f=c.keyCode,g=f>=97&&f<=122||f>=65&&f<=90||f>=48&&f<=57||32===f,f===q||f===p?(l=G(a[0]),l.length?e=e.substring(0,l.start)+e.substring(l.start+l.length):f===p&&l.start?e=e.substring(0,l.start-1)+e.substring(l.start+1):f===q&&"undefined"!=typeof l.start&&(e=e.substring(0,l.start)+e.substring(l.start+1))):g&&(j=c.shiftKey,k=String.fromCharCode(c.keyCode),k=j?k.toUpperCase():k.toLowerCase(),e+=k)),h=a.attr("placeholder"),!e&&h&&(e=h),i=I(e,a)+4,i!==b&&(b=i,a.width(i),a.triggerHandler("resize")))};a.on("keydown keyup update blur",c),c()},K=function(a){var b=document.createElement("div");return b.appendChild(a.cloneNode(!0)),b.innerHTML},L=function(c,d){var e,f,g,h,i=this;h=c[0],h.selectize=i;var j=window.getComputedStyle&&window.getComputedStyle(h,null);if(g=j?j.getPropertyValue("direction"):h.currentStyle&&h.currentStyle.direction,g=g||c.parents("[dir]:first").attr("dir")||"",a.extend(i,{order:0,settings:d,$input:c,tabIndex:c.attr("tabindex")||"",tagType:"select"===h.tagName.toLowerCase()?v:w,rtl:/rtl/i.test(g),eventNS:".selectize"+ ++L.count,highlightedValue:null,isOpen:!1,isDisabled:!1,isRequired:c.is("[required]"),isInvalid:!1,isLocked:!1,isFocused:!1,isInputHidden:!1,isSetup:!1,isShiftDown:!1,isCmdDown:!1,isCtrlDown:!1,ignoreFocus:!1,ignoreBlur:!1,ignoreHover:!1,hasOptions:!1,currentResults:null,lastValue:"",caretPos:0,loading:0,loadedSearches:{},$activeOption:null,$activeItems:[],optgroups:{},options:{},userOptions:{},items:[],renderCache:{},onSearchChange:null===d.loadThrottle?i.onSearchChange:D(i.onSearchChange,d.loadThrottle)}),i.sifter=new b(this.options,{diacritics:d.diacritics}),i.settings.options){for(e=0,f=i.settings.options.length;e<f;e++)i.registerOption(i.settings.options[e]);delete i.settings.options}if(i.settings.optgroups){for(e=0,f=i.settings.optgroups.length;e<f;e++)i.registerOptionGroup(i.settings.optgroups[e]);delete i.settings.optgroups}i.settings.mode=i.settings.mode||(1===i.settings.maxItems?"single":"multi"),"boolean"!=typeof i.settings.hideSelected&&(i.settings.hideSelected="multi"===i.settings.mode),i.initializePlugins(i.settings.plugins),i.setupCallbacks(),i.setupTemplates(),i.setup()};return e.mixin(L),c.mixin(L),a.extend(L.prototype,{setup:function(){var b,c,d,e,g,h,i,j,k,l=this,m=l.settings,n=l.eventNS,o=a(window),p=a(document),q=l.$input;if(i=l.settings.mode,j=q.attr("class")||"",b=a("<div>").addClass(m.wrapperClass).addClass(j).addClass(i),c=a("<div>").addClass(m.inputClass).addClass("items").appendTo(b),d=a('<input type="text" autocomplete="off" />').appendTo(c).attr("tabindex",q.is(":disabled")?"-1":l.tabIndex),h=a(m.dropdownParent||b),e=a("<div>").addClass(m.dropdownClass).addClass(i).hide().appendTo(h),g=a("<div>").addClass(m.dropdownContentClass).appendTo(e),l.settings.copyClassesToDropdown&&e.addClass(j),b.css({width:q[0].style.width}),l.plugins.names.length&&(k="plugin-"+l.plugins.names.join(" plugin-"),b.addClass(k),e.addClass(k)),(null===m.maxItems||m.maxItems>1)&&l.tagType===v&&q.attr("multiple","multiple"),l.settings.placeholder&&d.attr("placeholder",m.placeholder),!l.settings.splitOn&&l.settings.delimiter){var u=l.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");l.settings.splitOn=new RegExp("\\s*"+u+"+\\s*")}q.attr("autocorrect")&&d.attr("autocorrect",q.attr("autocorrect")),q.attr("autocapitalize")&&d.attr("autocapitalize",q.attr("autocapitalize")),l.$wrapper=b,l.$control=c,l.$control_input=d,l.$dropdown=e,l.$dropdown_content=g,e.on("mouseenter","[data-selectable]",function(){return l.onOptionHover.apply(l,arguments)}),e.on("mousedown click","[data-selectable]",function(){return l.onOptionSelect.apply(l,arguments)}),F(c,"mousedown","*:not(input)",function(){return l.onItemSelect.apply(l,arguments)}),J(d),c.on({mousedown:function(){return l.onMouseDown.apply(l,arguments)},click:function(){return l.onClick.apply(l,arguments)}}),d.on({mousedown:function(a){a.stopPropagation()},keydown:function(){return l.onKeyDown.apply(l,arguments)},keyup:function(){return l.onKeyUp.apply(l,arguments)},keypress:function(){return l.onKeyPress.apply(l,arguments)},resize:function(){l.positionDropdown.apply(l,[])},blur:function(){return l.onBlur.apply(l,arguments)},focus:function(){return l.ignoreBlur=!1,l.onFocus.apply(l,arguments)},paste:function(){return l.onPaste.apply(l,arguments)}}),p.on("keydown"+n,function(a){l.isCmdDown=a[f?"metaKey":"ctrlKey"],l.isCtrlDown=a[f?"altKey":"ctrlKey"],l.isShiftDown=a.shiftKey}),p.on("keyup"+n,function(a){a.keyCode===t&&(l.isCtrlDown=!1),a.keyCode===r&&(l.isShiftDown=!1),a.keyCode===s&&(l.isCmdDown=!1)}),p.on("mousedown"+n,function(a){if(l.isFocused){if(a.target===l.$dropdown[0]||a.target.parentNode===l.$dropdown[0])return!1;l.$control.has(a.target).length||a.target===l.$control[0]||l.blur(a.target)}}),o.on(["scroll"+n,"resize"+n].join(" "),function(){l.isOpen&&l.positionDropdown.apply(l,arguments)}),o.on("mousemove"+n,function(){l.ignoreHover=!1}),this.revertSettings={$children:q.children().detach(),tabindex:q.attr("tabindex")},q.attr("tabindex",-1).hide().after(l.$wrapper),a.isArray(m.items)&&(l.setValue(m.items),delete m.items),x&&q.on("invalid"+n,function(a){a.preventDefault(),l.isInvalid=!0,l.refreshState()}),l.updateOriginalInput(),l.refreshItems(),l.refreshState(),l.updatePlaceholder(),l.isSetup=!0,q.is(":disabled")&&l.disable(),l.on("change",this.onChange),q.data("selectize",l),q.addClass("selectized"),l.trigger("initialize"),m.preload===!0&&l.onSearchChange("")},setupTemplates:function(){var b=this,c=b.settings.labelField,d=b.settings.optgroupLabelField,e={optgroup:function(a){return'<div class="optgroup">'+a.html+"</div>"},optgroup_header:function(a,b){return'<div class="optgroup-header">'+b(a[d])+"</div>"},option:function(a,b){return'<div class="option">'+b(a[c])+"</div>"},item:function(a,b){return'<div class="item">'+b(a[c])+"</div>"},option_create:function(a,b){return'<div class="create">Add <strong>'+b(a.input)+"</strong>…</div>"}};b.settings.render=a.extend({},e,b.settings.render)},setupCallbacks:function(){var a,b,c={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(a in c)c.hasOwnProperty(a)&&(b=this.settings[c[a]],b&&this.on(a,b))},onClick:function(a){var b=this;b.isFocused||(b.focus(),a.preventDefault())},onMouseDown:function(b){var c=this,d=b.isDefaultPrevented();a(b.target);if(c.isFocused){if(b.target!==c.$control_input[0])return"single"===c.settings.mode?c.isOpen?c.close():c.open():d||c.setActiveItem(null),!1}else d||window.setTimeout(function(){c.focus()},0)},onChange:function(){this.$input.trigger("change")},onPaste:function(b){var c=this;c.isFull()||c.isInputHidden||c.isLocked?b.preventDefault():c.settings.splitOn&&setTimeout(function(){for(var b=a.trim(c.$control_input.val()||"").split(c.settings.splitOn),d=0,e=b.length;d<e;d++)c.createItem(b[d])},0)},onKeyPress:function(a){if(this.isLocked)return a&&a.preventDefault();var b=String.fromCharCode(a.keyCode||a.which);return this.settings.create&&"multi"===this.settings.mode&&b===this.settings.delimiter?(this.createItem(),a.preventDefault(),!1):void 0},onKeyDown:function(a){var b=(a.target===this.$control_input[0],this);if(b.isLocked)return void(a.keyCode!==u&&a.preventDefault());switch(a.keyCode){case g:if(b.isCmdDown)return void b.selectAll();break;case i:return void(b.isOpen&&(a.preventDefault(),a.stopPropagation(),b.close()));case o:if(!a.ctrlKey||a.altKey)break;case n:if(!b.isOpen&&b.hasOptions)b.open();else if(b.$activeOption){b.ignoreHover=!0;var c=b.getAdjacentOption(b.$activeOption,1);c.length&&b.setActiveOption(c,!0,!0)}return void a.preventDefault();case l:if(!a.ctrlKey||a.altKey)break;case k:if(b.$activeOption){b.ignoreHover=!0;var d=b.getAdjacentOption(b.$activeOption,-1);d.length&&b.setActiveOption(d,!0,!0)}return void a.preventDefault();case h:return void(b.isOpen&&b.$activeOption&&(b.onOptionSelect({currentTarget:b.$activeOption}),a.preventDefault()));case j:return void b.advanceSelection(-1,a);case m:return void b.advanceSelection(1,a);case u:return b.settings.selectOnTab&&b.isOpen&&b.$activeOption&&(b.onOptionSelect({currentTarget:b.$activeOption}),b.isFull()||a.preventDefault()),void(b.settings.create&&b.createItem()&&a.preventDefault());case p:case q:return void b.deleteSelection(a)}return!b.isFull()&&!b.isInputHidden||(f?a.metaKey:a.ctrlKey)?void 0:void a.preventDefault()},onKeyUp:function(a){var b=this;if(b.isLocked)return a&&a.preventDefault();var c=b.$control_input.val()||"";b.lastValue!==c&&(b.lastValue=c,b.onSearchChange(c),b.refreshOptions(),b.trigger("type",c))},onSearchChange:function(a){var b=this,c=b.settings.load;c&&(b.loadedSearches.hasOwnProperty(a)||(b.loadedSearches[a]=!0,b.load(function(d){c.apply(b,[a,d])})))},onFocus:function(a){var b=this,c=b.isFocused;return b.isDisabled?(b.blur(),a&&a.preventDefault(),!1):void(b.ignoreFocus||(b.isFocused=!0,"focus"===b.settings.preload&&b.onSearchChange(""),c||b.trigger("focus"),b.$activeItems.length||(b.showInput(),b.setActiveItem(null),b.refreshOptions(!!b.settings.openOnFocus)),b.refreshState()))},onBlur:function(a,b){var c=this;if(c.isFocused&&(c.isFocused=!1,!c.ignoreFocus)){if(!c.ignoreBlur&&document.activeElement===c.$dropdown_content[0])return c.ignoreBlur=!0,void c.onFocus(a);var d=function(){c.close(),c.setTextboxValue(""),c.setActiveItem(null),c.setActiveOption(null),c.setCaret(c.items.length),c.refreshState(),b&&b.focus(),c.ignoreFocus=!1,c.trigger("blur")};c.ignoreFocus=!0,c.settings.create&&c.settings.createOnBlur?c.createItem(null,!1,d):d()}},onOptionHover:function(a){this.ignoreHover||this.setActiveOption(a.currentTarget,!1)},onOptionSelect:function(b){var c,d,e=this;b.preventDefault&&(b.preventDefault(),b.stopPropagation()),d=a(b.currentTarget),d.hasClass("create")?e.createItem(null,function(){e.settings.closeAfterSelect&&e.close()}):(c=d.attr("data-value"),"undefined"!=typeof c&&(e.lastQuery=null,e.setTextboxValue(""),e.addItem(c),e.settings.closeAfterSelect?e.close():!e.settings.hideSelected&&b.type&&/mouse/.test(b.type)&&e.setActiveOption(e.getOption(c))))},onItemSelect:function(a){var b=this;b.isLocked||"multi"===b.settings.mode&&(a.preventDefault(),b.setActiveItem(a.currentTarget,a))},load:function(a){var b=this,c=b.$wrapper.addClass(b.settings.loadingClass);b.loading++,a.apply(b,[function(a){b.loading=Math.max(b.loading-1,0),a&&a.length&&(b.addOption(a),b.refreshOptions(b.isFocused&&!b.isInputHidden)),b.loading||c.removeClass(b.settings.loadingClass),b.trigger("load",a)}])},setTextboxValue:function(a){var b=this.$control_input,c=b.val()!==a;c&&(b.val(a).triggerHandler("update"),this.lastValue=a)},getValue:function(){return this.tagType===v&&this.$input.attr("multiple")?this.items:this.items.join(this.settings.delimiter)},setValue:function(a,b){var c=b?[]:["change"];E(this,c,function(){this.clear(b),this.addItems(a,b)})},setActiveItem:function(b,c){var d,e,f,g,h,i,j,k,l=this;if("single"!==l.settings.mode){if(b=a(b),!b.length)return a(l.$activeItems).removeClass("active"),l.$activeItems=[],void(l.isFocused&&l.showInput());if(d=c&&c.type.toLowerCase(),"mousedown"===d&&l.isShiftDown&&l.$activeItems.length){for(k=l.$control.children(".active:last"),g=Array.prototype.indexOf.apply(l.$control[0].childNodes,[k[0]]),h=Array.prototype.indexOf.apply(l.$control[0].childNodes,[b[0]]),g>h&&(j=g,g=h,h=j),e=g;e<=h;e++)i=l.$control[0].childNodes[e],l.$activeItems.indexOf(i)===-1&&(a(i).addClass("active"),l.$activeItems.push(i));c.preventDefault()}else"mousedown"===d&&l.isCtrlDown||"keydown"===d&&this.isShiftDown?b.hasClass("active")?(f=l.$activeItems.indexOf(b[0]),l.$activeItems.splice(f,1),b.removeClass("active")):l.$activeItems.push(b.addClass("active")[0]):(a(l.$activeItems).removeClass("active"),l.$activeItems=[b.addClass("active")[0]]);l.hideInput(),this.isFocused||l.focus()}},setActiveOption:function(b,c,d){var e,f,g,h,i,j=this;j.$activeOption&&j.$activeOption.removeClass("active"),j.$activeOption=null,b=a(b),b.length&&(j.$activeOption=b.addClass("active"),!c&&y(c)||(e=j.$dropdown_content.height(),f=j.$activeOption.outerHeight(!0),c=j.$dropdown_content.scrollTop()||0,g=j.$activeOption.offset().top-j.$dropdown_content.offset().top+c,h=g,i=g-e+f,g+f>e+c?j.$dropdown_content.stop().animate({scrollTop:i},d?j.settings.scrollDuration:0):g<c&&j.$dropdown_content.stop().animate({scrollTop:h},d?j.settings.scrollDuration:0)))},selectAll:function(){var a=this;"single"!==a.settings.mode&&(a.$activeItems=Array.prototype.slice.apply(a.$control.children(":not(input)").addClass("active")),a.$activeItems.length&&(a.hideInput(),a.close()),a.focus())},hideInput:function(){var a=this;a.setTextboxValue(""),a.$control_input.css({opacity:0,position:"absolute",left:a.rtl?1e4:-1e4}),a.isInputHidden=!0},showInput:function(){this.$control_input.css({opacity:1,position:"relative",left:0}),this.isInputHidden=!1},focus:function(){var a=this;a.isDisabled||(a.ignoreFocus=!0,a.$control_input[0].focus(),window.setTimeout(function(){a.ignoreFocus=!1,a.onFocus()},0))},blur:function(a){this.$control_input[0].blur(),this.onBlur(null,a)},getScoreFunction:function(a){return this.sifter.getScoreFunction(a,this.getSearchOptions())},getSearchOptions:function(){var a=this.settings,b=a.sortField;return"string"==typeof b&&(b=[{field:b}]),{fields:a.searchField,conjunction:a.searchConjunction,sort:b}},search:function(b){var c,d,e,f=this,g=f.settings,h=this.getSearchOptions();if(g.score&&(e=f.settings.score.apply(this,[b]),"function"!=typeof e))throw new Error('Selectize "score" setting must be a function that returns a function');if(b!==f.lastQuery?(f.lastQuery=b,d=f.sifter.search(b,a.extend(h,{score:e})),f.currentResults=d):d=a.extend(!0,{},f.currentResults),g.hideSelected)for(c=d.items.length-1;c>=0;c--)f.items.indexOf(z(d.items[c].id))!==-1&&d.items.splice(c,1);return d},refreshOptions:function(b){var c,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s;"undefined"==typeof b&&(b=!0);var t=this,u=a.trim(t.$control_input.val()),v=t.search(u),w=t.$dropdown_content,x=t.$activeOption&&z(t.$activeOption.attr("data-value"));for(g=v.items.length,"number"==typeof t.settings.maxOptions&&(g=Math.min(g,t.settings.maxOptions)),h={},i=[],c=0;c<g;c++)for(j=t.options[v.items[c].id],k=t.render("option",j),l=j[t.settings.optgroupField]||"",m=a.isArray(l)?l:[l],e=0,f=m&&m.length;e<f;e++)l=m[e],t.optgroups.hasOwnProperty(l)||(l=""),h.hasOwnProperty(l)||(h[l]=document.createDocumentFragment(),i.push(l)),h[l].appendChild(k);for(this.settings.lockOptgroupOrder&&i.sort(function(a,b){var c=t.optgroups[a].$order||0,d=t.optgroups[b].$order||0;return c-d}),n=document.createDocumentFragment(),c=0,g=i.length;c<g;c++)l=i[c],t.optgroups.hasOwnProperty(l)&&h[l].childNodes.length?(o=document.createDocumentFragment(),o.appendChild(t.render("optgroup_header",t.optgroups[l])),o.appendChild(h[l]),n.appendChild(t.render("optgroup",a.extend({},t.optgroups[l],{html:K(o),dom:o})))):n.appendChild(h[l]);if(w.html(n),t.settings.highlight&&v.query.length&&v.tokens.length)for(c=0,g=v.tokens.length;c<g;c++)d(w,v.tokens[c].regex);if(!t.settings.hideSelected)for(c=0,g=t.items.length;c<g;c++)t.getOption(t.items[c]).addClass("selected");p=t.canCreate(u),p&&(w.prepend(t.render("option_create",{input:u})),s=a(w[0].childNodes[0])),t.hasOptions=v.items.length>0||p,t.hasOptions?(v.items.length>0?(r=x&&t.getOption(x),r&&r.length?q=r:"single"===t.settings.mode&&t.items.length&&(q=t.getOption(t.items[0])),q&&q.length||(q=s&&!t.settings.addPrecedence?t.getAdjacentOption(s,1):w.find("[data-selectable]:first"))):q=s,t.setActiveOption(q),b&&!t.isOpen&&t.open()):(t.setActiveOption(null),b&&t.isOpen&&t.close())},addOption:function(b){var c,d,e,f=this;if(a.isArray(b))for(c=0,d=b.length;c<d;c++)f.addOption(b[c]);else(e=f.registerOption(b))&&(f.userOptions[e]=!0,f.lastQuery=null,f.trigger("option_add",e,b))},registerOption:function(a){var b=z(a[this.settings.valueField]);return"undefined"!=typeof b&&null!==b&&!this.options.hasOwnProperty(b)&&(a.$order=a.$order||++this.order,this.options[b]=a,b)},registerOptionGroup:function(a){var b=z(a[this.settings.optgroupValueField]);return!!b&&(a.$order=a.$order||++this.order,this.optgroups[b]=a,b)},addOptionGroup:function(a,b){b[this.settings.optgroupValueField]=a,(a=this.registerOptionGroup(b))&&this.trigger("optgroup_add",a,b)},removeOptionGroup:function(a){this.optgroups.hasOwnProperty(a)&&(delete this.optgroups[a],this.renderCache={},this.trigger("optgroup_remove",a))},clearOptionGroups:function(){this.optgroups={},this.renderCache={},this.trigger("optgroup_clear")},updateOption:function(b,c){var d,e,f,g,h,i,j,k=this;if(b=z(b),f=z(c[k.settings.valueField]),null!==b&&k.options.hasOwnProperty(b)){if("string"!=typeof f)throw new Error("Value must be set in option data");j=k.options[b].$order,f!==b&&(delete k.options[b],g=k.items.indexOf(b),g!==-1&&k.items.splice(g,1,f)),c.$order=c.$order||j,k.options[f]=c,h=k.renderCache.item,i=k.renderCache.option,h&&(delete h[b],delete h[f]),i&&(delete i[b],delete i[f]),k.items.indexOf(f)!==-1&&(d=k.getItem(b),e=a(k.render("item",c)),d.hasClass("active")&&e.addClass("active"),d.replaceWith(e)),k.lastQuery=null,k.isOpen&&k.refreshOptions(!1)}},removeOption:function(a,b){var c=this;a=z(a);var d=c.renderCache.item,e=c.renderCache.option;d&&delete d[a],e&&delete e[a],delete c.userOptions[a],delete c.options[a],c.lastQuery=null,c.trigger("option_remove",a),c.removeItem(a,b)},clearOptions:function(){var a=this;a.loadedSearches={},a.userOptions={},a.renderCache={},a.options=a.sifter.items={},a.lastQuery=null,a.trigger("option_clear"),a.clear()},getOption:function(a){return this.getElementWithValue(a,this.$dropdown_content.find("[data-selectable]"))},getAdjacentOption:function(b,c){var d=this.$dropdown.find("[data-selectable]"),e=d.index(b)+c;return e>=0&&e<d.length?d.eq(e):a()},getElementWithValue:function(b,c){if(b=z(b),"undefined"!=typeof b&&null!==b)for(var d=0,e=c.length;d<e;d++)if(c[d].getAttribute("data-value")===b)return a(c[d]);return a()},getItem:function(a){return this.getElementWithValue(a,this.$control.children())},addItems:function(b,c){for(var d=a.isArray(b)?b:[b],e=0,f=d.length;e<f;e++)this.isPending=e<f-1,this.addItem(d[e],c)},addItem:function(b,c){var d=c?[]:["change"];E(this,d,function(){var d,e,f,g,h,i=this,j=i.settings.mode;return b=z(b),i.items.indexOf(b)!==-1?void("single"===j&&i.close()):void(i.options.hasOwnProperty(b)&&("single"===j&&i.clear(c),"multi"===j&&i.isFull()||(d=a(i.render("item",i.options[b])),h=i.isFull(),i.items.splice(i.caretPos,0,b),i.insertAtCaret(d),(!i.isPending||!h&&i.isFull())&&i.refreshState(),i.isSetup&&(f=i.$dropdown_content.find("[data-selectable]"),i.isPending||(e=i.getOption(b),g=i.getAdjacentOption(e,1).attr("data-value"),i.refreshOptions(i.isFocused&&"single"!==j),g&&i.setActiveOption(i.getOption(g))),!f.length||i.isFull()?i.close():i.positionDropdown(),i.updatePlaceholder(),i.trigger("item_add",b,d),i.updateOriginalInput({silent:c})))))})},removeItem:function(b,c){var d,e,f,g=this;d=b instanceof a?b:g.getItem(b),b=z(d.attr("data-value")),e=g.items.indexOf(b),e!==-1&&(d.remove(),d.hasClass("active")&&(f=g.$activeItems.indexOf(d[0]),g.$activeItems.splice(f,1)),g.items.splice(e,1),g.lastQuery=null,!g.settings.persist&&g.userOptions.hasOwnProperty(b)&&g.removeOption(b,c),e<g.caretPos&&g.setCaret(g.caretPos-1),g.refreshState(),g.updatePlaceholder(),g.updateOriginalInput({silent:c}),g.positionDropdown(),g.trigger("item_remove",b,d))},createItem:function(b,c){var d=this,e=d.caretPos;b=b||a.trim(d.$control_input.val()||"");var f=arguments[arguments.length-1];if("function"!=typeof f&&(f=function(){}),"boolean"!=typeof c&&(c=!0),!d.canCreate(b))return f(),!1;d.lock();var g="function"==typeof d.settings.create?this.settings.create:function(a){var b={};return b[d.settings.labelField]=a,b[d.settings.valueField]=a,b},h=C(function(a){if(d.unlock(),!a||"object"!=typeof a)return f();var b=z(a[d.settings.valueField]);return"string"!=typeof b?f():(d.setTextboxValue(""),d.addOption(a),d.setCaret(e),d.addItem(b),d.refreshOptions(c&&"single"!==d.settings.mode),void f(a))}),i=g.apply(this,[b,h]);return"undefined"!=typeof i&&h(i),!0},refreshItems:function(){this.lastQuery=null,this.isSetup&&this.addItem(this.items),this.refreshState(),this.updateOriginalInput()},refreshState:function(){var a,b=this;b.isRequired&&(b.items.length&&(b.isInvalid=!1),b.$control_input.prop("required",a)),b.refreshClasses()},refreshClasses:function(){var b=this,c=b.isFull(),d=b.isLocked;b.$wrapper.toggleClass("rtl",b.rtl),b.$control.toggleClass("focus",b.isFocused).toggleClass("disabled",b.isDisabled).toggleClass("required",b.isRequired).toggleClass("invalid",b.isInvalid).toggleClass("locked",d).toggleClass("full",c).toggleClass("not-full",!c).toggleClass("input-active",b.isFocused&&!b.isInputHidden).toggleClass("dropdown-active",b.isOpen).toggleClass("has-options",!a.isEmptyObject(b.options)).toggleClass("has-items",b.items.length>0),b.$control_input.data("grow",!c&&!d)},isFull:function(){return null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems},updateOriginalInput:function(a){var b,c,d,e,f=this;if(a=a||{},f.tagType===v){for(d=[],b=0,c=f.items.length;b<c;b++)e=f.options[f.items[b]][f.settings.labelField]||"",d.push('<option value="'+A(f.items[b])+'" selected="selected">'+A(e)+"</option>");d.length||this.$input.attr("multiple")||d.push('<option value="" selected="selected"></option>'),f.$input.html(d.join(""))}else f.$input.val(f.getValue()),f.$input.attr("value",f.$input.val());f.isSetup&&(a.silent||f.trigger("change",f.$input.val()))},updatePlaceholder:function(){if(this.settings.placeholder){var a=this.$control_input;this.items.length?a.removeAttr("placeholder"):a.attr("placeholder",this.settings.placeholder),a.triggerHandler("update",{force:!0})}},open:function(){var a=this;a.isLocked||a.isOpen||"multi"===a.settings.mode&&a.isFull()||(a.focus(),a.isOpen=!0,a.refreshState(),a.$dropdown.css({visibility:"hidden",display:"block"}),a.positionDropdown(),a.$dropdown.css({visibility:"visible"}),a.trigger("dropdown_open",a.$dropdown))},close:function(){var a=this,b=a.isOpen;"single"===a.settings.mode&&a.items.length&&a.hideInput(),a.isOpen=!1,a.$dropdown.hide(),a.setActiveOption(null),a.refreshState(),b&&a.trigger("dropdown_close",a.$dropdown)},positionDropdown:function(){
+var a=this.$control,b="body"===this.settings.dropdownParent?a.offset():a.position();b.top+=a.outerHeight(!0),this.$dropdown.css({width:a.outerWidth(),top:b.top,left:b.left})},clear:function(a){var b=this;b.items.length&&(b.$control.children(":not(input)").remove(),b.items=[],b.lastQuery=null,b.setCaret(0),b.setActiveItem(null),b.updatePlaceholder(),b.updateOriginalInput({silent:a}),b.refreshState(),b.showInput(),b.trigger("clear"))},insertAtCaret:function(b){var c=Math.min(this.caretPos,this.items.length);0===c?this.$control.prepend(b):a(this.$control[0].childNodes[c]).before(b),this.setCaret(c+1)},deleteSelection:function(b){var c,d,e,f,g,h,i,j,k,l=this;if(e=b&&b.keyCode===p?-1:1,f=G(l.$control_input[0]),l.$activeOption&&!l.settings.hideSelected&&(i=l.getAdjacentOption(l.$activeOption,-1).attr("data-value")),g=[],l.$activeItems.length){for(k=l.$control.children(".active:"+(e>0?"last":"first")),h=l.$control.children(":not(input)").index(k),e>0&&h++,c=0,d=l.$activeItems.length;c<d;c++)g.push(a(l.$activeItems[c]).attr("data-value"));b&&(b.preventDefault(),b.stopPropagation())}else(l.isFocused||"single"===l.settings.mode)&&l.items.length&&(e<0&&0===f.start&&0===f.length?g.push(l.items[l.caretPos-1]):e>0&&f.start===l.$control_input.val().length&&g.push(l.items[l.caretPos]));if(!g.length||"function"==typeof l.settings.onDelete&&l.settings.onDelete.apply(l,[g])===!1)return!1;for("undefined"!=typeof h&&l.setCaret(h);g.length;)l.removeItem(g.pop());return l.showInput(),l.positionDropdown(),l.refreshOptions(!0),i&&(j=l.getOption(i),j.length&&l.setActiveOption(j)),!0},advanceSelection:function(a,b){var c,d,e,f,g,h,i=this;0!==a&&(i.rtl&&(a*=-1),c=a>0?"last":"first",d=G(i.$control_input[0]),i.isFocused&&!i.isInputHidden?(f=i.$control_input.val().length,g=a<0?0===d.start&&0===d.length:d.start===f,g&&!f&&i.advanceCaret(a,b)):(h=i.$control.children(".active:"+c),h.length&&(e=i.$control.children(":not(input)").index(h),i.setActiveItem(null),i.setCaret(a>0?e+1:e))))},advanceCaret:function(a,b){var c,d,e=this;0!==a&&(c=a>0?"next":"prev",e.isShiftDown?(d=e.$control_input[c](),d.length&&(e.hideInput(),e.setActiveItem(d),b&&b.preventDefault())):e.setCaret(e.caretPos+a))},setCaret:function(b){var c=this;if(b="single"===c.settings.mode?c.items.length:Math.max(0,Math.min(c.items.length,b)),!c.isPending){var d,e,f,g;for(f=c.$control.children(":not(input)"),d=0,e=f.length;d<e;d++)g=a(f[d]).detach(),d<b?c.$control_input.before(g):c.$control.append(g)}c.caretPos=b},lock:function(){this.close(),this.isLocked=!0,this.refreshState()},unlock:function(){this.isLocked=!1,this.refreshState()},disable:function(){var a=this;a.$input.prop("disabled",!0),a.$control_input.prop("disabled",!0).prop("tabindex",-1),a.isDisabled=!0,a.lock()},enable:function(){var a=this;a.$input.prop("disabled",!1),a.$control_input.prop("disabled",!1).prop("tabindex",a.tabIndex),a.isDisabled=!1,a.unlock()},destroy:function(){var b=this,c=b.eventNS,d=b.revertSettings;b.trigger("destroy"),b.off(),b.$wrapper.remove(),b.$dropdown.remove(),b.$input.html("").append(d.$children).removeAttr("tabindex").removeClass("selectized").attr({tabindex:d.tabindex}).show(),b.$control_input.removeData("grow"),b.$input.removeData("selectize"),a(window).off(c),a(document).off(c),a(document.body).off(c),delete b.$input[0].selectize},render:function(b,c){var d,e,f="",g=!1,h=this;return"option"!==b&&"item"!==b||(d=z(c[h.settings.valueField]),g=!!d),g&&(y(h.renderCache[b])||(h.renderCache[b]={}),h.renderCache[b].hasOwnProperty(d))?h.renderCache[b][d]:(f=a(h.settings.render[b].apply(this,[c,A])),"option"===b||"option_create"===b?f.attr("data-selectable",""):"optgroup"===b&&(e=c[h.settings.optgroupValueField]||"",f.attr("data-group",e)),"option"!==b&&"item"!==b||f.attr("data-value",d||""),g&&(h.renderCache[b][d]=f[0]),f[0])},clearCache:function(a){var b=this;"undefined"==typeof a?b.renderCache={}:delete b.renderCache[a]},canCreate:function(a){var b=this;if(!b.settings.create)return!1;var c=b.settings.createFilter;return a.length&&("function"!=typeof c||c.apply(b,[a]))&&("string"!=typeof c||new RegExp(c).test(a))&&(!(c instanceof RegExp)||c.test(a))}}),L.count=0,L.defaults={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:!1,createOnBlur:!1,createFilter:null,highlight:!0,openOnFocus:!0,maxOptions:1e3,maxItems:null,hideSelected:null,addPrecedence:!1,selectOnTab:!1,preload:!1,allowEmptyOption:!1,closeAfterSelect:!1,scrollDuration:60,loadThrottle:300,loadingClass:"loading",dataAttr:"data-data",optgroupField:"optgroup",valueField:"value",labelField:"text",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"selectize-control",inputClass:"selectize-input",dropdownClass:"selectize-dropdown",dropdownContentClass:"selectize-dropdown-content",dropdownParent:null,copyClassesToDropdown:!0,render:{}},a.fn.selectize=function(b){var c=a.fn.selectize.defaults,d=a.extend({},c,b),e=d.dataAttr,f=d.labelField,g=d.valueField,h=d.optgroupField,i=d.optgroupLabelField,j=d.optgroupValueField,k=function(b,c){var h,i,j,k,l=b.attr(e);if(l)for(c.options=JSON.parse(l),h=0,i=c.options.length;h<i;h++)c.items.push(c.options[h][g]);else{var m=a.trim(b.val()||"");if(!d.allowEmptyOption&&!m.length)return;for(j=m.split(d.delimiter),h=0,i=j.length;h<i;h++)k={},k[f]=j[h],k[g]=j[h],c.options.push(k);c.items=j}},l=function(b,c){var k,l,m,n,o=c.options,p={},q=function(a){var b=e&&a.attr(e);return"string"==typeof b&&b.length?JSON.parse(b):null},r=function(b,e){b=a(b);var i=z(b.val());if(i||d.allowEmptyOption)if(p.hasOwnProperty(i)){if(e){var j=p[i][h];j?a.isArray(j)?j.push(e):p[i][h]=[j,e]:p[i][h]=e}}else{var k=q(b)||{};k[f]=k[f]||b.text(),k[g]=k[g]||i,k[h]=k[h]||e,p[i]=k,o.push(k),b.is(":selected")&&c.items.push(i)}},s=function(b){var d,e,f,g,h;for(b=a(b),f=b.attr("label"),f&&(g=q(b)||{},g[i]=f,g[j]=f,c.optgroups.push(g)),h=a("option",b),d=0,e=h.length;d<e;d++)r(h[d],f)};for(c.maxItems=b.attr("multiple")?null:1,n=b.children(),k=0,l=n.length;k<l;k++)m=n[k].tagName.toLowerCase(),"optgroup"===m?s(n[k]):"option"===m&&r(n[k])};return this.each(function(){if(!this.selectize){var e,f=a(this),g=this.tagName.toLowerCase(),h=f.attr("placeholder")||f.attr("data-placeholder");h||d.allowEmptyOption||(h=f.children('option[value=""]').text());var i={placeholder:h,options:[],optgroups:[],items:[]};"select"===g?l(f,i):k(f,i),e=new L(f,a.extend(!0,{},c,i,b))}})},a.fn.selectize.defaults=L.defaults,a.fn.selectize.support={validity:x},L.define("drag_drop",function(b){if(!a.fn.sortable)throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');if("multi"===this.settings.mode){var c=this;c.lock=function(){var a=c.lock;return function(){var b=c.$control.data("sortable");return b&&b.disable(),a.apply(c,arguments)}}(),c.unlock=function(){var a=c.unlock;return function(){var b=c.$control.data("sortable");return b&&b.enable(),a.apply(c,arguments)}}(),c.setup=function(){var b=c.setup;return function(){b.apply(this,arguments);var d=c.$control.sortable({items:"[data-value]",forcePlaceholderSize:!0,disabled:c.isLocked,start:function(a,b){b.placeholder.css("width",b.helper.css("width")),d.css({overflow:"visible"})},stop:function(){d.css({overflow:"hidden"});var b=c.$activeItems?c.$activeItems.slice():null,e=[];d.children("[data-value]").each(function(){e.push(a(this).attr("data-value"))}),c.setValue(e),c.setActiveItem(b)}})}}()}}),L.define("dropdown_header",function(b){var c=this;b=a.extend({title:"Untitled",headerClass:"selectize-dropdown-header",titleRowClass:"selectize-dropdown-header-title",labelClass:"selectize-dropdown-header-label",closeClass:"selectize-dropdown-header-close",html:function(a){return'<div class="'+a.headerClass+'"><div class="'+a.titleRowClass+'"><span class="'+a.labelClass+'">'+a.title+'</span><a href="javascript:void(0)" class="'+a.closeClass+'">×</a></div></div>'}},b),c.setup=function(){var d=c.setup;return function(){d.apply(c,arguments),c.$dropdown_header=a(b.html(b)),c.$dropdown.prepend(c.$dropdown_header)}}()}),L.define("optgroup_columns",function(b){var c=this;b=a.extend({equalizeWidth:!0,equalizeHeight:!0},b),this.getAdjacentOption=function(b,c){var d=b.closest("[data-group]").find("[data-selectable]"),e=d.index(b)+c;return e>=0&&e<d.length?d.eq(e):a()},this.onKeyDown=function(){var a=c.onKeyDown;return function(b){var d,e,f,g;return!this.isOpen||b.keyCode!==j&&b.keyCode!==m?a.apply(this,arguments):(c.ignoreHover=!0,g=this.$activeOption.closest("[data-group]"),d=g.find("[data-selectable]").index(this.$activeOption),g=b.keyCode===j?g.prev("[data-group]"):g.next("[data-group]"),f=g.find("[data-selectable]"),e=f.eq(Math.min(f.length-1,d)),void(e.length&&this.setActiveOption(e)))}}();var d=function(){var a,b=d.width,c=document;return"undefined"==typeof b&&(a=c.createElement("div"),a.innerHTML='<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>',a=a.firstChild,c.body.appendChild(a),b=d.width=a.offsetWidth-a.clientWidth,c.body.removeChild(a)),b},e=function(){var e,f,g,h,i,j,k;if(k=a("[data-group]",c.$dropdown_content),f=k.length,f&&c.$dropdown_content.width()){if(b.equalizeHeight){for(g=0,e=0;e<f;e++)g=Math.max(g,k.eq(e).height());k.css({height:g})}b.equalizeWidth&&(j=c.$dropdown_content.innerWidth()-d(),h=Math.round(j/f),k.css({width:h}),f>1&&(i=j-h*(f-1),k.eq(f-1).css({width:i})))}};(b.equalizeHeight||b.equalizeWidth)&&(B.after(this,"positionDropdown",e),B.after(this,"refreshOptions",e))}),L.define("remove_button",function(b){b=a.extend({label:"×",title:"Remove",className:"remove",append:!0},b);var c=function(b,c){c.className="remove-single";var d=b,e='<a href="javascript:void(0)" class="'+c.className+'" tabindex="-1" title="'+A(c.title)+'">'+c.label+"</a>",f=function(a,b){return a+b};b.setup=function(){var g=d.setup;return function(){if(c.append){var h=a(d.$input.context).attr("id"),i=(a("#"+h),d.settings.render.item);d.settings.render.item=function(a){return f(i.apply(b,arguments),e)}}g.apply(b,arguments),b.$control.on("click","."+c.className,function(a){a.preventDefault(),d.isLocked||d.clear()})}}()},d=function(b,c){var d=b,e='<a href="javascript:void(0)" class="'+c.className+'" tabindex="-1" title="'+A(c.title)+'">'+c.label+"</a>",f=function(a,b){var c=a.search(/(<\/[^>]+>\s*)$/);return a.substring(0,c)+b+a.substring(c)};b.setup=function(){var g=d.setup;return function(){if(c.append){var h=d.settings.render.item;d.settings.render.item=function(a){return f(h.apply(b,arguments),e)}}g.apply(b,arguments),b.$control.on("click","."+c.className,function(b){if(b.preventDefault(),!d.isLocked){var c=a(b.currentTarget).parent();d.setActiveItem(c),d.deleteSelection()&&d.setCaret(d.items.length)}})}}()};return"single"===this.settings.mode?void c(this,b):void d(this,b)}),L.define("restore_on_backspace",function(a){var b=this;a.text=a.text||function(a){return a[this.settings.labelField]},this.onKeyDown=function(){var c=b.onKeyDown;return function(b){var d,e;return b.keyCode===p&&""===this.$control_input.val()&&!this.$activeItems.length&&(d=this.caretPos-1,d>=0&&d<this.items.length)?(e=this.options[this.items[d]],this.deleteSelection(b)&&(this.setTextboxValue(a.text.apply(this,[e])),this.refreshOptions(!0)),void b.preventDefault()):c.apply(this,arguments)}}()}),L});
\ No newline at end of file
<head>
<title>{{title}}</title>
<link rel="stylesheet" href="/static/commitfest/css/jquery-ui.css" type="text/css">
- <link rel="stylesheet" href="/static/selectable/css/dj.selectable.css" type="text/css" media="all">
<link rel="stylesheet" href="/static/commitfest/css/bootstrap.css" />
<link rel="stylesheet" href="/static/commitfest/css/bootstrap-theme.min.css" />
<link rel="stylesheet" href="/static/commitfest/css/commitfest.css" />
<link rel="shortcut icon" href="/static/commitfest/favicon.ico" />
+{%block extrahead%}{%endblock%}
{%if rss_alternate%} <link rel="alternate" type="application/rss+xml" title="{{rss_alternate_title}}" href="{{rss_alternate}}" />{%endif%}
</head>
<body>
<script src="/static/commitfest/js/jquery.js"></script>
<script src="/static/commitfest/js/jquery-ui.js"></script>
<script src="/static/commitfest/js/bootstrap.js"></script>
-<script src="/static/selectable/js/jquery.dj.selectable.js"></script>
<script src="/static/commitfest/js/commitfest.js"></script>
{%block morescript%}{%endblock%}
</html>
{%extends "base.html"%}
-{%load selectable_tags%}
{%load commitfest%}
{%block contents%}
<div class="alert alert-danger">{{e}}</div>
{%endfor%}
{%endif%}
-{{field|field_class:"form-control"}}
+{%if not field.name in form.selectize_multiple_fields%}{{field|field_class:"form-control"}}{%else%}{{field}}{%endif%}
{%if field.help_text%}<br/>{{field.help_text|safe}}{%endif%}</div>
</div>
{%else%}
</div>
{%endblock%}
+{%block extrahead%}
+<link rel="stylesheet" href="/static/commitfest/css/selectize.css" />
+<link rel="stylesheet" href="/static/commitfest/css/selectize.default.css" />
+{%endblock%}
{%block morescript%}
+<script src="/static/commitfest/js/selectize.min.js"></script>
<script>
-/* Set up djselectable to use bootstrap buttons */
-$.ui.djselectable.prototype._comboButtonTemplate = function (input) {
- var icon = $("<i>").addClass("glyphicon glyphicon-chevron-down");
- // Remove current classes on the text input
- $(input).attr("class", "");
- // Wrap with input-append
- $(input).wrap('<div class="input-append" />');
- // Return button link with the chosen icon
- return $("<a>").append(icon).addClass("btn btn-default btn-xs");
-};
-$.ui.djselectable.prototype._removeButtonTemplate = function (item) {
- var icon = $("<i>").addClass("glyphicon glyphicon-remove-sign");
- // Return button link with the chosen icon
- return $("<a>").append(icon).addClass("btn btn-default btn-xs selectable-deck-remove");
-};
-
-/* Set up userid selectables to be searchable */
-$('input[data-selectable-url="/selectable/commitfest-userlookup/"]').each(function() {
- $(this).after(
- $('<a href="#" class="btn btn-default btn-sm">Import user not listed</a>').click(function () {
- search_and_store_user();
-
- return false;
- })
+/* Set up selectize fields */
+{% for f, url in selectize_multiple_fields %}
+ $('#id_{{f}}').selectize({
+ plugins: ['remove_button'],
+ valueField: 'id',
+ labelField: 'value',
+ searchField: 'value',
+ load: function(query, callback) {
+ if (!query.length) return callback();
+ $.ajax({
+ 'url': '{{url}}',
+ 'type': 'GET',
+ 'dataType': 'json',
+ 'data': {
+ 'query': query,
+ },
+ 'error': function() { callback();},
+ 'success': function(res) { callback(res.values);},
+ });
+ }
+ });
+{%endfor%}
+ $('.selectize-control').after(
+ $('<a href="#" class="btn btn-default btn-sm">Import user not listed</a>').click(function () {
+ search_and_store_user();
+ })
);
-});
$('#searchUserModal').on('shown.bs.modal', function() {
$('#searchUserSearchField').focus();
'form': form,
'patch': patch,
'title': 'Edit patch',
+ 'selectize_multiple_fields': form.selectize_multiple_fields.items(),
'breadcrumbs': [{'title': cf.title, 'href': '/%s/' % cf.pk},
{'title': 'View patch', 'href': '/%s/%s/' % (cf.pk, patch.pk)}],
})
+++ /dev/null
-../dep/django-selectable/selectable
\ No newline at end of file
'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
- 'pgcommitfest.selectable',
'pgcommitfest.commitfest.apps.CFAppConfig',
'pgcommitfest.mailqueue',
'pgcommitfest.userprofile',
import pgcommitfest.commitfest.views as views
import pgcommitfest.commitfest.reports as reports
import pgcommitfest.commitfest.ajax as ajax
+import pgcommitfest.commitfest.lookups as lookups
import pgcommitfest.auth
import pgcommitfest.userprofile.views
url(r'^(\d+)/reports/authorstats/$', reports.authorstats),
url(r'^search/$', views.global_search),
url(r'^ajax/(\w+)/$', ajax.main),
+ url(r'^lookups/user/$', lookups.userlookup),
url(r'^thread_notify/$', views.thread_notify),
- url(r'^selectable/', include('selectable.urls')),
-
# Auth system integration
url(r'^(?:account/)?login/?$', pgcommitfest.auth.login),
url(r'^(?:account/)?logout/?$', pgcommitfest.auth.logout),
+++ /dev/null
-dep/django-selectable/selectable
\ No newline at end of file