diff --git a/.gitignore b/.gitignore index d9b2f10..d65aa34 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ docs/_build # System *.DS_Store -*.idea \ No newline at end of file +*.idea +*.fuse* diff --git a/AUTHORS.rst b/AUTHORS.rst index 10a6a7e..709d022 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,4 +10,4 @@ Development Lead Contributors ------------ -None yet. Why not be the first? \ No newline at end of file +* Nathan Shafer diff --git a/README.md b/README.md index d715832..bae8f90 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,22 @@ comma to separate multiple tags in same form field, and resulting in duplicate t And you probably want auto-complete/auto-suggest feature when user types some characters in tag field. Thanks to selectize.js, we got that covered :) +Features +-------- +* Supports Django 1.8.x and Django 1.9.x +* Supports >=Python2.7 and >=Python3.4 +* Simple installation, selectize.js 0.12.1 included +* Will use jQuery install included in Django admin, no installation of jQuery needed +* Will use custom jQuery object if it is installed, though +* Themed to match new Django 1.9 flat theme +* Exposes many selectize.js configuration options to your settings.py +* Supports multiple TaggableManagers in a single model + + Quickstart ---------- -Install taggit-selectize:: +Install taggit-selectize: pip install taggit-selectize @@ -24,39 +36,97 @@ Install taggit-selectize:: Usage ----- -1. Put `taggit-selectize` in settings: +1. Put `taggit_selectize` in settings: ``` INSTALLED_APPS = ( 'django.contrib.admin', ... ... + 'taggit', 'taggit_selectize', ) ``` 2. Update urls.py. ``` -urlpatterns = patterns('', +urlpatterns = [ ... url(r'^taggit/', include('taggit_selectize.urls')), url(r'^admin/', include(admin.site.urls)), ... -) +] ``` -3. Create `admin` dir inside templates folder. Create a template `base_site.html` and [copy paste this](https://github.com/chhantyal/taggit-selectize/blob/master/example_app/templates/admin/base_site.html). -This has to override from project template dirs otherwise django will still load default `base_site.html` from `django.contrib.admin` app. -Also, filesystem loader must be before app dir loader in settings like: +3. Use the `TaggableManager` from taggit_selectize (instead of taggit) in your models. +``` +from taggit_selectize.managers import TaggableManager +class MyModel(models.Model): + tags = TaggableManager() ``` -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) + + +Configuration +------------- +In your settings.py (these are defaults): + +``` +TAGGIT_SELECTIZE = { + 'MINIMUM_QUERY_LENGTH': 2, + 'RECOMMENDATION_LIMIT': 10, + 'CSS_FILENAMES': ("taggit_selectize/css/selectize.django.css",), + 'JS_FILENAMES': ("taggit_selectize/js/selectize.js",), + 'DIACRITICS': True, + 'CREATE': True, + 'PERSIST': True, + 'OPEN_ON_FOCUS': True, + 'HIDE_SELECTED': True, + 'CLOSE_AFTER_SELECT': False, + 'LOAD_THROTTLE': 300, + 'PRELOAD': False, + 'ADD_PRECEDENCE': False, + 'SELECT_ON_TAB': False, + 'REMOVE_BUTTON': False, + 'RESTORE_ON_BACKSPACE': False, + 'DRAG_DROP': False, +} ``` -You can also use outside of admin in same way. + +### MINIMUM_QUERY_LENGTH + +The minimum number of characters the user needs to type to cause an AJAX request to hit the server for autocompletion. Default: 2 + +### RECOMMENDATION_LIMIT + +The maximum number of results to return to the user for recommendation. Default: 10 + +### CSS_FILENAMES + +A tuple of CSS files to include on any page that has the taggit-selectize widget on it. Default: `("taggit_selectize/css/selectize.django.css",)` + +### JS_FILENAMES + +A tuple of JS files to include on any page that has the taggit-selectize widget on it. Default: `("taggit_selectize/js/selectize.js",)` + +### DIACRITICS, CREATE, PERSIST, OPEN_ON_FOCUS, HIDE_SELECTED, CLOSE_AFTER_SELECT, LOAD_THROTTLE, PRELOAD, ADD_PRECEDENCE, SELECT_ON_TAB + +Options that are passed directly to selectize.js. +Please see [the selectize.js documentation](https://github.com/selectize/selectize.js/blob/master/docs/usage.md) for explanation + +### REMOVE_BUTTON + +Adds a remove button to each tag item by including the 'remove_button' plugin. + +### RESTORE_ON_BACKSPACE + +Adds the 'restore_on_backspace' plugin to selectize.js. + +### DRAG_DROP + +Adds the 'drag_drop' plugin to selectize.js. WARNING: This requires JQuery UI (Sortable) to be installed. If it's not, then +selectize.js will throw an error in the console and refuse to run. Demo app diff --git a/example_app/__init__.py b/example_app/__init__.py deleted file mode 100644 index 983691f..0000000 --- a/example_app/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'parvachhantyal' diff --git a/example_app/example_app/app/__init__.py b/example_app/blog/__init__.py similarity index 100% rename from example_app/example_app/app/__init__.py rename to example_app/blog/__init__.py diff --git a/example_app/example_app/app/admin.py b/example_app/blog/admin.py similarity index 77% rename from example_app/example_app/app/admin.py rename to example_app/blog/admin.py index 67d1f07..bf3ec72 100644 --- a/example_app/example_app/app/admin.py +++ b/example_app/blog/admin.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from django.contrib import admin from .models import Blog @@ -11,4 +9,4 @@ class BlogAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("title",)} -admin.site.register(Blog, BlogAdmin) \ No newline at end of file +admin.site.register(Blog, BlogAdmin) diff --git a/example_app/blog/apps.py b/example_app/blog/apps.py new file mode 100644 index 0000000..7930587 --- /dev/null +++ b/example_app/blog/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + name = 'blog' diff --git a/example_app/blog/migrations/0001_initial.py b/example_app/blog/migrations/0001_initial.py new file mode 100644 index 0000000..6ffa04c --- /dev/null +++ b/example_app/blog/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import taggit_selectize.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('taggit', '0002_auto_20150616_2121'), + ] + + operations = [ + migrations.CreateModel( + name='Blog', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('title', models.CharField(max_length=255)), + ('slug', models.SlugField(unique=True, max_length=255)), + ('body', models.TextField()), + ('date', models.DateTimeField(auto_now_add=True)), + ('tags', taggit_selectize.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', help_text='A comma-separated list of tags.', verbose_name='Tags')), + ], + ), + ] diff --git a/example_app/example_app/app/migrations/__init__.py b/example_app/blog/migrations/__init__.py similarity index 100% rename from example_app/example_app/app/migrations/__init__.py rename to example_app/blog/migrations/__init__.py diff --git a/example_app/example_app/app/models.py b/example_app/blog/models.py similarity index 80% rename from example_app/example_app/app/models.py rename to example_app/blog/models.py index 8b1c6d2..1b02966 100644 --- a/example_app/example_app/app/models.py +++ b/example_app/blog/models.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- - from django.db import models from django.utils.encoding import python_2_unicode_compatible -from taggit.managers import TaggableManager +from taggit_selectize.managers import TaggableManager @python_2_unicode_compatible @@ -15,4 +13,4 @@ class Blog(models.Model): tags = TaggableManager() def __str__(self): - return self.title \ No newline at end of file + return self.title diff --git a/example_app/example_app/app/tests.py b/example_app/blog/tests.py similarity index 100% rename from example_app/example_app/app/tests.py rename to example_app/blog/tests.py diff --git a/example_app/example_app/app/views.py b/example_app/blog/views.py similarity index 71% rename from example_app/example_app/app/views.py rename to example_app/blog/views.py index 050a3d8..91ea44a 100644 --- a/example_app/example_app/app/views.py +++ b/example_app/blog/views.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from django.shortcuts import render # Create your views here. diff --git a/example_app/example_app/settings.py b/example_app/example_app/settings.py index c2002de..0527c76 100644 --- a/example_app/example_app/settings.py +++ b/example_app/example_app/settings.py @@ -1,30 +1,31 @@ """ Django settings for example_app project. +Generated by 'django-admin startproject' using Django 1.8.8. + For more information on this file, see -https://docs.djangoproject.com/en/1.7/topics/settings/ +https://docs.djangoproject.com/en/1.8/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.7/ref/settings/ +https://docs.djangoproject.com/en/1.8/ref/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '^_jy=02+8yaj)c2ei0ro+d9*v9qc*d1q-m725_=yjy=uev7$el' +SECRET_KEY = '9o*122uaw#7@*(o9)6b%n=gdlb9ec%+%4!@0-gr8#5h7fz15a4' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -TEMPLATE_DEBUG = True - -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = [] # Application definition @@ -39,7 +40,8 @@ 'taggit', 'taggit_selectize', - 'example_app.app', + + 'blog', ) MIDDLEWARE_CLASSES = ( @@ -50,15 +52,32 @@ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', ) ROOT_URLCONF = 'example_app.urls' +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + WSGI_APPLICATION = 'example_app.wsgi.application' # Database -# https://docs.djangoproject.com/en/1.7/ref/settings/#databases +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases DATABASES = { 'default': { @@ -67,8 +86,9 @@ } } + # Internationalization -# https://docs.djangoproject.com/en/1.7/topics/i18n/ +# https://docs.djangoproject.com/en/1.8/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -80,29 +100,22 @@ USE_TZ = True -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - os.path.join(BASE_DIR, "templates"), -) - -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - #'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - - -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - #'django.template.loaders.eggs.Loader', -) # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.7/howto/static-files/ +# https://docs.djangoproject.com/en/1.8/howto/static-files/ STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'static') \ No newline at end of file + +# Taggit-Selectize settings +TAGGIT_SELECTIZE = { + 'MINIMUM_QUERY_LENGTH': 1, + 'RECOMMENDATION_LIMIT': 15, + 'PERSIST': False, + 'OPEN_ON_FOCUS': False, + 'HIDE_SELECTED': True, + 'CLOSE_AFTER_SELECT': True, + 'LOAD_THROTTLE': 100, + 'SELECT_ON_TAB': True, + 'REMOVE_BUTTON': True, +} diff --git a/example_app/example_app/urls.py b/example_app/example_app/urls.py index c8fe527..c750871 100644 --- a/example_app/example_app/urls.py +++ b/example_app/example_app/urls.py @@ -1,10 +1,21 @@ -from django.conf.urls import patterns, include, url -from django.contrib import admin +"""example_app URL Configuration -urlpatterns = patterns('', - # Examples: - # url(r'^$', 'example_app.views.home', name='home'), +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.8/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import include, url +from django.contrib import admin +urlpatterns = [ url(r'^taggit/', include('taggit_selectize.urls')), url(r'^admin/', include(admin.site.urls)), -) +] diff --git a/example_app/example_app/wsgi.py b/example_app/example_app/wsgi.py index 11c596e..cf677fa 100644 --- a/example_app/example_app/wsgi.py +++ b/example_app/example_app/wsgi.py @@ -4,11 +4,13 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ """ import os -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_app.settings") from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_app.settings") + application = get_wsgi_application() diff --git a/example_app/templates/admin/base_site.html b/example_app/templates/admin/base_site.html deleted file mode 100644 index 0f1e7ab..0000000 --- a/example_app/templates/admin/base_site.html +++ /dev/null @@ -1,65 +0,0 @@ -{% extends "admin/base.html" %} -{% load staticfiles %} - -{% block extrastyle %} - -{% endblock %} - -{% block extrahead %} - - - -{% endblock %} - -{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} - -{% block branding %} -

