mirror of
https://github.com/Hopiu/django-select2.git
synced 2026-03-16 21:40:23 +00:00
Upgrade to select2 4.0
This commit is contained in:
parent
95297a362e
commit
6dad55eb11
28 changed files with 152 additions and 5357 deletions
|
|
@ -55,10 +55,10 @@ Light widgets are normally named, i.e. there is no
|
|||
:py:class:`.Select2MultipleWidget`,
|
||||
:py:class:`.HeavySelect2Widget`,
|
||||
:py:class:`.HeavySelect2MultipleWidget`,
|
||||
:py:class:`.AutoHeavySelect2Widget`,
|
||||
:py:class:`.AutoHeavySelect2MultipleWidget`,
|
||||
:py:class:`.ModelSelect2Widget`,
|
||||
:py:class:`.ModelSelect2MultipleWidget`,
|
||||
:py:class:`.HeavySelect2TagWidget`,
|
||||
:py:class:`.AutoHeavySelect2TagWidget`
|
||||
:py:class:`.ModelSelect2TagWidget`
|
||||
|
||||
`Read more`_
|
||||
|
||||
|
|
|
|||
|
|
@ -2,27 +2,19 @@
|
|||
"""Contains all the Django widgets for Select2."""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from functools import reduce
|
||||
from itertools import chain
|
||||
|
||||
from django import forms
|
||||
from django.core import signing
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import EMPTY_VALUES
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
||||
from django.db.models import Q
|
||||
from django.utils.datastructures import MergeDict, MultiValueDict
|
||||
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.six import text_type
|
||||
|
||||
from .cache import cache
|
||||
from .conf import settings
|
||||
from .media import (
|
||||
get_select2_css_libs, get_select2_heavy_js_libs, get_select2_js_libs
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -36,193 +28,19 @@ class Select2Mixin(object):
|
|||
The base mixin of all Select2 widgets.
|
||||
|
||||
This mixin is responsible for rendering the necessary
|
||||
JavaScript and CSS codes which turns normal ``<select>``
|
||||
markups into Select2 choice list.
|
||||
|
||||
The following Select2 options are added by this mixin:-
|
||||
|
||||
* minimumResultsForSearch: ``6``
|
||||
* placeholder: ``''``
|
||||
* allowClear: ``True``
|
||||
* multiple: ``False``
|
||||
* closeOnSelect: ``False``
|
||||
|
||||
.. note:: Many of them would be removed by sub-classes depending on requirements.
|
||||
data attributes for select2 as well as adding the static
|
||||
form media.
|
||||
"""
|
||||
|
||||
# For details on these options refer: http://ivaynberg.github.com/select2/#documentation
|
||||
options = {
|
||||
'minimumResultsForSearch': 6, # Only applicable for single value select.
|
||||
'placeholder': '', # Empty text label
|
||||
'allowClear': True, # Not allowed when field is multiple since there each value has a clear button.
|
||||
'multiple': False, # Not allowed when attached to <select>
|
||||
'closeOnSelect': False,
|
||||
}
|
||||
"""
|
||||
The options listed here are rendered as JS map and passed to Select2 JS code.
|
||||
Complete description of these options are available in Select2_ JS' site.
|
||||
|
||||
.. _Select2: http://ivaynberg.github.com/select2/#documentation.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Constructor of the class.
|
||||
|
||||
The following additional kwarg is allowed:-
|
||||
|
||||
:param select2_options: This is similar to standard Django way to pass extra attributes to widgets.
|
||||
This is meant to override values of existing :py:attr:`.options`.
|
||||
|
||||
Example::
|
||||
|
||||
class MyForm(ModelForm):
|
||||
class Meta:
|
||||
model = MyModel
|
||||
widgets = {
|
||||
'name': Select2WidgetName(select2_options={
|
||||
'minimumResultsForSearch': 10,
|
||||
'closeOnSelect': True,
|
||||
})
|
||||
}
|
||||
|
||||
.. tip:: You cannot introduce new options using this.
|
||||
For that you should sub-class and override
|
||||
:py:meth:`.init_options`. The reason for this is,
|
||||
few options are not compatible with each other
|
||||
or are not applicable in some scenarios. For example,
|
||||
when Select2 is attached to a ``<select>`` tag,
|
||||
it can detect if it is being used with a single or
|
||||
multiple values from that tag itself. If you specified the
|
||||
``multiple`` option in this case, it would not only be
|
||||
useless but an error from Select2 JS' point of view.
|
||||
|
||||
There are other such intricacies, based on which
|
||||
some options are removed. By enforcing this
|
||||
restriction we make sure to not break the code by
|
||||
passing some wrong concoction of options.
|
||||
|
||||
.. tip:: According to the select2 documentation, in order to
|
||||
get the ``placeholder`` and ``allowClear``
|
||||
settings working, you have to specify an empty
|
||||
``<option></option>`` as the first entry in your
|
||||
``<select>`` list. Otherwise the field will be
|
||||
rendered without a placeholder and the clear feature
|
||||
will stay disabled.
|
||||
|
||||
|
||||
:type select2_options: :py:obj:`dict` or None
|
||||
|
||||
"""
|
||||
# Making an instance specific copy
|
||||
self.options = dict(self.options)
|
||||
select2_options = kwargs.pop('select2_options', None)
|
||||
if select2_options:
|
||||
for name, value in select2_options.items():
|
||||
self.options[name] = value
|
||||
self.init_options()
|
||||
|
||||
super(Select2Mixin, self).__init__(**kwargs)
|
||||
|
||||
def init_options(self):
|
||||
"""
|
||||
Initialize options.
|
||||
|
||||
Sub-classes can use this to suppress or override options passed to Select2 JS library.
|
||||
|
||||
Example::
|
||||
|
||||
def init_options(self):
|
||||
self.options['createSearchChoice'] = 'Your_js_function'
|
||||
|
||||
In the above example we are setting ``Your_js_function``
|
||||
as Select2's ``createSearchChoice`` function.
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_placeholder(self, val):
|
||||
"""
|
||||
Placeholder is a value which Select2 JS library shows when nothing is selected.
|
||||
|
||||
This should be string.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.options['placeholder'] = val
|
||||
|
||||
def get_options(self):
|
||||
"""
|
||||
Return select2 js options.
|
||||
|
||||
:return: Dictionary of options to be passed to Select2 JS.
|
||||
|
||||
:rtype: :py:obj:`dict`
|
||||
"""
|
||||
options = dict(self.options)
|
||||
if options.get('allowClear', None) is not None:
|
||||
options['allowClear'] = not self.is_required
|
||||
if options.get('placeholder'):
|
||||
options['placeholder'] = force_text(options['placeholder'])
|
||||
return options
|
||||
|
||||
def render_js_code(self, id_, *args):
|
||||
"""
|
||||
Render the ``<script>`` block which contains the JS code for this widget.
|
||||
|
||||
:return: The rendered JS code enclosed inside ``<script>`` block.
|
||||
:rtype: :py:obj:`unicode`
|
||||
"""
|
||||
if id_:
|
||||
return self.render_js_script(self.render_inner_js_code(id_, *args))
|
||||
return ''
|
||||
|
||||
def render_js_script(self, inner_code):
|
||||
"""
|
||||
Wrap ``inner_code`` string inside a code block.
|
||||
|
||||
Example::
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(function ($) {
|
||||
// inner_code here
|
||||
});
|
||||
</script>
|
||||
|
||||
:rtype: :py:obj:`unicode`
|
||||
"""
|
||||
return """
|
||||
<script type="text/javascript">
|
||||
jQuery(function ($) {
|
||||
%s
|
||||
});
|
||||
</script>
|
||||
""" % inner_code
|
||||
|
||||
def render_inner_js_code(self, id_, *args):
|
||||
"""
|
||||
Render all the JS code required for this widget.
|
||||
|
||||
:return: The rendered JS code which will be later enclosed inside ``<script>`` block.
|
||||
:rtype: :py:obj:`unicode`
|
||||
"""
|
||||
options = json.dumps(self.get_options())
|
||||
options = options.replace('"*START*', '').replace('*END*"', '')
|
||||
js = 'var hashedSelector = "#" + "%s";' % id_
|
||||
js += '$(hashedSelector).select2(%s);' % (options)
|
||||
return js
|
||||
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
args = [name, value, attrs]
|
||||
if choices:
|
||||
args.append(choices)
|
||||
|
||||
s = text_type(super(Select2Mixin, self).render(*args)) # Thanks to @ouhouhsami Issue#1
|
||||
s += self.media.render()
|
||||
final_attrs = self.build_attrs(attrs)
|
||||
id_ = final_attrs.get('id', None)
|
||||
s += self.render_js_code(id_, name, value, attrs, choices)
|
||||
|
||||
return mark_safe(s)
|
||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
||||
attrs = super(Select2Mixin, self).build_attrs(extra_attrs=None, **kwargs)
|
||||
attrs.setdefault('data-allowClear', 'true' if self.is_required else 'false')
|
||||
attrs.setdefault('data-closeOnSelect', 'false')
|
||||
if 'class' in attrs:
|
||||
attrs['class'] += ' django-select2'
|
||||
else:
|
||||
attrs['class'] = 'django-select2'
|
||||
return attrs
|
||||
|
||||
def _get_media(self):
|
||||
"""
|
||||
|
|
@ -235,217 +53,102 @@ class Select2Mixin(object):
|
|||
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
|
||||
"""
|
||||
return forms.Media(
|
||||
js=get_select2_js_libs(),
|
||||
css={'screen': get_select2_css_libs(light=True)}
|
||||
js=('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js',
|
||||
'django_select2/django_select2.js'),
|
||||
css={'screen': ('//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css',)}
|
||||
)
|
||||
|
||||
media = property(_get_media)
|
||||
|
||||
|
||||
class Select2Widget(Select2Mixin, forms.Select):
|
||||
|
||||
"""
|
||||
Drop-in Select2 replacement for :py:class:`forms.Select`.
|
||||
|
||||
Following Select2 option from :py:attr:`.Select2Mixin.options` is removed:-
|
||||
|
||||
* multiple
|
||||
|
||||
"""
|
||||
|
||||
def init_options(self):
|
||||
self.options.pop('multiple', None)
|
||||
|
||||
def render_options(self, choices, selected_choices):
|
||||
all_choices = chain(self.choices, choices)
|
||||
if not self.is_required \
|
||||
and len([value for value, txt in all_choices if value == '']) == 0:
|
||||
# Checking if list already has empty choice
|
||||
# as in the case of Model based Light fields.
|
||||
choices = list(choices)
|
||||
choices.append(('', '', )) # Adding an empty choice
|
||||
return super(Select2Widget, self).render_options(choices, selected_choices)
|
||||
pass
|
||||
|
||||
|
||||
class Select2MultipleWidget(Select2Mixin, forms.SelectMultiple):
|
||||
|
||||
"""
|
||||
Drop-in Select2 replacement for :py:class:`forms.SelectMultiple`.
|
||||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are removed:-
|
||||
|
||||
* multiple
|
||||
* allowClear
|
||||
* minimumResultsForSearch
|
||||
|
||||
"""
|
||||
|
||||
def init_options(self):
|
||||
self.options.pop('multiple', None)
|
||||
self.options.pop('allowClear', None)
|
||||
self.options.pop('minimumResultsForSearch', None)
|
||||
pass
|
||||
|
||||
|
||||
# ## Specialized Multiple Hidden Input Widget ##
|
||||
class HeavySelect2Mixin(object):
|
||||
|
||||
"""Mixin that adds select2's ajax options and registers itself on django's cache."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.data_view = kwargs.pop('data_view')
|
||||
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
|
||||
super(HeavySelect2Mixin, self).__init__(**kwargs)
|
||||
|
||||
def get_url(self):
|
||||
return reverse_lazy(self.data_view)
|
||||
|
||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
||||
attrs = super(HeavySelect2Mixin, self).build_attrs(extra_attrs, **kwargs)
|
||||
|
||||
# encrypt instance Id
|
||||
widget_id = signing.dumps(id(self))
|
||||
# add widget object to cache
|
||||
cache.set(self._get_cache_key(), self)
|
||||
|
||||
attrs['data-field_id'] = widget_id
|
||||
attrs.setdefault('data-ajax--url', self.get_url())
|
||||
attrs.setdefault('data-ajax--cache', "true")
|
||||
attrs.setdefault('data-ajax--type', "GET")
|
||||
attrs.setdefault('data-minimumInputLength', 2)
|
||||
return attrs
|
||||
|
||||
def _get_cache_key(self):
|
||||
return "%s%s" % (settings.SELECT2_CACHE_PREFIX, id(self))
|
||||
|
||||
def value_from_datadict(self, *args, **kwargs):
|
||||
return super(HeavySelect2Mixin, self).value_from_datadict(*args, **kwargs)
|
||||
|
||||
def render_options(self, choices, selected_choices):
|
||||
selected_choices = set(force_text(v) for v in selected_choices)
|
||||
output = []
|
||||
for option_value, option_label in selected_choices:
|
||||
output.append(self.render_option(selected_choices, option_value, option_label))
|
||||
return '\n'.join(output)
|
||||
|
||||
|
||||
class MultipleSelect2HiddenInput(forms.TextInput):
|
||||
|
||||
"""
|
||||
Multiple hidden input for Select2.
|
||||
|
||||
This is a specialized multiple Hidden Input widget. This includes a special
|
||||
JS component which renders multiple Hidden Input boxes as there are values.
|
||||
So, if user suppose chooses values 1, 4 and 9 then Select2 would would write them
|
||||
to the primary hidden input. The JS component of this widget will read that value and
|
||||
will render three more hidden input boxes each with values 1, 4 and 9 respectively.
|
||||
They will all share the name of this field, and the name of the primary source
|
||||
hidden input would be removed. This way, when submitted all the selected values
|
||||
would be available as list.
|
||||
"""
|
||||
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
attrs = self.build_attrs(attrs, multiple='multiple')
|
||||
s = text_type(super(MultipleSelect2HiddenInput, self).render(name, "", attrs))
|
||||
id_ = attrs.get('id', None)
|
||||
if id_:
|
||||
jscode = ''
|
||||
if value:
|
||||
jscode = '$("#%s").val(django_select2.convertArrToStr(%s));' % (id_, json.dumps(value))
|
||||
jscode += "django_select2.initMultipleHidden($('#%s'));" % id_
|
||||
s += self.render_js_script(jscode)
|
||||
return mark_safe(s)
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
if isinstance(data, (MultiValueDict, MergeDict)):
|
||||
return data.getlist(name)
|
||||
return data.get(name, None)
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
if initial is None:
|
||||
initial = []
|
||||
if data is None:
|
||||
data = []
|
||||
if len(initial) != len(data):
|
||||
return True
|
||||
initial_set = set([force_text(value) for value in initial])
|
||||
data_set = set([force_text(value) for value in data])
|
||||
return data_set != initial_set
|
||||
|
||||
@property
|
||||
def is_hidden(self):
|
||||
# we return false because even if input_type is 'hidden'
|
||||
# , the final field will be displayed by javascript
|
||||
# and we want label and other layout elements.
|
||||
return False
|
||||
class HeavySelect2Widget(HeavySelect2Mixin, forms.Select):
|
||||
pass
|
||||
|
||||
|
||||
# ## Heavy mixins and widgets ###
|
||||
class HeavySelect2MultipleWidget(HeavySelect2Mixin, forms.SelectMultiple):
|
||||
pass
|
||||
|
||||
class HeavySelect2Mixin(Select2Mixin):
|
||||
|
||||
"""
|
||||
The base mixin of all Heavy Select2 widgets. It sub-classes :py:class:`Select2Mixin`.
|
||||
class HeavySelect2TagWidget(HeavySelect2MultipleWidget):
|
||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
||||
attrs = super(HeavySelect2TagWidget, self).build_attrs(self, extra_attrs, **kwargs)
|
||||
attrs['data-minimumInputLength'] = 1
|
||||
attrs['data-tags'] = 'true'
|
||||
attrs['data-tokenSeparators'] = [",", " "]
|
||||
return attrs
|
||||
|
||||
This mixin adds more Select2 options to :py:attr:`.Select2Mixin.options`. These are:-
|
||||
|
||||
* minimumInputLength: ``2``
|
||||
* initSelection: ``'django_select2.onInit'``
|
||||
* ajax:
|
||||
* dataType: ``'json'``
|
||||
* quietMillis: ``100``
|
||||
* data: ``'django_select2.get_url_params'``
|
||||
* results: ``'django_select2.process_results'``
|
||||
# Auto Heavy widgets
|
||||
|
||||
.. tip:: You can override these options by passing ``select2_options``
|
||||
kwarg to :py:meth:`.__init__`.
|
||||
"""
|
||||
|
||||
class ModelSelect2Mixin(object):
|
||||
|
||||
"""Mixin for """
|
||||
|
||||
model = None
|
||||
queryset = None
|
||||
search_fields = []
|
||||
max_results = 25
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Constructor of the class.
|
||||
|
||||
The following kwargs are allowed:-
|
||||
|
||||
:param data_view: A :py:class:`~.views.Select2View` sub-class which
|
||||
can respond to this widget's Ajax queries.
|
||||
:type data_view: :py:class:`django.views.generic.base.View` or None
|
||||
|
||||
:param data_url: Url which will respond to Ajax queries with JSON object.
|
||||
:type data_url: :py:obj:`str` or None
|
||||
|
||||
.. tip:: When ``data_view`` is provided then it is converted into an URL using
|
||||
:py:func:`~django.core.urlresolvers.reverse`.
|
||||
|
||||
.. warning:: Either of ``data_view`` or ``data_url`` must be specified,
|
||||
otherwise :py:exc:`ValueError` will
|
||||
be raised.
|
||||
|
||||
:param choices: The list of available choices.
|
||||
If not provided then empty list is used instead.
|
||||
It should be of the form -- ``[(val1, 'Label1'), (val2, 'Label2'), ...]``.
|
||||
:type choices: :py:obj:`list` or :py:obj:`tuple`
|
||||
|
||||
:param userGetValTextFuncName: The name of the custom JS function which
|
||||
you want to use to convert value to label.
|
||||
|
||||
In ``heavy_data.js``, ``django_select2.getValText()`` employs
|
||||
the following logic to convert value to label :-
|
||||
|
||||
1. First check if the Select2 input field has ``txt`` attribute
|
||||
set along with ``value``. If found then use it.
|
||||
|
||||
2. Otherwise, check if user has provided any custom method for this.
|
||||
Then use that. If it returns a label then use it.
|
||||
|
||||
3. Otherwise, check the cached results. When the user searches
|
||||
in the fields then all the returned responses from server,
|
||||
which has the value and label mapping, are cached by ``heavy_data.js``.
|
||||
|
||||
:type userGetValTextFuncName: :py:obj:`str`
|
||||
|
||||
.. tip:: Since version 3.2.0, cookies or localStorage are no longer checked or used.
|
||||
All :py:class:`~.field.HeavyChoiceField` must override
|
||||
:py:meth:`~.fields.HeavyChoiceField.get_val_txt`.
|
||||
If you are only using heavy widgets in your own fields
|
||||
then you should override :py:meth:`.render_texts`.
|
||||
"""
|
||||
self.field = None
|
||||
self.options = dict(self.options) # Making an instance specific copy
|
||||
self.view = kwargs.pop('data_view', 'django_select2_central_json')
|
||||
self.url = kwargs.pop('data_url', None)
|
||||
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
|
||||
self.choices = kwargs.pop('choices', [])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.model = kwargs.pop('model', self.model)
|
||||
self.queryset = kwargs.pop('queryset', self.queryset)
|
||||
self.search_fields = kwargs.pop('search_fields', self.search_fields)
|
||||
self.max_results = kwargs.pop('max_results', self.max_results)
|
||||
|
||||
self.options['ajax'] = {
|
||||
'dataType': 'json',
|
||||
'quietMillis': 100,
|
||||
'data': ('*START*django_select2.runInContextHelper('
|
||||
'django_select2.get_url_params, selector'
|
||||
')*END*'),
|
||||
'results': ('*START*django_select2.runInContextHelper('
|
||||
'django_select2.process_results, selector'
|
||||
')*END*'),
|
||||
}
|
||||
self.options['minimumInputLength'] = 2
|
||||
self.options['initSelection'] = '*START*django_select2.onInit*END*'
|
||||
super(HeavySelect2Mixin, self).__init__(**kwargs)
|
||||
defaults = {'data_view': 'django_select2_central_json'}
|
||||
defaults.update(kwargs)
|
||||
super(ModelSelect2Mixin, self).__init__(*args, **defaults)
|
||||
|
||||
def filter_queryset(self, term):
|
||||
"""
|
||||
See :py:meth:`.views.Select2View.get_results`.
|
||||
|
||||
This implementation takes care of detecting if more results are available.
|
||||
"""
|
||||
qs = self.get_queryset()
|
||||
search_fields = self.get_search_fields()
|
||||
select = reduce(lambda x, y: Q(**{x: term}) | Q(**{y: term}), search_fields,
|
||||
|
|
@ -472,314 +175,22 @@ class HeavySelect2Mixin(Select2Mixin):
|
|||
return self.search_fields
|
||||
raise NotImplementedError('%s, must implement "search_fields".' % self.__class__.__name__)
|
||||
|
||||
def render_texts(self, selected_choices, choices):
|
||||
"""
|
||||
Render a JS array with labels for the ``selected_choices``.
|
||||
|
||||
:param selected_choices: List of selected choices' values.
|
||||
:type selected_choices: :py:obj:`list` or :py:obj:`tuple`
|
||||
|
||||
:param choices: Extra choices, if any. This is a list of tuples. In each tuple, the first
|
||||
item is the choice value and the second item is choice label.
|
||||
:type choices: :py:obj:`list` or :py:obj:`tuple`
|
||||
|
||||
:return: The rendered JS array code.
|
||||
:rtype: :py:obj:`unicode`
|
||||
"""
|
||||
selected_choices = list(force_text(v) for v in selected_choices)
|
||||
txts = []
|
||||
all_choices = choices if choices else []
|
||||
choices_dict = dict()
|
||||
self_choices = self.choices
|
||||
|
||||
for val, txt in chain(self_choices, all_choices):
|
||||
val = force_text(val)
|
||||
choices_dict[val] = force_text(txt)
|
||||
|
||||
for val in selected_choices:
|
||||
try:
|
||||
txts.append(choices_dict[val])
|
||||
except KeyError:
|
||||
logger.error("Value '%s' is not a valid choice.", val)
|
||||
|
||||
if hasattr(self.field, '_get_val_txt') and selected_choices:
|
||||
for val in selected_choices:
|
||||
txt = self.field._get_val_txt(val)
|
||||
if txt is not None:
|
||||
txts.append(txt)
|
||||
if txts:
|
||||
return json.dumps(txts)
|
||||
|
||||
def get_options(self):
|
||||
if self.url is None:
|
||||
# We lazy resolve the view. By this time Url conf would been loaded fully.
|
||||
self.url = reverse(self.view)
|
||||
|
||||
if self.options['ajax'].get('url', None) is None:
|
||||
self.options['ajax']['url'] = self.url
|
||||
|
||||
return super(HeavySelect2Mixin, self).get_options()
|
||||
|
||||
def render_texts_for_value(self, id_, value, choices):
|
||||
"""
|
||||
Render the JS code which sets the ``txt`` attribute on the field.
|
||||
|
||||
It gets the array of lables from :py:meth:`.render_texts`.
|
||||
|
||||
:param id_: Id of the field. This can be used to get reference of this field's DOM in JS.
|
||||
:type id_: :py:obj:`str`
|
||||
|
||||
:param value: Currently set value on the field.
|
||||
:type value: Any
|
||||
|
||||
:param choices: Extra choices, if any. This is a list of tuples. In each tuple, the first
|
||||
item is the choice value and the second item is choice label.
|
||||
:type choices: :py:obj:`list` or :py:obj:`tuple`
|
||||
|
||||
:return: JS code which sets the ``txt`` attribute.
|
||||
:rtype: :py:obj:`unicode`
|
||||
"""
|
||||
empty_values = getattr(self.field, 'empty_values', EMPTY_VALUES)
|
||||
if value is not None and (self.field is None or value not in empty_values):
|
||||
# Just like forms.Select.render() it assumes that value will be single valued.
|
||||
values = [value]
|
||||
texts = self.render_texts(values, choices)
|
||||
if texts:
|
||||
return "$('#%s').txt(%s);" % (id_, texts)
|
||||
|
||||
def render_inner_js_code(self, id_, name, value, attrs=None, choices=(), *args):
|
||||
js = '$(hashedSelector).change(django_select2.onValChange).data("userGetValText", null);'
|
||||
texts = self.render_texts_for_value(id_, value, choices)
|
||||
if texts:
|
||||
js += texts
|
||||
js += super(HeavySelect2Mixin, self).render_inner_js_code(id_, name, value, attrs, choices, *args)
|
||||
return js
|
||||
|
||||
def _get_cache_key(self):
|
||||
return "%s%s" % (settings.SELECT2_CACHE_PREFIX, id(self))
|
||||
|
||||
def render(self, name, value, attrs={}, choices=()):
|
||||
self.widget_id = signing.dumps(id(self))
|
||||
cache.set(self._get_cache_key(), self)
|
||||
attrs.setdefault('data-field_id', self.widget_id)
|
||||
output = super(HeavySelect2Mixin, self).render(name, value, attrs, choices)
|
||||
return output
|
||||
|
||||
def value_from_datadict(self, *args, **kwargs):
|
||||
return super(HeavySelect2Mixin, self).value_from_datadict(*args, **kwargs)
|
||||
|
||||
def _get_media(self):
|
||||
"""
|
||||
Construct Media as a dynamic property.
|
||||
|
||||
This is essential because we need to check RENDER_SELECT2_STATICS
|
||||
before returning our assets.
|
||||
|
||||
for more information:
|
||||
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
|
||||
"""
|
||||
return forms.Media(
|
||||
js=get_select2_heavy_js_libs(),
|
||||
css={'screen': get_select2_css_libs()}
|
||||
)
|
||||
media = property(_get_media)
|
||||
|
||||
|
||||
class HeavySelect2Widget(HeavySelect2Mixin, forms.TextInput):
|
||||
|
||||
"""
|
||||
Single selection heavy widget.
|
||||
|
||||
Following Select2 option from :py:attr:`.Select2Mixin.options` is added or set:-
|
||||
|
||||
* multiple: ``False``
|
||||
|
||||
"""
|
||||
|
||||
def init_options(self):
|
||||
self.options['multiple'] = False
|
||||
|
||||
@property
|
||||
def is_hidden(self):
|
||||
# we return false because even if input_type is 'hidden'
|
||||
# , the final field will be displayed by javascript
|
||||
# and we want label and other layout elements.
|
||||
return False
|
||||
|
||||
def render_inner_js_code(self, id_, *args):
|
||||
field_id = self.widget_id
|
||||
fieldset_id = re.sub(r'-\d+-', '_', id_).replace('-', '_')
|
||||
if '__prefix__' in id_:
|
||||
return ''
|
||||
else:
|
||||
js = '''
|
||||
window.django_select2.%s = function (selector, fieldID) {
|
||||
var hashedSelector = "#" + selector;
|
||||
''' % (fieldset_id)
|
||||
js += super(HeavySelect2Widget, self).render_inner_js_code(id_, *args)
|
||||
js += '};'
|
||||
js += 'django_select2.%s("%s", "%s");' % (fieldset_id, id_, field_id)
|
||||
return js
|
||||
|
||||
|
||||
class HeavySelect2MultipleWidget(HeavySelect2Mixin, MultipleSelect2HiddenInput):
|
||||
|
||||
"""
|
||||
Multiple selection heavy widget.
|
||||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are removed:-
|
||||
|
||||
* allowClear
|
||||
* minimumResultsForSearch
|
||||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are added or set:-
|
||||
|
||||
* multiple: ``True``
|
||||
* separator: ``django_select2.MULTISEPARATOR``
|
||||
|
||||
"""
|
||||
|
||||
def init_options(self):
|
||||
self.options['multiple'] = True
|
||||
self.options.pop('allowClear', None)
|
||||
self.options.pop('minimumResultsForSearch', None)
|
||||
self.options['separator'] = '*START*django_select2.MULTISEPARATOR*END*'
|
||||
|
||||
def render_texts_for_value(self, id_, value, choices):
|
||||
"""
|
||||
Render the JS code which sets the ``txt`` attribute on the field.
|
||||
|
||||
It gets the array of lables from :py:meth:`.render_texts`.
|
||||
|
||||
:param id_: Id of the field. This can be used to get reference of this field's DOM in JS.
|
||||
:type id_: :py:obj:`str`
|
||||
|
||||
:param value: **List** of currently set value on the field.
|
||||
:type value: :py:obj:`list`
|
||||
|
||||
:param choices: Extra choices, if any. This is a list of tuples. In each tuple, the first
|
||||
item is the choice value and the second item is choice label.
|
||||
:type choices: :py:obj:`list` or :py:obj:`tuple`
|
||||
|
||||
:return: JS code which sets the ``txt`` attribute.
|
||||
:rtype: :py:obj:`unicode`
|
||||
"""
|
||||
# Just like forms.SelectMultiple.render()
|
||||
# it assumes that value will be multi-valued (list).
|
||||
if value:
|
||||
texts = self.render_texts(value, choices)
|
||||
if texts:
|
||||
return '$("#%s").txt(%s);' % (id_, texts)
|
||||
|
||||
def render_inner_js_code(self, id_, *args):
|
||||
field_id = self.widget_id
|
||||
fieldset_id = re.sub(r'-\d+-', '_', id_).replace('-', '_')
|
||||
if '__prefix__' in id_:
|
||||
return ''
|
||||
else:
|
||||
js = '''
|
||||
window.django_select2.%s = function (selector, fieldID) {
|
||||
var hashedSelector = "#" + selector;
|
||||
''' % (fieldset_id)
|
||||
js += super(HeavySelect2MultipleWidget, self).render_inner_js_code(id_, *args)
|
||||
js += '};'
|
||||
js += 'django_select2.%s("%s", "%s");' % (fieldset_id, id_, field_id)
|
||||
return js
|
||||
|
||||
|
||||
class HeavySelect2TagWidget(HeavySelect2MultipleWidget):
|
||||
|
||||
"""
|
||||
Heavy widget with tagging support.
|
||||
|
||||
Based on :py:class:`HeavySelect2MultipleWidget`,
|
||||
unlike other widgets this allows users to create new options (tags).
|
||||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are removed:-
|
||||
|
||||
* allowClear
|
||||
* minimumResultsForSearch
|
||||
* closeOnSelect
|
||||
|
||||
Following Select2 options from :py:attr:`.Select2Mixin.options` are added or set:-
|
||||
|
||||
* multiple: ``True``
|
||||
* separator: ``django_select2.MULTISEPARATOR``
|
||||
* tags: ``True``
|
||||
* tokenSeparators: ``,`` and `` ``
|
||||
* createSearchChoice: ``django_select2.createSearchChoice``
|
||||
* minimumInputLength: ``1``
|
||||
|
||||
"""
|
||||
|
||||
def init_options(self):
|
||||
super(HeavySelect2TagWidget, self).init_options()
|
||||
self.options.pop('closeOnSelect', None)
|
||||
self.options['minimumInputLength'] = 1
|
||||
self.options['tags'] = True
|
||||
self.options['tokenSeparators'] = [",", " "]
|
||||
self.options['createSearchChoice'] = '*START*django_select2.createSearchChoice*END*'
|
||||
|
||||
def render_inner_js_code(self, id_, *args):
|
||||
field_id = self.widget_id
|
||||
fieldset_id = re.sub(r'-\d+-', '_', id_).replace('-', '_')
|
||||
if '__prefix__' in id_:
|
||||
return ''
|
||||
else:
|
||||
js = '''
|
||||
window.django_select2.%s = function (selector, fieldID) {
|
||||
var hashedSelector = "#" + selector;
|
||||
''' % (fieldset_id)
|
||||
js += super(HeavySelect2TagWidget, self).render_inner_js_code(id_, *args)
|
||||
js += '};'
|
||||
js += 'django_select2.%s("%s", "%s");' % (fieldset_id, id_, field_id)
|
||||
return js
|
||||
|
||||
|
||||
# ## Auto Heavy widgets ##
|
||||
|
||||
|
||||
class AutoHeavySelect2Mixin(object):
|
||||
|
||||
"""
|
||||
This mixin is needed for Auto heavy fields.
|
||||
|
||||
This mixin adds extra JS code to notify the field's DOM object of the generated id.
|
||||
The generated id is not the same as the ``id`` attribute of the field's HTML markup.
|
||||
This id is generated by :py:func:`~.util.register_field` when the Auto field is registered.
|
||||
The client side (DOM) sends this id along with the Ajax request, so that the central
|
||||
view can identify which field should be used to serve the request.
|
||||
|
||||
The js call to dynamically add the `django_select2` is as follows::
|
||||
|
||||
django_select2.id_cities('id_cities', django_select2.id_cities_field_id);
|
||||
|
||||
For an inline formset::
|
||||
|
||||
django_select2.id_musician_set_name(
|
||||
'id_musician_set-0-name', django_select2.id_musician_set_name_field_id);
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['data_view'] = "django_select2_central_json"
|
||||
super(AutoHeavySelect2Mixin, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class AutoHeavySelect2Widget(AutoHeavySelect2Mixin, HeavySelect2Widget):
|
||||
class ModelSelect2Widget(ModelSelect2Mixin, HeavySelect2Widget):
|
||||
|
||||
"""Auto version of :py:class:`.HeavySelect2Widget`."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AutoHeavySelect2MultipleWidget(AutoHeavySelect2Mixin, HeavySelect2MultipleWidget):
|
||||
class ModelSelect2MultipleWidget(ModelSelect2Mixin, HeavySelect2MultipleWidget):
|
||||
|
||||
"""Auto version of :py:class:`.HeavySelect2MultipleWidget`."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AutoHeavySelect2TagWidget(AutoHeavySelect2Mixin, HeavySelect2TagWidget):
|
||||
class ModelSelect2TagWidget(ModelSelect2Mixin, HeavySelect2TagWidget):
|
||||
|
||||
"""Auto version of :py:class:`.HeavySelect2TagWidget`."""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||
|
||||
from .conf import settings
|
||||
|
||||
|
||||
def django_select2_static(file):
|
||||
return static('django_select2/' + file)
|
||||
|
||||
|
||||
def get_select2_js_libs():
|
||||
if settings.DEBUG:
|
||||
js_file = 'js/select2.js'
|
||||
else:
|
||||
js_file = 'js/select2.min.js'
|
||||
return django_select2_static(js_file),
|
||||
|
||||
|
||||
def get_select2_heavy_js_libs():
|
||||
libs = get_select2_js_libs()
|
||||
|
||||
if settings.DEBUG:
|
||||
js_file = 'js/heavy_data.js'
|
||||
else:
|
||||
js_file = 'js/heavy_data.min.js'
|
||||
return libs + (django_select2_static(js_file), )
|
||||
|
||||
|
||||
def get_select2_css_libs(light=False):
|
||||
if settings.DEBUG:
|
||||
if light:
|
||||
css_files = 'css/select2.css',
|
||||
else:
|
||||
css_files = 'css/select2.css', 'css/extra.css'
|
||||
if settings.SELECT2_BOOTSTRAP:
|
||||
css_files += 'css/select2-bootstrap.css',
|
||||
else:
|
||||
if settings.SELECT2_BOOTSTRAP:
|
||||
if light:
|
||||
css_files = 'css/select2-bootstrapped.min.css',
|
||||
else:
|
||||
css_files = 'css/all-bootstrapped.min.css',
|
||||
else:
|
||||
if light:
|
||||
css_files = 'css/select2.min.css',
|
||||
else:
|
||||
css_files = 'css/all.min.css',
|
||||
|
||||
return [django_select2_static(f) for f in css_files]
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,9 +0,0 @@
|
|||
.error a.select2-choice {
|
||||
border: 1px solid #B94A48;
|
||||
}
|
||||
.select2-container {
|
||||
min-width: 150px;
|
||||
}
|
||||
.select2-container.select2-container-multi {
|
||||
width: 300px;
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
.form-control .select2-choice {
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.form-control .select2-choice .select2-arrow {
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
.form-control.select2-container {
|
||||
height: auto !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-control.select2-container.select2-dropdown-open {
|
||||
border-color: #5897FB;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.form-control .select2-container.select2-dropdown-open .select2-choices {
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.form-control.select2-container .select2-choices {
|
||||
border: 0 !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.control-group.warning .select2-container .select2-choice,
|
||||
.control-group.warning .select2-container .select2-choices,
|
||||
.control-group.warning .select2-container-active .select2-choice,
|
||||
.control-group.warning .select2-container-active .select2-choices,
|
||||
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.warning .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #C09853 !important;
|
||||
}
|
||||
|
||||
.control-group.warning .select2-container .select2-choice div {
|
||||
border-left: 1px solid #C09853 !important;
|
||||
background: #FCF8E3 !important;
|
||||
}
|
||||
|
||||
.control-group.error .select2-container .select2-choice,
|
||||
.control-group.error .select2-container .select2-choices,
|
||||
.control-group.error .select2-container-active .select2-choice,
|
||||
.control-group.error .select2-container-active .select2-choices,
|
||||
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.error .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #B94A48 !important;
|
||||
}
|
||||
|
||||
.control-group.error .select2-container .select2-choice div {
|
||||
border-left: 1px solid #B94A48 !important;
|
||||
background: #F2DEDE !important;
|
||||
}
|
||||
|
||||
.control-group.info .select2-container .select2-choice,
|
||||
.control-group.info .select2-container .select2-choices,
|
||||
.control-group.info .select2-container-active .select2-choice,
|
||||
.control-group.info .select2-container-active .select2-choices,
|
||||
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.info .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #3A87AD !important;
|
||||
}
|
||||
|
||||
.control-group.info .select2-container .select2-choice div {
|
||||
border-left: 1px solid #3A87AD !important;
|
||||
background: #D9EDF7 !important;
|
||||
}
|
||||
|
||||
.control-group.success .select2-container .select2-choice,
|
||||
.control-group.success .select2-container .select2-choices,
|
||||
.control-group.success .select2-container-active .select2-choice,
|
||||
.control-group.success .select2-container-active .select2-choices,
|
||||
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choices,
|
||||
.control-group.success .select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #468847 !important;
|
||||
}
|
||||
|
||||
.control-group.success .select2-container .select2-choice div {
|
||||
border-left: 1px solid #468847 !important;
|
||||
background: #DFF0D8 !important;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,704 +0,0 @@
|
|||
/*
|
||||
Version: 3.5.2 Timestamp: Sat Nov 1 14:43:36 EDT 2014
|
||||
*/
|
||||
.select2-container {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
/* inline-block for ie7 */
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.select2-container,
|
||||
.select2-drop,
|
||||
.select2-search,
|
||||
.select2-search input {
|
||||
/*
|
||||
Force border-box so that % widths fit the parent
|
||||
container without overlap because of margin/padding.
|
||||
More Info : http://www.quirksmode.org/css/box.html
|
||||
*/
|
||||
-webkit-box-sizing: border-box; /* webkit */
|
||||
-moz-box-sizing: border-box; /* firefox */
|
||||
box-sizing: border-box; /* css3 */
|
||||
}
|
||||
|
||||
.select2-container .select2-choice {
|
||||
display: block;
|
||||
height: 26px;
|
||||
padding: 0 0 0 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
white-space: nowrap;
|
||||
line-height: 26px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
|
||||
background-image: linear-gradient(to top, #eee 0%, #fff 50%);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice {
|
||||
padding: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-drop-above .select2-choice {
|
||||
border-bottom-color: #aaa;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
|
||||
background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice .select2-chosen {
|
||||
margin-right: 42px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice > .select2-chosen {
|
||||
margin-right: 26px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice > .select2-chosen {
|
||||
margin-left: 26px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr {
|
||||
display: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 8px;
|
||||
|
||||
font-size: 1px;
|
||||
text-decoration: none;
|
||||
|
||||
border: 0;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice abbr {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr:hover {
|
||||
background-position: right -11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2-drop-mask {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
opacity: 0;
|
||||
z-index: 9998;
|
||||
/* styles required for IE to work */
|
||||
background-color: #fff;
|
||||
filter: alpha(opacity=0);
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
width: 100%;
|
||||
margin-top: -1px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 100%;
|
||||
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #aaa;
|
||||
border-top: 0;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above {
|
||||
margin-top: 1px;
|
||||
border-top: 1px solid #aaa;
|
||||
border-bottom: 0;
|
||||
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
border: 1px solid #5897fb;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above.select2-drop-active {
|
||||
border-top: 1px solid #5897fb;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width {
|
||||
border-top: 1px solid #aaa;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width .select2-search {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
border-left: 1px solid #aaa;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
background: #ccc;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
|
||||
background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice .select2-arrow {
|
||||
left: 0;
|
||||
right: auto;
|
||||
|
||||
border-left: none;
|
||||
border-right: 1px solid #aaa;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('select2.png') no-repeat 0 1px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice .select2-arrow b {
|
||||
background-position: 2px 1px;
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
min-height: 26px;
|
||||
margin: 0;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
|
||||
position: relative;
|
||||
z-index: 10000;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
min-height: 26px;
|
||||
padding: 4px 20px 4px 5px;
|
||||
margin: 0;
|
||||
|
||||
outline: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 0;
|
||||
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
|
||||
background: #fff url('select2.png') no-repeat 100% -22px;
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-search input {
|
||||
padding: 4px 5px 4px 20px;
|
||||
|
||||
background: #fff url('select2.png') no-repeat -37px -22px;
|
||||
background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above .select2-search input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.select2-search input.select2-active {
|
||||
background: #fff url('select2-spinner.gif') no-repeat 100%;
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-container-active .select2-choice,
|
||||
.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice {
|
||||
border-bottom-color: transparent;
|
||||
-webkit-box-shadow: 0 1px 0 #fff inset;
|
||||
box-shadow: 0 1px 0 #fff inset;
|
||||
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
background-color: #eee;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
background-image: linear-gradient(to top, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.select2-dropdown-open.select2-drop-above .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
border-top-color: transparent;
|
||||
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow {
|
||||
background: transparent;
|
||||
border-left: none;
|
||||
filter: none;
|
||||
}
|
||||
html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow b {
|
||||
background-position: -18px 1px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b {
|
||||
background-position: -16px 1px;
|
||||
}
|
||||
|
||||
.select2-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* results */
|
||||
.select2-results {
|
||||
max-height: 200px;
|
||||
padding: 0 0 0 4px;
|
||||
margin: 4px 4px 4px 0;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-results {
|
||||
padding: 0 4px 0 0;
|
||||
margin: 4px 0 4px 4px;
|
||||
}
|
||||
|
||||
.select2-results ul.select2-result-sub {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.select2-results li {
|
||||
list-style: none;
|
||||
display: list-item;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.select2-results li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
padding: 3px 7px 4px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
min-height: 1em;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.select2-results-dept-1 .select2-result-label { padding-left: 20px }
|
||||
.select2-results-dept-2 .select2-result-label { padding-left: 40px }
|
||||
.select2-results-dept-3 .select2-result-label { padding-left: 60px }
|
||||
.select2-results-dept-4 .select2-result-label { padding-left: 80px }
|
||||
.select2-results-dept-5 .select2-result-label { padding-left: 100px }
|
||||
.select2-results-dept-6 .select2-result-label { padding-left: 110px }
|
||||
.select2-results-dept-7 .select2-result-label { padding-left: 120px }
|
||||
|
||||
.select2-results .select2-highlighted {
|
||||
background: #3875d7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.select2-results li em {
|
||||
background: #feffde;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted em {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted ul {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-ajax-error,
|
||||
.select2-results .select2-selection-limit {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
disabled look for disabled choices in the results dropdown
|
||||
*/
|
||||
.select2-results .select2-disabled.select2-highlighted {
|
||||
color: #666;
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
.select2-results .select2-disabled {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-results .select2-selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-more-results.select2-active {
|
||||
background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
|
||||
}
|
||||
|
||||
.select2-results .select2-ajax-error {
|
||||
background: rgba(255, 50, 50, .2);
|
||||
}
|
||||
|
||||
.select2-more-results {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice abbr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* multiselect */
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
margin: 0;
|
||||
padding: 0 5px 0 0;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
|
||||
background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices {
|
||||
padding: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.select2-locked {
|
||||
padding: 3px 5px 3px 5px !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
}
|
||||
.select2-container-multi .select2-choices li {
|
||||
float: left;
|
||||
list-style: none;
|
||||
}
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices li
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-field {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input {
|
||||
padding: 5px;
|
||||
margin: 1px 0;
|
||||
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
color: #666;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
|
||||
background: #fff url('select2-spinner.gif') no-repeat 100% !important;
|
||||
}
|
||||
|
||||
.select2-default {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px 3px 18px;
|
||||
margin: 3px 0 3px 5px;
|
||||
position: relative;
|
||||
|
||||
line-height: 13px;
|
||||
color: #333;
|
||||
cursor: default;
|
||||
border: 1px solid #aaaaaa;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
-webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #e4e4e4;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
|
||||
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
background-image: linear-gradient(to bottom, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
}
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice
|
||||
{
|
||||
margin: 3px 5px 3px 0;
|
||||
padding: 3px 18px 3px 5px;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
|
||||
cursor: default;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus {
|
||||
background: #d4d4d4;
|
||||
}
|
||||
|
||||
.select2-search-choice-close {
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 13px;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 4px;
|
||||
|
||||
font-size: 1px;
|
||||
outline: none;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
}
|
||||
html[dir="rtl"] .select2-search-choice-close {
|
||||
right: auto;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-search-choice-close {
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container-multi .select2-search-choice-close {
|
||||
left: auto;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
|
||||
background-position: right -11px;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
|
||||
background-position: right -11px;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
.select2-container-multi.select2-container-disabled .select2-choices {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px 3px 5px;
|
||||
border: 1px solid #ddd;
|
||||
background-image: none;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
|
||||
background: none;
|
||||
}
|
||||
/* end multiselect */
|
||||
|
||||
|
||||
.select2-result-selectable .select2-match,
|
||||
.select2-result-unselectable .select2-match {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.select2-offscreen, .select2-offscreen:focus {
|
||||
clip: rect(0 0 0 0) !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
border: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
position: absolute !important;
|
||||
outline: 0 !important;
|
||||
left: 0px !important;
|
||||
top: 0px !important;
|
||||
}
|
||||
|
||||
.select2-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-measure-scrollbar {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
/* Retina-ize icons */
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) {
|
||||
.select2-search input,
|
||||
.select2-search-choice-close,
|
||||
.select2-container .select2-choice abbr,
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
background-image: url('select2x2.png') !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 60px 40px !important;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
background-position: 100% -21px !important;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 613 B |
Binary file not shown.
|
Before Width: | Height: | Size: 845 B |
22
django_select2/static/django_select2/django_select2.js
Normal file
22
django_select2/static/django_select2/django_select2.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
$(function () {
|
||||
$('.django-select2').each(function () {
|
||||
var field_id = $(this).data('field_id');
|
||||
$(this).select2({
|
||||
ajax: {
|
||||
data: function (params) {
|
||||
return {
|
||||
term: params.term,
|
||||
page: params.page,
|
||||
field_id: field_id
|
||||
};
|
||||
},
|
||||
processResults: function (data, page) {
|
||||
return {
|
||||
results: data.results
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1,253 +0,0 @@
|
|||
if (!window['django_select2']) {
|
||||
// This JS file can be included multiple times. So, as not to overwrite previous states, we run this only once.
|
||||
|
||||
window.django_select2 = {
|
||||
MULTISEPARATOR: String.fromCharCode(31), // We use this unprintable char as separator,
|
||||
// since this can't be entered by user.
|
||||
get_url_params: function (term, page, context) {
|
||||
var field_id = jQuery(this).data('field_id'),
|
||||
res = {
|
||||
'term': term,
|
||||
'page': page,
|
||||
'context': context
|
||||
};
|
||||
if (field_id) {
|
||||
res['field_id'] = field_id;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
process_results: function (data, page, context) {
|
||||
var results;
|
||||
if (data.err && data.err.toLowerCase() === 'nil') {
|
||||
results = {
|
||||
'results': data.results
|
||||
};
|
||||
if (context) {
|
||||
results['context'] = context;
|
||||
}
|
||||
if (data.more === true || data.more === false) {
|
||||
results['more'] = data.more;
|
||||
}
|
||||
} else {
|
||||
results = {'results':[]};
|
||||
}
|
||||
if (results.results) {
|
||||
jQuery(this).data('results', results.results);
|
||||
} else {
|
||||
jQuery(this).removeData('results');
|
||||
}
|
||||
return results;
|
||||
},
|
||||
onValChange: function () {
|
||||
django_select2.updateText(jQuery(this));
|
||||
},
|
||||
prepareValText: function (vals, txts, isMultiple) {
|
||||
var data = []
|
||||
jQuery(vals).each(function (index) {
|
||||
data.push({id: this, text: txts[index]});
|
||||
});
|
||||
if (isMultiple) {
|
||||
return data;
|
||||
} else {
|
||||
if (data.length > 0) {
|
||||
return data[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
updateText: function ($e) {
|
||||
var val = $e.select2('val'), data = $e.select2('data'), txt = $e.txt(), isMultiple = !!$e.attr('multiple'),
|
||||
diff;
|
||||
|
||||
if (val || val === 0) { // Means value is set. A numerical 0 is also a valid value.
|
||||
if (isMultiple) {
|
||||
if (val.length !== txt.length) {
|
||||
txt = [];
|
||||
jQuery(val).each(function (idx) {
|
||||
var i, value = this, id;
|
||||
|
||||
for (i in data) {
|
||||
id = data [i].id;
|
||||
if (id instanceof String) {
|
||||
id = id.valueOf();
|
||||
}
|
||||
if (id == value) {
|
||||
txt.push(data[i].text);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
txt = data.text;
|
||||
}
|
||||
$e.txt(txt);
|
||||
} else {
|
||||
$e.txt('');
|
||||
}
|
||||
},
|
||||
getValText: function ($e) {
|
||||
var val = $e.select2('val'), res = $e.data('results'), txt = $e.txt(), isMultiple = !!$e.attr('multiple'),
|
||||
f, id = $e.attr('id');
|
||||
if (val || val === 0) { // Means value is set. A numerical 0 is also a valid value.
|
||||
|
||||
if (!isMultiple) {
|
||||
val = [val];
|
||||
if (txt || txt === 0) {
|
||||
txt = [txt];
|
||||
}
|
||||
}
|
||||
|
||||
if (txt === 0 || (txt && val.length === txt.length)) {
|
||||
return [val, txt];
|
||||
}
|
||||
|
||||
f = $e.data('userGetValText');
|
||||
if (f) {
|
||||
txt = f($e, val, isMultiple);
|
||||
if (txt || txt === 0) {
|
||||
return [val, txt];
|
||||
}
|
||||
}
|
||||
|
||||
if (res) {
|
||||
txt = [];
|
||||
jQuery(val).each(function (idx) {
|
||||
var i, value = this;
|
||||
|
||||
for (i in res) {
|
||||
if (res[i].id == value) {
|
||||
val[idx] = res[i].id; // To set it to correct data type.
|
||||
txt.push(res[i].text);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (txt || txt === 0) {
|
||||
return [val, txt];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onInit: function (e, callback) {
|
||||
e = jQuery(e);
|
||||
var id = e.attr('id'), data = null, val = e.select2('val');
|
||||
|
||||
if (!val && val !== 0) {
|
||||
val = e.data('initVal');
|
||||
}
|
||||
|
||||
if (val || val === 0) {
|
||||
// Value is set so need to get the text.
|
||||
data = django_select2.getValText(e);
|
||||
if (data && data[0]) {
|
||||
data = django_select2.prepareValText(data[0], data[1], !!e.attr('multiple'));
|
||||
}
|
||||
}
|
||||
if (!data) {
|
||||
e.val(null); // Nulling out set value so as not to confuse users.
|
||||
}
|
||||
callback(data); // Change for 2.3.x
|
||||
django_select2.updateText(e);
|
||||
},
|
||||
createSearchChoice: function(term, data) {
|
||||
if (!data || jQuery(data).filter(function () {
|
||||
return this.text.localeCompare(term) === 0;
|
||||
}).length === 0) {
|
||||
return {
|
||||
id: term,
|
||||
text: term
|
||||
};
|
||||
}
|
||||
},
|
||||
onMultipleHiddenChange: function () {
|
||||
var $e = jQuery(this), valContainer = $e.data('valContainer'), name = $e.data('name'), vals = $e.val();
|
||||
valContainer.empty();
|
||||
if (vals) {
|
||||
vals = vals.split(django_select2.MULTISEPARATOR);
|
||||
jQuery(vals).each(function () {
|
||||
var inp = jQuery('<input type="hidden">').appendTo(valContainer);
|
||||
inp.attr('name', name);
|
||||
inp.val(this);
|
||||
});
|
||||
}
|
||||
},
|
||||
initMultipleHidden: function ($e) {
|
||||
var valContainer;
|
||||
|
||||
$e.data('name', $e.attr('name'));
|
||||
$e.attr('name', '');
|
||||
|
||||
valContainer = jQuery('<div>').insertAfter($e).css({'display': 'none'});
|
||||
$e.data('valContainer', valContainer);
|
||||
|
||||
$e.change(django_select2.onMultipleHiddenChange);
|
||||
if ($e.val()) {
|
||||
$e.change();
|
||||
}
|
||||
},
|
||||
convertArrToStr: function (arr) {
|
||||
return arr.join(django_select2.MULTISEPARATOR);
|
||||
},
|
||||
runInContextHelper: function (f, id) {
|
||||
return function () {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
return f.apply(jQuery('#' + id).get(0), args);
|
||||
}
|
||||
},
|
||||
logErr: function () {
|
||||
if (console && console.error) {
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
console.error.apply(console, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(function (isDebug) { // Only used for debugging.
|
||||
if (isDebug) {
|
||||
for (var i in django_select2) {
|
||||
var f = django_select2[i];
|
||||
if (typeof(f) == "function") {
|
||||
django_select2[i] = (function (i, f) {
|
||||
return function () {
|
||||
console.log('Function ' + i + ' called for object: ', this);
|
||||
return f.apply(this, arguments);
|
||||
};
|
||||
}(i, f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}(false));
|
||||
|
||||
(function( $ ){
|
||||
// This sets or gets the text lables for an element. It merely takes care returing array or single
|
||||
// value, based on if element is multiple type.
|
||||
$.fn.txt = function(val) {
|
||||
if (typeof(val) !== 'undefined') {
|
||||
if (val) {
|
||||
if (val instanceof Array) {
|
||||
if (this.attr('multiple')) {
|
||||
val = django_select2.convertArrToStr(val);
|
||||
} else {
|
||||
val = val[0]
|
||||
}
|
||||
}
|
||||
this.attr('txt', val);
|
||||
} else {
|
||||
this.removeAttr('txt');
|
||||
}
|
||||
return this;
|
||||
} else {
|
||||
val = this.attr('txt');
|
||||
if (this.attr('multiple')) {
|
||||
if (val) {
|
||||
val = val.split(django_select2.MULTISEPARATOR);
|
||||
} else {
|
||||
val = [];
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
};
|
||||
})( jQuery );
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
if(!window.django_select2){window.django_select2={MULTISEPARATOR:String.fromCharCode(31),get_url_params:function(c,e,b){var d=jQuery(this).data("field_id"),a={term:c,page:e,context:b};if(d){a.field_id=d}return a},process_results:function(d,c,b){var a;if(d.err&&d.err.toLowerCase()==="nil"){a={results:d.results};if(b){a.context=b}if(d.more===true||d.more===false){a.more=d.more}}else{a={results:[]}}if(a.results){jQuery(this).data("results",a.results)}else{jQuery(this).removeData("results")}return a},onValChange:function(){django_select2.updateText(jQuery(this))},prepareValText:function(d,a,c){var b=[];jQuery(d).each(function(e){b.push({id:this,text:a[e]})});if(c){return b}else{if(b.length>0){return b[0]}else{return null}}},updateText:function(b){var f=b.select2("val"),d=b.select2("data"),a=b.txt(),c=!!b.attr("multiple"),e;if(f||f===0){if(c){if(f.length!==a.length){a=[];jQuery(f).each(function(g){var h,j=this,k;for(h in d){k=d[h].id;if(k instanceof String){k=k.valueOf()}if(k==j){a.push(d[h].text)}}})}}else{a=d.text}b.txt(a)}else{b.txt("")}},getValText:function(b){var g=b.select2("val"),c=b.data("results"),a=b.txt(),e=!!b.attr("multiple"),d,h=b.attr("id");if(g||g===0){if(!e){g=[g];if(a||a===0){a=[a]}}if(a===0||(a&&g.length===a.length)){return[g,a]}d=b.data("userGetValText");if(d){a=d(b,g,e);if(a||a===0){return[g,a]}}if(c){a=[];jQuery(g).each(function(f){var j,k=this;for(j in c){if(c[j].id==k){g[f]=c[j].id;a.push(c[j].text)}}});if(a||a===0){return[g,a]}}}return null},onInit:function(b,f){b=jQuery(b);var d=b.attr("id"),a=null,c=b.select2("val");if(!c&&c!==0){c=b.data("initVal")}if(c||c===0){a=django_select2.getValText(b);if(a&&a[0]){a=django_select2.prepareValText(a[0],a[1],!!b.attr("multiple"))}}if(!a){b.val(null)}f(a);django_select2.updateText(b)},createSearchChoice:function(a,b){if(!b||jQuery(b).filter(function(){return this.text.localeCompare(a)===0}).length===0){return{id:a,text:a}}},onMultipleHiddenChange:function(){var b=jQuery(this),d=b.data("valContainer"),a=b.data("name"),c=b.val();d.empty();if(c){c=c.split(django_select2.MULTISEPARATOR);jQuery(c).each(function(){var e=jQuery('<input type="hidden">').appendTo(d);e.attr("name",a);e.val(this)})}},initMultipleHidden:function(a){var b;a.data("name",a.attr("name"));a.attr("name","");b=jQuery("<div>").insertAfter(a).css({display:"none"});a.data("valContainer",b);a.change(django_select2.onMultipleHiddenChange);if(a.val()){a.change()}},convertArrToStr:function(a){return a.join(django_select2.MULTISEPARATOR)},runInContextHelper:function(a,b){return function(){var c=Array.prototype.slice.call(arguments,0);return a.apply(jQuery("#"+b).get(0),c)}},logErr:function(){if(console&&console.error){args=Array.prototype.slice.call(arguments);console.error.apply(console,args)}}};(function(b){if(b){for(var a in django_select2){var c=django_select2[a];if(typeof(c)=="function"){django_select2[a]=(function(d,e){return function(){console.log("Function "+d+" called for object: ",this);return e.apply(this,arguments)}}(a,c))}}}}(false));(function(a){a.fn.txt=function(b){if(typeof(b)!=="undefined"){if(b){if(b instanceof Array){if(this.attr("multiple")){b=django_select2.convertArrToStr(b)}else{b=b[0]}}this.attr("txt",b)}else{this.removeAttr("txt")}return this}else{b=this.attr("txt");if(this.attr("multiple")){if(b){b=b.split(django_select2.MULTISEPARATOR)}else{b=[]}}return b}}})(jQuery)};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -8,5 +8,5 @@ from .views import AutoResponseView
|
|||
urlpatterns = patterns(
|
||||
"",
|
||||
url(r"^fields/auto.json$",
|
||||
AutoResponseView.as_view(), name="django_select2_central_json"),
|
||||
AutoResponseView.as_view(), name="django_select2-json"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ class AutoResponseView(BaseListView):
|
|||
}
|
||||
for obj in context['object_list']
|
||||
],
|
||||
'err': NO_ERR_RESP,
|
||||
'more': context['is_paginated']
|
||||
})
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from selenium.common.exceptions import NoSuchElementException
|
|||
from six import text_type
|
||||
|
||||
from django_select2.cache import cache
|
||||
from django_select2.forms import AutoHeavySelect2Widget
|
||||
from django_select2.forms import ModelSelect2Widget
|
||||
from tests.testapp.models import Genre
|
||||
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ class TestHeavySelect2Mixin(object):
|
|||
mommy.make(Genre, 100)
|
||||
|
||||
def test_get_queryset(self):
|
||||
widget = AutoHeavySelect2Widget()
|
||||
widget = ModelSelect2Widget()
|
||||
with pytest.raises(NotImplementedError):
|
||||
widget.get_queryset()
|
||||
widget.model = Genre
|
||||
|
|
@ -92,7 +92,7 @@ class TestHeavySelect2Mixin(object):
|
|||
assert isinstance(widget.get_queryset(), QuerySet)
|
||||
|
||||
def test_get_search_fields(self):
|
||||
widget = AutoHeavySelect2Widget()
|
||||
widget = ModelSelect2Widget()
|
||||
with pytest.raises(NotImplementedError):
|
||||
widget.get_search_fields()
|
||||
|
||||
|
|
@ -101,32 +101,32 @@ class TestHeavySelect2Mixin(object):
|
|||
assert all(isinstance(x, text_type) for x in widget.get_search_fields())
|
||||
|
||||
def test_model_kwarg(self):
|
||||
widget = AutoHeavySelect2Widget(model=Genre, search_fields=['title__icontains'])
|
||||
widget = ModelSelect2Widget(model=Genre, search_fields=['title__icontains'])
|
||||
genre = Genre.objects.last()
|
||||
result = widget.filter_queryset(genre.title)
|
||||
assert result.exists()
|
||||
|
||||
def test_queryset_kwarg(self):
|
||||
widget = AutoHeavySelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
|
||||
widget = ModelSelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
|
||||
genre = Genre.objects.last()
|
||||
result = widget.filter_queryset(genre.title)
|
||||
assert result.exists()
|
||||
|
||||
def test_widget_id(self):
|
||||
widget = AutoHeavySelect2Widget()
|
||||
widget = ModelSelect2Widget()
|
||||
widget.render('name', 'value')
|
||||
assert widget.widget_id
|
||||
assert signing.loads(widget.widget_id) == id(widget)
|
||||
|
||||
def test_render(self):
|
||||
widget = AutoHeavySelect2Widget()
|
||||
widget = ModelSelect2Widget()
|
||||
widget.render('name', 'value')
|
||||
cached_widget = cache.get(widget._get_cache_key())
|
||||
assert isinstance(cached_widget, AutoHeavySelect2Widget)
|
||||
assert isinstance(cached_widget, ModelSelect2Widget)
|
||||
assert cached_widget.widget_id == widget.widget_id
|
||||
|
||||
def test_ajax_view_registration(self, client):
|
||||
widget = AutoHeavySelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
|
||||
widget = ModelSelect2Widget(queryset=Genre.objects, search_fields=['title__icontains'])
|
||||
widget.render('name', 'value')
|
||||
url = reverse('django_select2_central_json')
|
||||
genre = Genre.objects.last()
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from django import forms
|
|||
|
||||
from django_select2.forms import (
|
||||
HeavySelect2MultipleWidget, HeavySelect2Widget, Select2MultipleWidget,
|
||||
Select2Widget
|
||||
)
|
||||
Select2Widget,
|
||||
ModelSelect2MultipleWidget, ModelSelect2Widget)
|
||||
from tests.testapp import models
|
||||
|
||||
|
||||
|
|
@ -38,9 +38,9 @@ class ArtistModelForm(forms.ModelForm):
|
|||
|
||||
class ArtistForm(forms.Form):
|
||||
title = forms.CharField(max_length=50)
|
||||
genres = forms.ModelMultipleChoiceField(widget=HeavySelect2MultipleWidget(
|
||||
genres = forms.ModelMultipleChoiceField(widget=ModelSelect2MultipleWidget(
|
||||
queryset=models.Genre.objects.all(),
|
||||
search_fields=['title'],
|
||||
search_fields=['title__icontains'],
|
||||
), queryset=models.Genre.objects.all())
|
||||
|
||||
|
||||
|
|
@ -55,9 +55,9 @@ class AlbumModelForm(forms.ModelForm):
|
|||
|
||||
class AlbumForm(forms.Form):
|
||||
title = forms.CharField(max_length=255)
|
||||
artist = forms.ModelChoiceField(widget=HeavySelect2Widget(
|
||||
artist = forms.ModelChoiceField(widget=ModelSelect2Widget(
|
||||
model=models.Artist,
|
||||
search_fields=['title']
|
||||
search_fields=['title__icontains']
|
||||
), queryset=models.Artist.objects.all())
|
||||
|
||||
|
||||
|
|
|
|||
10
tests/testapp/manage.py
Executable file
10
tests/testapp/manage.py
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import os.path
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
DEBUG = True
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:',
|
||||
'NAME': 'testdb.sqlite',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,4 +49,4 @@ if os.environ.get('TRAVIS'):
|
|||
}
|
||||
}
|
||||
|
||||
AUTO_RENDER_SELECT2_STATICS = False
|
||||
SELECT2_BOOTSTRAP = True
|
||||
4
tests/testapp/static/jquery-1.7.2.min.js
vendored
4
tests/testapp/static/jquery-1.7.2.min.js
vendored
File diff suppressed because one or more lines are too long
4
tests/testapp/static/jquery-2.1.4.min.js
vendored
Normal file
4
tests/testapp/static/jquery-2.1.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -2,18 +2,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="{% static 'jquery-1.7.2.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
window.onerror = function (msg) {
|
||||
$("body").attr("JSError", msg);
|
||||
}
|
||||
</script>
|
||||
{{ form.media }}
|
||||
{{ form.media.css }}
|
||||
<style type="text/css">select{width:200px}</style>
|
||||
</head>
|
||||
<body>
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit Form"/>
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit Form"/>
|
||||
</form>
|
||||
<script src="{% static 'jquery-2.1.4.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
window.onerror = function (msg) {
|
||||
$("body").attr("JSError", msg);
|
||||
}
|
||||
</script>
|
||||
{{ form.media.js }}
|
||||
</body>
|
||||
Loading…
Reference in a new issue