{{ site_header|default:_('Django Admin') }}

-{% endblock %} - -{% block nav-global %}{% endblock %} - -{% block footer %} - - - -{% endblock %} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2be41ab..aeadd1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -django>=1.5.1 -django-taggit==0.12.2 \ No newline at end of file +django>=1.8.8 +django-taggit>=0.18.0 diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index d229b69..d424afb --- a/setup.py +++ b/setup.py @@ -48,4 +48,4 @@ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', ], -) \ No newline at end of file +) diff --git a/taggit_selectize/conf.py b/taggit_selectize/conf.py index 1f477a9..974adb9 100644 --- a/taggit_selectize/conf.py +++ b/taggit_selectize/conf.py @@ -1,7 +1,25 @@ -# -*- coding: utf-8 -*- - from django.conf import settings -settings.TAGGIT_SELECTIZE_RECOMMENDATION_LIMIT = getattr(settings, - 'TAGGIT_SELECTIZE_RECOMMENDATION_LIMIT', 10 -) \ No newline at end of file +default_settings = { + 'MINIMUM_QUERY_LENGTH': 2, + 'RECOMMENDATION_LIMIT': 10, + 'CSS_FILENAMES': ("taggit_selectize/css/selectize.django.css",), + 'JS_FILENAMES': ("taggit_selectize/js/selectize.js",), + 'DIACRITICS': True, + 'CREATE': True, + 'PERSIST': True, + 'OPEN_ON_FOCUS': True, + 'HIDE_SELECTED': True, + 'CLOSE_AFTER_SELECT': False, + 'LOAD_THROTTLE': 300, + 'PRELOAD': False, + 'ADD_PRECEDENCE': False, + 'SELECT_ON_TAB': False, + 'REMOVE_BUTTON': False, + 'RESTORE_ON_BACKSPACE': False, + 'DRAG_DROP': False, +} + +user_settings = getattr(settings, 'TAGGIT_SELECTIZE', {}) +default_settings.update(user_settings) +settings.TAGGIT_SELECTIZE = default_settings diff --git a/taggit_selectize/managers.py b/taggit_selectize/managers.py new file mode 100644 index 0000000..de2ba9a --- /dev/null +++ b/taggit_selectize/managers.py @@ -0,0 +1,16 @@ +from django.utils.text import capfirst +from taggit.forms import TagField +from taggit.managers import TaggableManager as BaseTaggableManager +from .widgets import TagSelectize + + +class TaggableManager(BaseTaggableManager): + def formfield(self, form_class=TagField, **kwargs): + defaults = { + "label": capfirst(self.verbose_name), + "help_text": None, + "required": not self.blank, + "widget": TagSelectize, + } + defaults.update(kwargs) + return form_class(**defaults) diff --git a/taggit_selectize/models.py b/taggit_selectize/models.py deleted file mode 100644 index 7c68785..0000000 --- a/taggit_selectize/models.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/taggit_selectize/static/img/.gitignore b/taggit_selectize/static/img/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/taggit_selectize/static/css/selectize.default.css b/taggit_selectize/static/taggit_selectize/css/selectize.django.css old mode 100755 new mode 100644 similarity index 87% rename from taggit_selectize/static/css/selectize.default.css rename to taggit_selectize/static/taggit_selectize/css/selectize.django.css index c1ffd7e..94463f6 --- a/taggit_selectize/static/css/selectize.default.css +++ b/taggit_selectize/static/taggit_selectize/css/selectize.django.css @@ -1,5 +1,5 @@ /** - * selectize.default.css (v0.12.0) - Default Theme + * selectize.default.css (v0.12.1) - Default Theme * Copyright (c) 2013–2015 Brian Reavis & contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this @@ -314,6 +314,24 @@ opacity: 0.5; background-color: #fafafa; } + +/* Django style, based on default, goal is to fit Django 1.9 flat theme */ +.colM .aligned .selectize-input { + /* fix alignment in django admin */ + width: 625px; +} +.selectize-control.plugin-remove_button [data-value] { + padding-right: 28px !important; +} +.selectize-control.plugin-remove_button [data-value] .remove { + padding: 5px 0 0 0; + border-left: 1px solid #2c5e83; + font-size: 16px; + width: 19px; +} +.selectize-control.plugin-remove_button [data-value].active .remove { + border-left-color: #2d698f; +} .selectize-control.multi .selectize-input.has-items { padding-left: 5px; padding-right: 5px; @@ -333,30 +351,16 @@ 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); + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + border: none; + background-color: #205067; + padding: 5px 7px; } .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); + background-color: #236179; + border: none; } .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); diff --git a/taggit_selectize/static/js/selectize.js b/taggit_selectize/static/taggit_selectize/js/selectize.js old mode 100755 new mode 100644 similarity index 99% rename from taggit_selectize/static/js/selectize.js rename to taggit_selectize/static/taggit_selectize/js/selectize.js index a578bab..1d5a387 --- a/taggit_selectize/static/js/selectize.js +++ b/taggit_selectize/static/taggit_selectize/js/selectize.js @@ -1,3 +1,10 @@ +/** + * selectize.js 0.12.1 + * + * This is the Selectize distributable slightly modified to use Django's Admin's built-in jQuery plugin if no other + * jQuery is installed. (Line 641) + */ + /** * sifter.js * Copyright (c) 2013 Brian Reavis & contributors @@ -608,7 +615,7 @@ })); /** - * selectize.js (v0.12.0) + * selectize.js (v0.12.1) * Copyright (c) 2013–2015 Brian Reavis & contributors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this @@ -632,7 +639,7 @@ } else if (typeof exports === 'object') { module.exports = factory(require('jquery'), require('sifter'), require('microplugin')); } else { - root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin); + root.Selectize = factory(root.jQuery || root.django.jQuery, root.Sifter, root.MicroPlugin); } }(this, function($, Sifter, MicroPlugin) { 'use strict'; @@ -640,7 +647,7 @@ var highlight = function($element, pattern) { if (typeof pattern === 'string' && !pattern.length) return; var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern; - + var highlight = function(node) { var skip = 0; if (node.nodeType === 3) { @@ -663,12 +670,12 @@ } return skip; }; - + return $element.each(function() { highlight(this); }); }; - + var MicroEvent = function() {}; MicroEvent.prototype = { on: function(event, fct){ @@ -680,7 +687,7 @@ var n = arguments.length; if (n === 0) return delete this._events; if (n === 1) return delete this._events[event]; - + this._events = this._events || {}; if (event in this._events === false) return; this._events[event].splice(this._events[event].indexOf(fct), 1); @@ -693,7 +700,7 @@ } } }; - + /** * Mixin will delegate all MicroEvent.js function in the destination object. * @@ -707,9 +714,9 @@ destObject.prototype[props[i]] = MicroEvent.prototype[props[i]]; } }; - + var IS_MAC = /Mac/.test(navigator.userAgent); - + var KEY_A = 65; var KEY_COMMA = 188; var KEY_RETURN = 13; @@ -726,17 +733,17 @@ var KEY_CMD = IS_MAC ? 91 : 17; var KEY_CTRL = IS_MAC ? 18 : 17; var KEY_TAB = 9; - + var TAG_SELECT = 1; var TAG_INPUT = 2; - + // for now, android support in general is too spotty to support validity var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('form').validity; - + var isset = function(object) { return typeof object !== 'undefined'; }; - + /** * Converts a scalar to its best string representation * for hash keys and HTML attribute values. @@ -758,7 +765,7 @@ if (typeof value === 'boolean') return value ? '1' : '0'; return value + ''; }; - + /** * Escapes a string for use within HTML. * @@ -772,7 +779,7 @@ .replace(/>/g, '>') .replace(/"/g, '"'); }; - + /** * Escapes "$" characters in replacement strings. * @@ -782,9 +789,9 @@ var escape_replace = function(str) { return (str + '').replace(/\$/g, '$$$$'); }; - + var hook = {}; - + /** * Wraps `method` on `self` so that `fn` * is invoked before the original method. @@ -800,7 +807,7 @@ return original.apply(self, arguments); }; }; - + /** * Wraps `method` on `self` so that `fn` * is invoked after the original method. @@ -817,7 +824,7 @@ return result; }; }; - + /** * Wraps `fn` so that it can only be invoked once. * @@ -832,7 +839,7 @@ fn.apply(this, arguments); }; }; - + /** * Wraps `fn` so that it can only be called once * every `delay` milliseconds (invoked on the falling edge). @@ -852,7 +859,7 @@ }, delay); }; }; - + /** * Debounce all fired events types listed in `types` * while executing the provided `fn`. @@ -865,7 +872,7 @@ var type; var trigger = self.trigger; var event_args = {}; - + // override trigger method self.trigger = function() { var type = arguments[0]; @@ -875,11 +882,11 @@ return trigger.apply(self, arguments); } }; - + // invoke provided function fn.apply(self, []); self.trigger = trigger; - + // trigger queued events for (type in event_args) { if (event_args.hasOwnProperty(type)) { @@ -887,7 +894,7 @@ } } }; - + /** * A workaround for http://bugs.jquery.com/ticket/6696 * @@ -906,7 +913,7 @@ return fn.apply(this, [e]); }); }; - + /** * Determines the current selection within a text input control. * Returns an object containing: @@ -931,7 +938,7 @@ } return result; }; - + /** * Copies CSS properties from one element to another. * @@ -950,7 +957,7 @@ } $to.css(styles); }; - + /** * Measures the width of a string within a * parent element (in pixels). @@ -963,7 +970,7 @@ if (!str) { return 0; } - + var $test = $('').css({ position: 'absolute', top: -99999, @@ -972,7 +979,7 @@ padding: 0, whiteSpace: 'pre' }).text(str).appendTo('body'); - + transferStyles($parent, $test, [ 'letterSpacing', 'fontSize', @@ -980,13 +987,13 @@ 'fontWeight', 'textTransform' ]); - + var width = $test.width(); $test.remove(); - + return width; }; - + /** * Sets up an input to grow horizontally as the user * types. If the value is changed manually, you can @@ -998,16 +1005,16 @@ */ var autoGrow = function($input) { var currentWidth = null; - + var update = function(e, options) { var value, keyCode, printable, placeholder, width; var shift, character, selection; e = e || window.event || {}; options = options || {}; - + if (e.metaKey || e.altKey) return; if (!options.force && $input.data('grow') === false) return; - + value = $input.val(); if (e.type && e.type.toLowerCase() === 'keydown') { keyCode = e.keyCode; @@ -1017,7 +1024,7 @@ (keyCode >= 48 && keyCode <= 57) || // 0-9 keyCode === 32 // space ); - + if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) { selection = getSelection($input[0]); if (selection.length) { @@ -1035,12 +1042,12 @@ value += character; } } - + placeholder = $input.attr('placeholder'); if (!value && placeholder) { value = placeholder; } - + width = measureString(value, $input) + 4; if (width !== currentWidth) { currentWidth = width; @@ -1048,21 +1055,21 @@ $input.triggerHandler('resize'); } }; - + $input.on('keydown keyup update blur', update); update(); }; - + var Selectize = function($input, settings) { var key, i, n, dir, input, self = this; input = $input[0]; input.selectize = self; - + // detect rtl environment var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null); dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction; dir = dir || $input.parents('[dir]:first').attr('dir') || ''; - + // setup default state $.extend(self, { order : 0, @@ -1071,7 +1078,7 @@ tabIndex : $input.attr('tabindex') || '', tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT, rtl : /rtl/i.test(dir), - + eventNS : '.selectize' + (++Selectize.count), highlightedValue : null, isOpen : false, @@ -1094,10 +1101,10 @@ caretPos : 0, loading : 0, loadedSearches : {}, - + $activeOption : null, $activeItems : [], - + optgroups : {}, options : {}, userOptions : {}, @@ -1105,10 +1112,10 @@ renderCache : {}, onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle) }); - + // search system self.sifter = new Sifter(this.options, {diacritics: settings.diacritics}); - + // build options table if (self.settings.options) { for (i = 0, n = self.settings.options.length; i < n; i++) { @@ -1116,7 +1123,7 @@ } delete self.settings.options; } - + // build optgroup table if (self.settings.optgroups) { for (i = 0, n = self.settings.optgroups.length; i < n; i++) { @@ -1124,30 +1131,30 @@ } delete self.settings.optgroups; } - + // option-dependent defaults self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi'); if (typeof self.settings.hideSelected !== 'boolean') { self.settings.hideSelected = self.settings.mode === 'multi'; } - + self.initializePlugins(self.settings.plugins); self.setupCallbacks(); self.setupTemplates(); self.setup(); }; - + // mixins // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + MicroEvent.mixin(Selectize); MicroPlugin.mixin(Selectize); - + // methods // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + $.extend(Selectize.prototype, { - + /** * Creates all elements and sets up event bindings. */ @@ -1158,7 +1165,7 @@ var $window = $(window); var $document = $(document); var $input = self.$input; - + var $wrapper; var $control; var $control_input; @@ -1170,69 +1177,69 @@ var timeout_focus; var classes; var classes_plugins; - + inputMode = self.settings.mode; classes = $input.attr('class') || ''; - + $wrapper = $('
').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode); $control = $('
').addClass(settings.inputClass).addClass('items').appendTo($wrapper); $control_input = $('').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex); $dropdown_parent = $(settings.dropdownParent || $wrapper); $dropdown = $('
').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent); $dropdown_content = $('
').addClass(settings.dropdownContentClass).appendTo($dropdown); - + if(self.settings.copyClassesToDropdown) { $dropdown.addClass(classes); } - + $wrapper.css({ width: $input[0].style.width }); - + if (self.plugins.names.length) { classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-'); $wrapper.addClass(classes_plugins); $dropdown.addClass(classes_plugins); } - + if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) { $input.attr('multiple', 'multiple'); } - + if (self.settings.placeholder) { $control_input.attr('placeholder', settings.placeholder); } - + // if splitOn was not passed in, construct it from the delimiter to allow pasting universally if (!self.settings.splitOn && self.settings.delimiter) { var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*'); } - + if ($input.attr('autocorrect')) { $control_input.attr('autocorrect', $input.attr('autocorrect')); } - + if ($input.attr('autocapitalize')) { $control_input.attr('autocapitalize', $input.attr('autocapitalize')); } - + self.$wrapper = $wrapper; self.$control = $control; self.$control_input = $control_input; self.$dropdown = $dropdown; self.$dropdown_content = $dropdown_content; - + $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); }); $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); }); watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); }); autoGrow($control_input); - + $control.on({ mousedown : function() { return self.onMouseDown.apply(self, arguments); }, click : function() { return self.onClick.apply(self, arguments); } }); - + $control_input.on({ mousedown : function(e) { e.stopPropagation(); }, keydown : function() { return self.onKeyDown.apply(self, arguments); }, @@ -1243,19 +1250,19 @@ focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); }, paste : function() { return self.onPaste.apply(self, arguments); } }); - + $document.on('keydown' + eventNS, function(e) { self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey']; self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey']; self.isShiftDown = e.shiftKey; }); - + $document.on('keyup' + eventNS, function(e) { if (e.keyCode === KEY_CTRL) self.isCtrlDown = false; if (e.keyCode === KEY_SHIFT) self.isShiftDown = false; if (e.keyCode === KEY_CMD) self.isCmdDown = false; }); - + $document.on('mousedown' + eventNS, function(e) { if (self.isFocused) { // prevent events on the dropdown scrollbar from causing the control to blur @@ -1268,7 +1275,7 @@ } } }); - + $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() { if (self.isOpen) { self.positionDropdown.apply(self, arguments); @@ -1277,21 +1284,21 @@ $window.on('mousemove' + eventNS, function() { self.ignoreHover = false; }); - + // store original children and tab index so that they can be // restored when the destroy() method is called. this.revertSettings = { $children : $input.children().detach(), tabindex : $input.attr('tabindex') }; - + $input.attr('tabindex', -1).hide().after(self.$wrapper); - + if ($.isArray(settings.items)) { self.setValue(settings.items); delete settings.items; } - + // feature detect for the validation API if (SUPPORTS_VALIDITY_API) { $input.on('invalid' + eventNS, function(e) { @@ -1300,30 +1307,30 @@ self.refreshState(); }); } - + self.updateOriginalInput(); self.refreshItems(); self.refreshState(); self.updatePlaceholder(); self.isSetup = true; - + if ($input.is(':disabled')) { self.disable(); } - + self.on('change', this.onChange); - + $input.data('selectize', self); $input.addClass('selectized'); self.trigger('initialize'); - + // preload options if (settings.preload === true) { self.onSearchChange(''); } - + }, - + /** * Sets up default rendering functions. */ @@ -1331,7 +1338,7 @@ var self = this; var field_label = self.settings.labelField; var field_optgroup = self.settings.optgroupLabelField; - + var templates = { 'optgroup': function(data) { return '
' + data.html + '
'; @@ -1349,10 +1356,10 @@ return '
Add ' + escape(data.input) + '
'; } }; - + self.settings.render = $.extend({}, templates, self.settings.render); }, - + /** * Maps fired events to callbacks provided * in the settings used when creating the control. @@ -1377,7 +1384,7 @@ 'focus' : 'onFocus', 'blur' : 'onBlur' }; - + for (key in callbacks) { if (callbacks.hasOwnProperty(key)) { fn = this.settings[callbacks[key]]; @@ -1385,7 +1392,7 @@ } } }, - + /** * Triggered when the main control element * has a click event. @@ -1395,7 +1402,7 @@ */ onClick: function(e) { var self = this; - + // necessary for mobile webkit devices (manual focus triggering // is ignored unless invoked within a click event) if (!self.isFocused) { @@ -1403,7 +1410,7 @@ e.preventDefault(); } }, - + /** * Triggered when the main control element * has a mouse down event. @@ -1415,7 +1422,7 @@ var self = this; var defaultPrevented = e.isDefaultPrevented(); var $target = $(e.target); - + if (self.isFocused) { // retain focus by preventing native handling. if the // event target is the input it should not be modified. @@ -1438,7 +1445,7 @@ } } }, - + /** * Triggered when the value of the control has been changed. * This should propagate the event to the original DOM @@ -1447,7 +1454,7 @@ onChange: function() { this.$input.trigger('change'); }, - + /** * Triggered on paste. * @@ -1471,7 +1478,7 @@ } } }, - + /** * Triggered on keypress. * @@ -1487,7 +1494,7 @@ return false; } }, - + /** * Triggered on keydown. * @@ -1497,14 +1504,14 @@ onKeyDown: function(e) { var isInput = e.target === this.$control_input[0]; var self = this; - + if (self.isLocked) { if (e.keyCode !== KEY_TAB) { e.preventDefault(); } return; } - + switch (e.keyCode) { case KEY_A: if (self.isCmdDown) { @@ -1556,7 +1563,7 @@ case KEY_TAB: if (self.settings.selectOnTab && self.isOpen && self.$activeOption) { self.onOptionSelect({currentTarget: self.$activeOption}); - + // Default behaviour is to jump to the next field, we only want this // if the current field doesn't accept any more entries if (!self.isFull()) { @@ -1572,13 +1579,13 @@ self.deleteSelection(e); return; } - + if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) { e.preventDefault(); return; } }, - + /** * Triggered on keyup. * @@ -1587,7 +1594,7 @@ */ onKeyUp: function(e) { var self = this; - + if (self.isLocked) return e && e.preventDefault(); var value = self.$control_input.val() || ''; if (self.lastValue !== value) { @@ -1597,7 +1604,7 @@ self.trigger('type', value); } }, - + /** * Invokes the user-provide option provider / loader. * @@ -1616,7 +1623,7 @@ fn.apply(self, [value, callback]); }); }, - + /** * Triggered on focus. * @@ -1626,28 +1633,28 @@ onFocus: function(e) { var self = this; var wasFocused = self.isFocused; - + if (self.isDisabled) { self.blur(); e && e.preventDefault(); return false; } - + if (self.ignoreFocus) return; self.isFocused = true; if (self.settings.preload === 'focus') self.onSearchChange(''); - + if (!wasFocused) self.trigger('focus'); - + if (!self.$activeItems.length) { self.showInput(); self.setActiveItem(null); self.refreshOptions(!!self.settings.openOnFocus); } - + self.refreshState(); }, - + /** * Triggered on blur. * @@ -1658,7 +1665,7 @@ var self = this; if (!self.isFocused) return; self.isFocused = false; - + if (self.ignoreFocus) { return; } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) { @@ -1667,7 +1674,7 @@ self.onFocus(e); return; } - + var deactivate = function() { self.close(); self.setTextboxValue(''); @@ -1675,14 +1682,14 @@ self.setActiveOption(null); self.setCaret(self.items.length); self.refreshState(); - + // IE11 bug: element still marked as active (dest || document.body).focus(); - + self.ignoreFocus = false; self.trigger('blur'); }; - + self.ignoreFocus = true; if (self.settings.create && self.settings.createOnBlur) { self.createItem(null, false, deactivate); @@ -1690,7 +1697,7 @@ deactivate(); } }, - + /** * Triggered when the user rolls over * an option in the autocomplete dropdown menu. @@ -1702,7 +1709,7 @@ if (this.ignoreHover) return; this.setActiveOption(e.currentTarget, false); }, - + /** * Triggered when the user clicks on an option * in the autocomplete dropdown menu. @@ -1712,12 +1719,12 @@ */ onOptionSelect: function(e) { var value, $target, $option, self = this; - + if (e.preventDefault) { e.preventDefault(); e.stopPropagation(); } - + $target = $(e.currentTarget); if ($target.hasClass('create')) { self.createItem(null, function() { @@ -1739,7 +1746,7 @@ } } }, - + /** * Triggered when the user clicks on an item * that has been selected. @@ -1749,14 +1756,14 @@ */ onItemSelect: function(e) { var self = this; - + if (self.isLocked) return; if (self.settings.mode === 'multi') { e.preventDefault(); self.setActiveItem(e.currentTarget, e); } }, - + /** * Invokes the provided method that provides * results to a callback---which are then added @@ -1767,7 +1774,7 @@ load: function(fn) { var self = this; var $wrapper = self.$wrapper.addClass(self.settings.loadingClass); - + self.loading++; fn.apply(self, [function(results) { self.loading = Math.max(self.loading - 1, 0); @@ -1781,7 +1788,7 @@ self.trigger('load', results); }]); }, - + /** * Sets the input field of the control to the specified value. * @@ -1795,7 +1802,7 @@ this.lastValue = value; } }, - + /** * Returns the value of the control. If multiple items * can be selected (e.g. or * element to reflect the current state. @@ -2700,7 +2707,7 @@ updateOriginalInput: function(opts) { var i, n, options, label, self = this; opts = opts || {}; - + if (self.tagType === TAG_SELECT) { options = []; for (i = 0, n = self.items.length; i < n; i++) { @@ -2715,14 +2722,14 @@ self.$input.val(self.getValue()); self.$input.attr('value',self.$input.val()); } - + if (self.isSetup) { if (!opts.silent) { self.trigger('change', self.$input.val()); } } }, - + /** * Shows/hide the input placeholder depending * on if there items in the list already. @@ -2730,7 +2737,7 @@ updatePlaceholder: function() { if (!this.settings.placeholder) return; var $input = this.$control_input; - + if (this.items.length) { $input.removeAttr('placeholder'); } else { @@ -2738,14 +2745,14 @@ } $input.triggerHandler('update', {force: true}); }, - + /** * Shows the autocomplete dropdown containing * the available options. */ open: function() { var self = this; - + if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return; self.focus(); self.isOpen = true; @@ -2755,26 +2762,26 @@ self.$dropdown.css({visibility: 'visible'}); self.trigger('dropdown_open', self.$dropdown); }, - + /** * Closes the autocomplete dropdown menu. */ close: function() { var self = this; var trigger = self.isOpen; - + if (self.settings.mode === 'single' && self.items.length) { self.hideInput(); } - + self.isOpen = false; self.$dropdown.hide(); self.setActiveOption(null); self.refreshState(); - + if (trigger) self.trigger('dropdown_close', self.$dropdown); }, - + /** * Calculates and applies the appropriate * position of the dropdown. @@ -2783,14 +2790,14 @@ var $control = this.$control; var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position(); offset.top += $control.outerHeight(true); - + this.$dropdown.css({ width : $control.outerWidth(), top : offset.top, left : offset.left }); }, - + /** * Resets / clears all selected items * from the control. @@ -2799,7 +2806,7 @@ */ clear: function(silent) { var self = this; - + if (!self.items.length) return; self.$control.children(':not(input)').remove(); self.items = []; @@ -2812,7 +2819,7 @@ self.showInput(); self.trigger('clear'); }, - + /** * A helper method for inserting an element * at the current caret position. @@ -2828,7 +2835,7 @@ } this.setCaret(caret + 1); }, - + /** * Removes the current selected item(s). * @@ -2838,22 +2845,22 @@ deleteSelection: function(e) { var i, n, direction, selection, values, caret, option_select, $option_select, $tail; var self = this; - + direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1; selection = getSelection(self.$control_input[0]); - + if (self.$activeOption && !self.settings.hideSelected) { option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value'); } - + // determine items that will be removed values = []; - + if (self.$activeItems.length) { $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first')); caret = self.$control.children(':not(input)').index($tail); if (direction > 0) { caret++; } - + for (i = 0, n = self.$activeItems.length; i < n; i++) { values.push($(self.$activeItems[i]).attr('data-value')); } @@ -2868,12 +2875,12 @@ values.push(self.items[self.caretPos]); } } - + // allow the callback to abort if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) { return false; } - + // perform removal if (typeof caret !== 'undefined') { self.setCaret(caret); @@ -2881,11 +2888,11 @@ while (values.length) { self.removeItem(values.pop()); } - + self.showInput(); self.positionDropdown(); self.refreshOptions(true); - + // select previous option if (option_select) { $option_select = self.getOption(option_select); @@ -2893,10 +2900,10 @@ self.setActiveOption($option_select); } } - + return true; }, - + /** * Selects the previous / next item (depending * on the `direction` argument). @@ -2910,19 +2917,19 @@ advanceSelection: function(direction, e) { var tail, selection, idx, valueLength, cursorAtEdge, $tail; var self = this; - + if (direction === 0) return; if (self.rtl) direction *= -1; - + tail = direction > 0 ? 'last' : 'first'; selection = getSelection(self.$control_input[0]); - + if (self.isFocused && !self.isInputHidden) { valueLength = self.$control_input.val().length; cursorAtEdge = direction < 0 ? selection.start === 0 && selection.length === 0 : selection.start === valueLength; - + if (cursorAtEdge && !valueLength) { self.advanceCaret(direction, e); } @@ -2935,7 +2942,7 @@ } } }, - + /** * Moves the caret left / right. * @@ -2944,9 +2951,9 @@ */ advanceCaret: function(direction, e) { var self = this, fn, $adj; - + if (direction === 0) return; - + fn = direction > 0 ? 'next' : 'prev'; if (self.isShiftDown) { $adj = self.$control_input[fn](); @@ -2959,7 +2966,7 @@ self.setCaret(self.caretPos + direction); } }, - + /** * Moves the caret to the specified index. * @@ -2967,13 +2974,13 @@ */ setCaret: function(i) { var self = this; - + if (self.settings.mode === 'single') { i = self.items.length; } else { i = Math.max(0, Math.min(self.items.length, i)); } - + if(!self.isPending) { // the input must be moved by leaving it in place and moving the // siblings, due to the fact that focus cannot be restored once lost @@ -2989,10 +2996,10 @@ } } } - + self.caretPos = i; }, - + /** * Disables user input on the control. Used while * items are being asynchronously created. @@ -3002,7 +3009,7 @@ this.isLocked = true; this.refreshState(); }, - + /** * Re-enables user input on the control. */ @@ -3010,7 +3017,7 @@ this.isLocked = false; this.refreshState(); }, - + /** * Disables user input on the control completely. * While disabled, it cannot receive focus. @@ -3022,7 +3029,7 @@ self.isDisabled = true; self.lock(); }, - + /** * Enables the control so that it can respond * to focus and user input. @@ -3034,7 +3041,7 @@ self.isDisabled = false; self.unlock(); }, - + /** * Completely destroys the control and * unbinds all event listeners so that it can @@ -3044,12 +3051,12 @@ var self = this; var eventNS = self.eventNS; var revertSettings = self.revertSettings; - + self.trigger('destroy'); self.off(); self.$wrapper.remove(); self.$dropdown.remove(); - + self.$input .html('') .append(revertSettings.$children) @@ -3057,17 +3064,17 @@ .removeClass('selectized') .attr({tabindex: revertSettings.tabindex}) .show(); - + self.$control_input.removeData('grow'); self.$input.removeData('selectize'); - + $(window).off(eventNS); $(document).off(eventNS); $(document.body).off(eventNS); - + delete self.$input[0].selectize; }, - + /** * A helper method for rendering "item" and * "option" templates, given the data. @@ -3082,12 +3089,12 @@ var cache = false; var self = this; var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i; - + if (templateName === 'option' || templateName === 'item') { value = hash_key(data[self.settings.valueField]); cache = !!value; } - + // pull markup from cache if it exists if (cache) { if (!isset(self.renderCache[templateName])) { @@ -3097,10 +3104,10 @@ return self.renderCache[templateName][value]; } } - + // render markup html = self.settings.render[templateName].apply(this, [data, escape_html]); - + // add mandatory attributes if (templateName === 'option' || templateName === 'option_create') { html = html.replace(regex_tag, '<$1 data-selectable'); @@ -3112,15 +3119,15 @@ if (templateName === 'option' || templateName === 'item') { html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"'); } - + // update cache if (cache) { self.renderCache[templateName][value] = html; } - + return html; }, - + /** * Clears the render cache for a template. If * no template is given, clears all render @@ -3136,7 +3143,7 @@ delete self.renderCache[templateName]; } }, - + /** * Determines whether or not to display the * create item prompt, given a user input. @@ -3153,15 +3160,15 @@ && (typeof filter !== 'string' || new RegExp(filter).test(input)) && (!(filter instanceof RegExp) || filter.test(input)); } - + }); - - + + Selectize.count = 0; Selectize.defaults = { options: [], optgroups: [], - + plugins: [], delimiter: ',', splitOn: null, // regexp or string for splitting up values from a paste command @@ -3180,11 +3187,11 @@ preload: false, allowEmptyOption: false, closeAfterSelect: false, - + scrollDuration: 60, loadThrottle: 300, loadingClass: 'loading', - + dataAttr: 'data-data', optgroupField: 'optgroup', valueField: 'value', @@ -3192,21 +3199,21 @@ optgroupLabelField: 'label', optgroupValueField: 'value', lockOptgroupOrder: false, - + sortField: '$order', searchField: ['text'], searchConjunction: 'and', - + mode: null, wrapperClass: 'selectize-control', inputClass: 'selectize-input', dropdownClass: 'selectize-dropdown', dropdownContentClass: 'selectize-dropdown-content', - + dropdownParent: null, - + copyClassesToDropdown: true, - + /* load : null, // function(query, callback) { ... } score : null, // function(search) { ... } @@ -3226,7 +3233,7 @@ onType : null, // function(str) { ... } onDelete : null, // function(values) { ... } */ - + render: { /* item: null, @@ -3237,8 +3244,8 @@ */ } }; - - + + $.fn.selectize = function(settings_user) { var defaults = $.fn.selectize.defaults; var settings = $.extend({}, defaults, settings_user); @@ -3248,9 +3255,7 @@ var field_optgroup = settings.optgroupField; var field_optgroup_label = settings.optgroupLabelField; var field_optgroup_value = settings.optgroupValueField; - - var optionsMap = {}; - + /** * Initializes selectize from a element. * @@ -3259,9 +3264,9 @@ */ var init_textbox = function($input, settings_element) { var i, n, values, option; - + var data_raw = $input.attr(attr_data); - + if (!data_raw) { var value = $.trim($input.val() || ''); if (!settings.allowEmptyOption && !value.length) return; @@ -3280,7 +3285,7 @@ } } }; - + /** * Initializes selectize from a