From 840376a4f5d47406bf923c2b380957735b903380 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <yohann.danello@gmail.com>
Date: Mon, 13 Apr 2020 06:01:27 +0200
Subject: [PATCH] Add teams

---
 apps/wei/forms.py                        |    7 +-
 apps/wei/models.py                       |   12 +
 apps/wei/tables.py                       |    8 +-
 apps/wei/urls.py                         |    6 +-
 apps/wei/views.py                        |   73 +-
 note_kfet/inputs.py                      |   25 +-
 static/colorfield/colorfield.js          |   12 +
 static/colorfield/jscolor/jscolor.js     | 1855 ++++++++++++++++++++++
 static/colorfield/jscolor/jscolor.min.js |    1 +
 templates/colorfield/color.html          |    8 +
 templates/wei/bus_detail.html            |    9 +
 templates/wei/bus_form.html              |    4 +-
 templates/wei/bus_tables.html            |   33 +
 templates/wei/busteam_form.html          |   15 +
 templates/wei/weiclub_tables.html        |    2 +-
 15 files changed, 2054 insertions(+), 16 deletions(-)
 create mode 100644 static/colorfield/colorfield.js
 create mode 100755 static/colorfield/jscolor/jscolor.js
 create mode 100755 static/colorfield/jscolor/jscolor.min.js
 create mode 100755 templates/colorfield/color.html
 create mode 100644 templates/wei/bus_detail.html
 create mode 100644 templates/wei/bus_tables.html
 create mode 100644 templates/wei/busteam_form.html

diff --git a/apps/wei/forms.py b/apps/wei/forms.py
index 6fb7af04..e4816cc2 100644
--- a/apps/wei/forms.py
+++ b/apps/wei/forms.py
@@ -4,9 +4,9 @@
 from django import forms
 from django.contrib.auth.models import User
 from django.utils.translation import ugettext_lazy as _
-from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete
+from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
 
-from .models import WEIClub, WEIRegistration, Bus
+from .models import WEIClub, WEIRegistration, Bus, BusTeam
 
 
 class WEIForm(forms.ModelForm):
@@ -62,7 +62,7 @@ class BusForm(forms.ModelForm):
 
 class BusTeamForm(forms.ModelForm):
     class Meta:
-        model = Bus
+        model = BusTeam
         fields = '__all__'
         widgets = {
             "bus": Autocomplete(
@@ -72,4 +72,5 @@ class BusTeamForm(forms.ModelForm):
                     'placeholder': 'Bus ...',
                 },
             ),
+            "color": ColorWidget(),
         }
diff --git a/apps/wei/models.py b/apps/wei/models.py
index 8a57863c..634038bb 100644
--- a/apps/wei/models.py
+++ b/apps/wei/models.py
@@ -50,6 +50,12 @@ class Bus(models.Model):
         verbose_name=_("name"),
     )
 
+    description = models.TextField(
+        blank=True,
+        default="",
+        verbose_name=_("description"),
+    )
+
     def __str__(self):
         return self.name
 
@@ -77,6 +83,12 @@ class BusTeam(models.Model):
         help_text=_("The color of the T-Shirt, stored with its number equivalent"),
     )
 
+    description = models.TextField(
+        blank=True,
+        default="",
+        verbose_name=_("description"),
+    )
+
     def __str__(self):
         return self.name + " (" + str(self.bus) + ")"
 
diff --git a/apps/wei/tables.py b/apps/wei/tables.py
index e0e40d0d..9bb27bc2 100644
--- a/apps/wei/tables.py
+++ b/apps/wei/tables.py
@@ -107,18 +107,22 @@ class BusTeamTable(tables.Table):
     color = tables.Column(
         attrs={
             "td": {
-                "style": lambda record: "background-color: #" + "".format(record.color) + ";"
+                "style": lambda record: "background-color: #{:06X}; color: #{:06X};"
+                                        .format(record.color, 0xFFFFFF - record.color, )
             }
         }
     )
 
+    def render_color(self, value):
+        return "#{:06X}".format(value)
+
     class Meta:
         attrs = {
             'class': 'table table-condensed table-striped table-hover'
         }
         model = BusTeam
         template_name = 'django_tables2/bootstrap4.html'
-        fields = ('name', 'color', 'team',)
+        fields = ('name', 'color',)
         row_attrs = {
             'class': 'table-row',
             'id': lambda record: "row-" + str(record.pk),
diff --git a/apps/wei/urls.py b/apps/wei/urls.py
index 331cb4db..25779c5b 100644
--- a/apps/wei/urls.py
+++ b/apps/wei/urls.py
@@ -3,7 +3,8 @@
 
 from django.urls import path
 
-from .views import WEIListView, WEICreateView, WEIDetailView, WEIUpdateView, BusCreateView,\
+from .views import WEIListView, WEICreateView, WEIDetailView, WEIUpdateView,\
+    BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView,\
     WEIRegisterView, WEIUpdateRegistrationView
 
 
@@ -14,6 +15,9 @@ urlpatterns = [
     path('detail/<int:pk>/', WEIDetailView.as_view(), name="wei_detail"),
     path('update/<int:pk>/', WEIUpdateView.as_view(), name="wei_update"),
     path('add-bus/<int:pk>/', BusCreateView.as_view(), name="add_bus"),
+    path('manage-bus/<int:pk>/', BusManageView.as_view(), name="manage_bus"),
+    path('update-bus/<int:pk>/', BusUpdateView.as_view(), name="update_bus"),
+    path('add-bus-team/<int:pk>/', BusTeamCreateView.as_view(), name="add_team"),
     path('register/<int:wei_pk>/', WEIRegisterView.as_view(), name="wei_register"),
     path('edit-registration/<int:pk>/', WEIUpdateRegistrationView.as_view(), name="wei_update_registration"),
 ]
diff --git a/apps/wei/views.py b/apps/wei/views.py
index cf7c9ca8..7f245dfd 100644
--- a/apps/wei/views.py
+++ b/apps/wei/views.py
@@ -16,9 +16,9 @@ from note.tables import HistoryTable
 from permission.backends import PermissionBackend
 from permission.views import ProtectQuerysetMixin
 
-from .models import WEIClub, WEIRegistration, WEIMembership, Bus
-from .forms import WEIForm, WEIRegistrationForm, BusForm
-from .tables import WEITable, WEIRegistrationTable, BusTable
+from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam
+from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm
+from .tables import WEITable, WEIRegistrationTable, BusTable, BusTeamTable
 
 
 class WEIListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
@@ -141,7 +141,71 @@ class BusCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
 
     def get_success_url(self):
         self.object.refresh_from_db()
-        return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk})
+        return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.pk})
+
+
+class BusUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
+    """
+    Update Bus
+    """
+    model = Bus
+    form_class = BusForm
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["club"] = self.object.wei
+        return context
+
+    def get_form(self, form_class=None):
+        form = super().get_form(form_class)
+        form.fields["wei"].disabled = True
+        return form
+
+    def get_success_url(self):
+        self.object.refresh_from_db()
+        return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.pk})
+
+
+class BusManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
+    """
+    Manage Bus
+    """
+    model = Bus
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["club"] = self.object.wei
+
+        bus = self.object
+        teams = BusTeam.objects.filter(PermissionBackend.filter_queryset(self.request.user, BusTeam, "view"))\
+            .filter(bus=bus)
+        teams_table = BusTeamTable(data=teams, prefix="teams-")
+        context["teams"] = teams_table
+
+        return context
+
+
+class BusTeamCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
+    """
+    Create BusTeam
+    """
+    model = BusTeam
+    form_class = BusTeamForm
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        bus = Bus.objects.get(pk=self.kwargs["pk"])
+        context["club"] = bus.wei
+        return context
+
+    def get_form(self, form_class=None):
+        form = super().get_form(form_class)
+        form.fields["bus"].initial = Bus.objects.get(pk=self.kwargs["pk"])
+        return form
+
+    def get_success_url(self):
+        self.object.refresh_from_db()
+        return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.bus.pk})
 
 
 class WEIRegisterView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
@@ -180,4 +244,3 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
     def get_success_url(self):
         self.object.refresh_from_db()
         return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk})
-
diff --git a/note_kfet/inputs.py b/note_kfet/inputs.py
index b3cccbce..1a17d5ac 100644
--- a/note_kfet/inputs.py
+++ b/note_kfet/inputs.py
@@ -3,7 +3,7 @@
 
 from json import dumps as json_dumps
 
-from django.forms.widgets import DateTimeBaseInput, NumberInput, TextInput
+from django.forms.widgets import DateTimeBaseInput, NumberInput, TextInput, Widget
 
 
 class AmountInput(NumberInput):
@@ -41,6 +41,29 @@ class Autocomplete(TextInput):
         return ""
 
 
+class ColorWidget(Widget):
+    """
+    Pulled from django-colorfield.
+    Select a color.
+    """
+    template_name = 'colorfield/color.html'
+
+    class Media:
+        js = [
+            'colorfield/jscolor/jscolor.min.js',
+            'colorfield/colorfield.js',
+        ]
+
+    def format_value(self, value):
+        if value is None:
+            value = 0xFFFFFF
+        return "#{:06X}".format(value)
+
+    def value_from_datadict(self, data, files, name):
+        val = super().value_from_datadict(data, files, name)
+        return int(val[1:], 16)
+
+
 """
 The remaining of this file comes from the project `django-bootstrap-datepicker-plus` available on Github:
 https://github.com/monim67/django-bootstrap-datepicker-plus
diff --git a/static/colorfield/colorfield.js b/static/colorfield/colorfield.js
new file mode 100644
index 00000000..a3c2de62
--- /dev/null
+++ b/static/colorfield/colorfield.js
@@ -0,0 +1,12 @@
+/** global: django */
+
+window.onload = function() {
+    if (typeof(django) !== 'undefined' && typeof(django.jQuery) !== 'undefined') {
+        (function($) {
+            // add colopicker to inlines added dynamically
+            $(document).on('formset:added', function onFormsetAdded(event, row) {
+                jscolor.installByClassName('jscolor');
+            });
+        }(django.jQuery));
+    }
+};
\ No newline at end of file
diff --git a/static/colorfield/jscolor/jscolor.js b/static/colorfield/jscolor/jscolor.js
new file mode 100755
index 00000000..dbbd2342
--- /dev/null
+++ b/static/colorfield/jscolor/jscolor.js
@@ -0,0 +1,1855 @@
+/**
+ * jscolor - JavaScript Color Picker
+ *
+ * @link    http://jscolor.com
+ * @license For open source use: GPLv3
+ *          For commercial use: JSColor Commercial License
+ * @author  Jan Odvarko
+ * @version 2.0.5
+ *
+ * See usage examples at http://jscolor.com/examples/
+ */
+
+
+"use strict";
+
+
+if (!window.jscolor) { window.jscolor = (function () {
+
+
+var jsc = {
+
+
+    register : function () {
+        jsc.attachDOMReadyEvent(jsc.init);
+        jsc.attachEvent(document, 'mousedown', jsc.onDocumentMouseDown);
+        jsc.attachEvent(document, 'touchstart', jsc.onDocumentTouchStart);
+        jsc.attachEvent(window, 'resize', jsc.onWindowResize);
+    },
+
+
+    init : function () {
+        if (jsc.jscolor.lookupClass) {
+            jsc.jscolor.installByClassName(jsc.jscolor.lookupClass);
+        }
+    },
+
+
+    tryInstallOnElements : function (elms, className) {
+        var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i');
+
+        for (var i = 0; i < elms.length; i += 1) {
+            if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') {
+                if (jsc.isColorAttrSupported) {
+                    // skip inputs of type 'color' if supported by the browser
+                    continue;
+                }
+            }
+            var m;
+            if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) {
+                var targetElm = elms[i];
+                var optsStr = null;
+
+                var dataOptions = jsc.getDataAttr(targetElm, 'jscolor');
+                if (dataOptions !== null) {
+                    optsStr = dataOptions;
+                } else if (m[4]) {
+                    optsStr = m[4];
+                }
+
+                var opts = {};
+                if (optsStr) {
+                    try {
+                        opts = (new Function ('return (' + optsStr + ')'))();
+                    } catch(eParseError) {
+                        jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr);
+                    }
+                }
+                targetElm.jscolor = new jsc.jscolor(targetElm, opts);
+            }
+        }
+    },
+
+
+    isColorAttrSupported : (function () {
+        var elm = document.createElement('input');
+        if (elm.setAttribute) {
+            elm.setAttribute('type', 'color');
+            if (elm.type.toLowerCase() == 'color') {
+                return true;
+            }
+        }
+        return false;
+    })(),
+
+
+    isCanvasSupported : (function () {
+        var elm = document.createElement('canvas');
+        return !!(elm.getContext && elm.getContext('2d'));
+    })(),
+
+
+    fetchElement : function (mixed) {
+        return typeof mixed === 'string' ? document.getElementById(mixed) : mixed;
+    },
+
+
+    isElementType : function (elm, type) {
+        return elm.nodeName.toLowerCase() === type.toLowerCase();
+    },
+
+
+    getDataAttr : function (el, name) {
+        var attrName = 'data-' + name;
+        var attrValue = el.getAttribute(attrName);
+        if (attrValue !== null) {
+            return attrValue;
+        }
+        return null;
+    },
+
+
+    attachEvent : function (el, evnt, func) {
+        if (el.addEventListener) {
+            el.addEventListener(evnt, func, false);
+        } else if (el.attachEvent) {
+            el.attachEvent('on' + evnt, func);
+        }
+    },
+
+
+    detachEvent : function (el, evnt, func) {
+        if (el.removeEventListener) {
+            el.removeEventListener(evnt, func, false);
+        } else if (el.detachEvent) {
+            el.detachEvent('on' + evnt, func);
+        }
+    },
+
+
+    _attachedGroupEvents : {},
+
+
+    attachGroupEvent : function (groupName, el, evnt, func) {
+        if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
+            jsc._attachedGroupEvents[groupName] = [];
+        }
+        jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
+        jsc.attachEvent(el, evnt, func);
+    },
+
+
+    detachGroupEvents : function (groupName) {
+        if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
+            for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) {
+                var evt = jsc._attachedGroupEvents[groupName][i];
+                jsc.detachEvent(evt[0], evt[1], evt[2]);
+            }
+            delete jsc._attachedGroupEvents[groupName];
+        }
+    },
+
+
+    attachDOMReadyEvent : function (func) {
+        var fired = false;
+        var fireOnce = function () {
+            if (!fired) {
+                fired = true;
+                func();
+            }
+        };
+
+        if (document.readyState === 'complete') {
+            setTimeout(fireOnce, 1); // async
+            return;
+        }
+
+        if (document.addEventListener) {
+            document.addEventListener('DOMContentLoaded', fireOnce, false);
+
+            // Fallback
+            window.addEventListener('load', fireOnce, false);
+
+        } else if (document.attachEvent) {
+            // IE
+            document.attachEvent('onreadystatechange', function () {
+                if (document.readyState === 'complete') {
+                    document.detachEvent('onreadystatechange', arguments.callee);
+                    fireOnce();
+                }
+            })
+
+            // Fallback
+            window.attachEvent('onload', fireOnce);
+
+            // IE7/8
+            if (document.documentElement.doScroll && window == window.top) {
+                var tryScroll = function () {
+                    if (!document.body) { return; }
+                    try {
+                        document.documentElement.doScroll('left');
+                        fireOnce();
+                    } catch (e) {
+                        setTimeout(tryScroll, 1);
+                    }
+                };
+                tryScroll();
+            }
+        }
+    },
+
+
+    warn : function (msg) {
+        if (window.console && window.console.warn) {
+            window.console.warn(msg);
+        }
+    },
+
+
+    preventDefault : function (e) {
+        if (e.preventDefault) { e.preventDefault(); }
+        e.returnValue = false;
+    },
+
+
+    captureTarget : function (target) {
+        // IE
+        if (target.setCapture) {
+            jsc._capturedTarget = target;
+            jsc._capturedTarget.setCapture();
+        }
+    },
+
+
+    releaseTarget : function () {
+        // IE
+        if (jsc._capturedTarget) {
+            jsc._capturedTarget.releaseCapture();
+            jsc._capturedTarget = null;
+        }
+    },
+
+
+    fireEvent : function (el, evnt) {
+        if (!el) {
+            return;
+        }
+        if (document.createEvent) {
+            var ev = document.createEvent('HTMLEvents');
+            ev.initEvent(evnt, true, true);
+            el.dispatchEvent(ev);
+        } else if (document.createEventObject) {
+            var ev = document.createEventObject();
+            el.fireEvent('on' + evnt, ev);
+        } else if (el['on' + evnt]) { // alternatively use the traditional event model
+            el['on' + evnt]();
+        }
+    },
+
+
+    classNameToList : function (className) {
+        return className.replace(/^\s+|\s+$/g, '').split(/\s+/);
+    },
+
+
+    // The className parameter (str) can only contain a single class name
+    hasClass : function (elm, className) {
+        if (!className) {
+            return false;
+        }
+        return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' ');
+    },
+
+
+    // The className parameter (str) can contain multiple class names separated by whitespace
+    setClass : function (elm, className) {
+        var classList = jsc.classNameToList(className);
+        for (var i = 0; i < classList.length; i += 1) {
+            if (!jsc.hasClass(elm, classList[i])) {
+                elm.className += (elm.className ? ' ' : '') + classList[i];
+            }
+        }
+    },
+
+
+    // The className parameter (str) can contain multiple class names separated by whitespace
+    unsetClass : function (elm, className) {
+        var classList = jsc.classNameToList(className);
+        for (var i = 0; i < classList.length; i += 1) {
+            var repl = new RegExp(
+                '^\\s*' + classList[i] + '\\s*|' +
+                '\\s*' + classList[i] + '\\s*$|' +
+                '\\s+' + classList[i] + '(\\s+)',
+                'g'
+            );
+            elm.className = elm.className.replace(repl, '$1');
+        }
+    },
+
+
+    getStyle : function (elm) {
+        return window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle;
+    },
+
+
+    setStyle : (function () {
+        var helper = document.createElement('div');
+        var getSupportedProp = function (names) {
+            for (var i = 0; i < names.length; i += 1) {
+                if (names[i] in helper.style) {
+                    return names[i];
+                }
+            }
+        };
+        var props = {
+            borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']),
+            boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow'])
+        };
+        return function (elm, prop, value) {
+            switch (prop.toLowerCase()) {
+            case 'opacity':
+                var alphaOpacity = Math.round(parseFloat(value) * 100);
+                elm.style.opacity = value;
+                elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')';
+                break;
+            default:
+                elm.style[props[prop]] = value;
+                break;
+            }
+        };
+    })(),
+
+
+    setBorderRadius : function (elm, value) {
+        jsc.setStyle(elm, 'borderRadius', value || '0');
+    },
+
+
+    setBoxShadow : function (elm, value) {
+        jsc.setStyle(elm, 'boxShadow', value || 'none');
+    },
+
+
+    getElementPos : function (e, relativeToViewport) {
+        var x=0, y=0;
+        var rect = e.getBoundingClientRect();
+        x = rect.left;
+        y = rect.top;
+        if (!relativeToViewport) {
+            var viewPos = jsc.getViewPos();
+            x += viewPos[0];
+            y += viewPos[1];
+        }
+        return [x, y];
+    },
+
+
+    getElementSize : function (e) {
+        return [e.offsetWidth, e.offsetHeight];
+    },
+
+
+    // get pointer's X/Y coordinates relative to viewport
+    getAbsPointerPos : function (e) {
+        if (!e) { e = window.event; }
+        var x = 0, y = 0;
+        if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
+            // touch devices
+            x = e.changedTouches[0].clientX;
+            y = e.changedTouches[0].clientY;
+        } else if (typeof e.clientX === 'number') {
+            x = e.clientX;
+            y = e.clientY;
+        }
+        return { x: x, y: y };
+    },
+
+
+    // get pointer's X/Y coordinates relative to target element
+    getRelPointerPos : function (e) {
+        if (!e) { e = window.event; }
+        var target = e.target || e.srcElement;
+        var targetRect = target.getBoundingClientRect();
+
+        var x = 0, y = 0;
+
+        var clientX = 0, clientY = 0;
+        if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
+            // touch devices
+            clientX = e.changedTouches[0].clientX;
+            clientY = e.changedTouches[0].clientY;
+        } else if (typeof e.clientX === 'number') {
+            clientX = e.clientX;
+            clientY = e.clientY;
+        }
+
+        x = clientX - targetRect.left;
+        y = clientY - targetRect.top;
+        return { x: x, y: y };
+    },
+
+
+    getViewPos : function () {
+        var doc = document.documentElement;
+        return [
+            (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
+            (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
+        ];
+    },
+
+
+    getViewSize : function () {
+        var doc = document.documentElement;
+        return [
+            (window.innerWidth || doc.clientWidth),
+            (window.innerHeight || doc.clientHeight),
+        ];
+    },
+
+
+    redrawPosition : function () {
+
+        if (jsc.picker && jsc.picker.owner) {
+            var thisObj = jsc.picker.owner;
+
+            var tp, vp;
+
+            if (thisObj.fixed) {
+                // Fixed elements are positioned relative to viewport,
+                // therefore we can ignore the scroll offset
+                tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
+                vp = [0, 0]; // view pos
+            } else {
+                tp = jsc.getElementPos(thisObj.targetElement); // target pos
+                vp = jsc.getViewPos(); // view pos
+            }
+
+            var ts = jsc.getElementSize(thisObj.targetElement); // target size
+            var vs = jsc.getViewSize(); // view size
+            var ps = jsc.getPickerOuterDims(thisObj); // picker size
+            var a, b, c;
+            switch (thisObj.position.toLowerCase()) {
+                case 'left': a=1; b=0; c=-1; break;
+                case 'right':a=1; b=0; c=1; break;
+                case 'top':  a=0; b=1; c=-1; break;
+                default:     a=0; b=1; c=1; break;
+            }
+            var l = (ts[b]+ps[b])/2;
+
+            // compute picker position
+            if (!thisObj.smartPosition) {
+                var pp = [
+                    tp[a],
+                    tp[b]+ts[b]-l+l*c
+                ];
+            } else {
+                var pp = [
+                    -vp[a]+tp[a]+ps[a] > vs[a] ?
+                        (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
+                        tp[a],
+                    -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
+                        (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
+                        (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
+                ];
+            }
+
+            var x = pp[a];
+            var y = pp[b];
+            var positionValue = thisObj.fixed ? 'fixed' : 'absolute';
+            var contractShadow =
+                (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
+                (pp[1] + ps[1] < tp[1] + ts[1]);
+
+            jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
+        }
+    },
+
+
+    _drawPosition : function (thisObj, x, y, positionValue, contractShadow) {
+        var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px
+
+        jsc.picker.wrap.style.position = positionValue;
+        jsc.picker.wrap.style.left = x + 'px';
+        jsc.picker.wrap.style.top = y + 'px';
+
+        jsc.setBoxShadow(
+            jsc.picker.boxS,
+            thisObj.shadow ?
+                new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) :
+                null);
+    },
+
+
+    getPickerDims : function (thisObj) {
+        var displaySlider = !!jsc.getSliderComponent(thisObj);
+        var dims = [
+            2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.width +
+                (displaySlider ? 2 * thisObj.insetWidth + jsc.getPadToSliderPadding(thisObj) + thisObj.sliderSize : 0),
+            2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.height +
+                (thisObj.closable ? 2 * thisObj.insetWidth + thisObj.padding + thisObj.buttonHeight : 0)
+        ];
+        return dims;
+    },
+
+
+    getPickerOuterDims : function (thisObj) {
+        var dims = jsc.getPickerDims(thisObj);
+        return [
+            dims[0] + 2 * thisObj.borderWidth,
+            dims[1] + 2 * thisObj.borderWidth
+        ];
+    },
+
+
+    getPadToSliderPadding : function (thisObj) {
+        return Math.max(thisObj.padding, 1.5 * (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness));
+    },
+
+
+    getPadYComponent : function (thisObj) {
+        switch (thisObj.mode.charAt(1).toLowerCase()) {
+            case 'v': return 'v'; break;
+        }
+        return 's';
+    },
+
+
+    getSliderComponent : function (thisObj) {
+        if (thisObj.mode.length > 2) {
+            switch (thisObj.mode.charAt(2).toLowerCase()) {
+                case 's': return 's'; break;
+                case 'v': return 'v'; break;
+            }
+        }
+        return null;
+    },
+
+
+    onDocumentMouseDown : function (e) {
+        if (!e) { e = window.event; }
+        var target = e.target || e.srcElement;
+
+        if (target._jscLinkedInstance) {
+            if (target._jscLinkedInstance.showOnClick) {
+                target._jscLinkedInstance.show();
+            }
+        } else if (target._jscControlName) {
+            jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse');
+        } else {
+            // Mouse is outside the picker controls -> hide the color picker!
+            if (jsc.picker && jsc.picker.owner) {
+                jsc.picker.owner.hide();
+            }
+        }
+    },
+
+
+    onDocumentTouchStart : function (e) {
+        if (!e) { e = window.event; }
+        var target = e.target || e.srcElement;
+
+        if (target._jscLinkedInstance) {
+            if (target._jscLinkedInstance.showOnClick) {
+                target._jscLinkedInstance.show();
+            }
+        } else if (target._jscControlName) {
+            jsc.onControlPointerStart(e, target, target._jscControlName, 'touch');
+        } else {
+            if (jsc.picker && jsc.picker.owner) {
+                jsc.picker.owner.hide();
+            }
+        }
+    },
+
+
+    onWindowResize : function (e) {
+        jsc.redrawPosition();
+    },
+
+
+    onParentScroll : function (e) {
+        // hide the picker when one of the parent elements is scrolled
+        if (jsc.picker && jsc.picker.owner) {
+            jsc.picker.owner.hide();
+        }
+    },
+
+
+    _pointerMoveEvent : {
+        mouse: 'mousemove',
+        touch: 'touchmove'
+    },
+    _pointerEndEvent : {
+        mouse: 'mouseup',
+        touch: 'touchend'
+    },
+
+
+    _pointerOrigin : null,
+    _capturedTarget : null,
+
+
+    onControlPointerStart : function (e, target, controlName, pointerType) {
+        var thisObj = target._jscInstance;
+
+        jsc.preventDefault(e);
+        jsc.captureTarget(target);
+
+        var registerDragEvents = function (doc, offset) {
+            jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType],
+                jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset));
+            jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType],
+                jsc.onDocumentPointerEnd(e, target, controlName, pointerType));
+        };
+
+        registerDragEvents(document, [0, 0]);
+
+        if (window.parent && window.frameElement) {
+            var rect = window.frameElement.getBoundingClientRect();
+            var ofs = [-rect.left, -rect.top];
+            registerDragEvents(window.parent.window.document, ofs);
+        }
+
+        var abs = jsc.getAbsPointerPos(e);
+        var rel = jsc.getRelPointerPos(e);
+        jsc._pointerOrigin = {
+            x: abs.x - rel.x,
+            y: abs.y - rel.y
+        };
+
+        switch (controlName) {
+        case 'pad':
+            // if the slider is at the bottom, move it up
+            switch (jsc.getSliderComponent(thisObj)) {
+            case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break;
+            case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break;
+            }
+            jsc.setPad(thisObj, e, 0, 0);
+            break;
+
+        case 'sld':
+            jsc.setSld(thisObj, e, 0);
+            break;
+        }
+
+        jsc.dispatchFineChange(thisObj);
+    },
+
+
+    onDocumentPointerMove : function (e, target, controlName, pointerType, offset) {
+        return function (e) {
+            var thisObj = target._jscInstance;
+            switch (controlName) {
+            case 'pad':
+                if (!e) { e = window.event; }
+                jsc.setPad(thisObj, e, offset[0], offset[1]);
+                jsc.dispatchFineChange(thisObj);
+                break;
+
+            case 'sld':
+                if (!e) { e = window.event; }
+                jsc.setSld(thisObj, e, offset[1]);
+                jsc.dispatchFineChange(thisObj);
+                break;
+            }
+        }
+    },
+
+
+    onDocumentPointerEnd : function (e, target, controlName, pointerType) {
+        return function (e) {
+            var thisObj = target._jscInstance;
+            jsc.detachGroupEvents('drag');
+            jsc.releaseTarget();
+            // Always dispatch changes after detaching outstanding mouse handlers,
+            // in case some user interaction will occur in user's onchange callback
+            // that would intrude with current mouse events
+            jsc.dispatchChange(thisObj);
+        };
+    },
+
+
+    dispatchChange : function (thisObj) {
+        if (thisObj.valueElement) {
+            if (jsc.isElementType(thisObj.valueElement, 'input')) {
+                jsc.fireEvent(thisObj.valueElement, 'change');
+            }
+        }
+    },
+
+
+    dispatchFineChange : function (thisObj) {
+        if (thisObj.onFineChange) {
+            var callback;
+            if (typeof thisObj.onFineChange === 'string') {
+                callback = new Function (thisObj.onFineChange);
+            } else {
+                callback = thisObj.onFineChange;
+            }
+            callback.call(thisObj);
+        }
+    },
+
+
+    setPad : function (thisObj, e, ofsX, ofsY) {
+        var pointerAbs = jsc.getAbsPointerPos(e);
+        var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth;
+        var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth;
+
+        var xVal = x * (360 / (thisObj.width - 1));
+        var yVal = 100 - (y * (100 / (thisObj.height - 1)));
+
+        switch (jsc.getPadYComponent(thisObj)) {
+        case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break;
+        case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break;
+        }
+    },
+
+
+    setSld : function (thisObj, e, ofsY) {
+        var pointerAbs = jsc.getAbsPointerPos(e);
+        var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth;
+
+        var yVal = 100 - (y * (100 / (thisObj.height - 1)));
+
+        switch (jsc.getSliderComponent(thisObj)) {
+        case 's': thisObj.fromHSV(null, yVal, null, jsc.leavePad); break;
+        case 'v': thisObj.fromHSV(null, null, yVal, jsc.leavePad); break;
+        }
+    },
+
+
+    _vmlNS : 'jsc_vml_',
+    _vmlCSS : 'jsc_vml_css_',
+    _vmlReady : false,
+
+
+    initVML : function () {
+        if (!jsc._vmlReady) {
+            // init VML namespace
+            var doc = document;
+            if (!doc.namespaces[jsc._vmlNS]) {
+                doc.namespaces.add(jsc._vmlNS, 'urn:schemas-microsoft-com:vml');
+            }
+            if (!doc.styleSheets[jsc._vmlCSS]) {
+                var tags = ['shape', 'shapetype', 'group', 'background', 'path', 'formulas', 'handles', 'fill', 'stroke', 'shadow', 'textbox', 'textpath', 'imagedata', 'line', 'polyline', 'curve', 'rect', 'roundrect', 'oval', 'arc', 'image'];
+                var ss = doc.createStyleSheet();
+                ss.owningElement.id = jsc._vmlCSS;
+                for (var i = 0; i < tags.length; i += 1) {
+                    ss.addRule(jsc._vmlNS + '\\:' + tags[i], 'behavior:url(#default#VML);');
+                }
+            }
+            jsc._vmlReady = true;
+        }
+    },
+
+
+    createPalette : function () {
+
+        var paletteObj = {
+            elm: null,
+            draw: null
+        };
+
+        if (jsc.isCanvasSupported) {
+            // Canvas implementation for modern browsers
+
+            var canvas = document.createElement('canvas');
+            var ctx = canvas.getContext('2d');
+
+            var drawFunc = function (width, height, type) {
+                canvas.width = width;
+                canvas.height = height;
+
+                ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+                var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
+                hGrad.addColorStop(0 / 6, '#F00');
+                hGrad.addColorStop(1 / 6, '#FF0');
+                hGrad.addColorStop(2 / 6, '#0F0');
+                hGrad.addColorStop(3 / 6, '#0FF');
+                hGrad.addColorStop(4 / 6, '#00F');
+                hGrad.addColorStop(5 / 6, '#F0F');
+                hGrad.addColorStop(6 / 6, '#F00');
+
+                ctx.fillStyle = hGrad;
+                ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+                var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
+                switch (type.toLowerCase()) {
+                case 's':
+                    vGrad.addColorStop(0, 'rgba(255,255,255,0)');
+                    vGrad.addColorStop(1, 'rgba(255,255,255,1)');
+                    break;
+                case 'v':
+                    vGrad.addColorStop(0, 'rgba(0,0,0,0)');
+                    vGrad.addColorStop(1, 'rgba(0,0,0,1)');
+                    break;
+                }
+                ctx.fillStyle = vGrad;
+                ctx.fillRect(0, 0, canvas.width, canvas.height);
+            };
+
+            paletteObj.elm = canvas;
+            paletteObj.draw = drawFunc;
+
+        } else {
+            // VML fallback for IE 7 and 8
+
+            jsc.initVML();
+
+            var vmlContainer = document.createElement('div');
+            vmlContainer.style.position = 'relative';
+            vmlContainer.style.overflow = 'hidden';
+
+            var hGrad = document.createElement(jsc._vmlNS + ':fill');
+            hGrad.type = 'gradient';
+            hGrad.method = 'linear';
+            hGrad.angle = '90';
+            hGrad.colors = '16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0'
+
+            var hRect = document.createElement(jsc._vmlNS + ':rect');
+            hRect.style.position = 'absolute';
+            hRect.style.left = -1 + 'px';
+            hRect.style.top = -1 + 'px';
+            hRect.stroked = false;
+            hRect.appendChild(hGrad);
+            vmlContainer.appendChild(hRect);
+
+            var vGrad = document.createElement(jsc._vmlNS + ':fill');
+            vGrad.type = 'gradient';
+            vGrad.method = 'linear';
+            vGrad.angle = '180';
+            vGrad.opacity = '0';
+
+            var vRect = document.createElement(jsc._vmlNS + ':rect');
+            vRect.style.position = 'absolute';
+            vRect.style.left = -1 + 'px';
+            vRect.style.top = -1 + 'px';
+            vRect.stroked = false;
+            vRect.appendChild(vGrad);
+            vmlContainer.appendChild(vRect);
+
+            var drawFunc = function (width, height, type) {
+                vmlContainer.style.width = width + 'px';
+                vmlContainer.style.height = height + 'px';
+
+                hRect.style.width =
+                vRect.style.width =
+                    (width + 1) + 'px';
+                hRect.style.height =
+                vRect.style.height =
+                    (height + 1) + 'px';
+
+                // Colors must be specified during every redraw, otherwise IE won't display
+                // a full gradient during a subsequential redraw
+                hGrad.color = '#F00';
+                hGrad.color2 = '#F00';
+
+                switch (type.toLowerCase()) {
+                case 's':
+                    vGrad.color = vGrad.color2 = '#FFF';
+                    break;
+                case 'v':
+                    vGrad.color = vGrad.color2 = '#000';
+                    break;
+                }
+            };
+
+            paletteObj.elm = vmlContainer;
+            paletteObj.draw = drawFunc;
+        }
+
+        return paletteObj;
+    },
+
+
+    createSliderGradient : function () {
+
+        var sliderObj = {
+            elm: null,
+            draw: null
+        };
+
+        if (jsc.isCanvasSupported) {
+            // Canvas implementation for modern browsers
+
+            var canvas = document.createElement('canvas');
+            var ctx = canvas.getContext('2d');
+
+            var drawFunc = function (width, height, color1, color2) {
+                canvas.width = width;
+                canvas.height = height;
+
+                ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+                var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
+                grad.addColorStop(0, color1);
+                grad.addColorStop(1, color2);
+
+                ctx.fillStyle = grad;
+                ctx.fillRect(0, 0, canvas.width, canvas.height);
+            };
+
+            sliderObj.elm = canvas;
+            sliderObj.draw = drawFunc;
+
+        } else {
+            // VML fallback for IE 7 and 8
+
+            jsc.initVML();
+
+            var vmlContainer = document.createElement('div');
+            vmlContainer.style.position = 'relative';
+            vmlContainer.style.overflow = 'hidden';
+
+            var grad = document.createElement(jsc._vmlNS + ':fill');
+            grad.type = 'gradient';
+            grad.method = 'linear';
+            grad.angle = '180';
+
+            var rect = document.createElement(jsc._vmlNS + ':rect');
+            rect.style.position = 'absolute';
+            rect.style.left = -1 + 'px';
+            rect.style.top = -1 + 'px';
+            rect.stroked = false;
+            rect.appendChild(grad);
+            vmlContainer.appendChild(rect);
+
+            var drawFunc = function (width, height, color1, color2) {
+                vmlContainer.style.width = width + 'px';
+                vmlContainer.style.height = height + 'px';
+
+                rect.style.width = (width + 1) + 'px';
+                rect.style.height = (height + 1) + 'px';
+
+                grad.color = color1;
+                grad.color2 = color2;
+            };
+
+            sliderObj.elm = vmlContainer;
+            sliderObj.draw = drawFunc;
+        }
+
+        return sliderObj;
+    },
+
+
+    leaveValue : 1<<0,
+    leaveStyle : 1<<1,
+    leavePad : 1<<2,
+    leaveSld : 1<<3,
+
+
+    BoxShadow : (function () {
+        var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) {
+            this.hShadow = hShadow;
+            this.vShadow = vShadow;
+            this.blur = blur;
+            this.spread = spread;
+            this.color = color;
+            this.inset = !!inset;
+        };
+
+        BoxShadow.prototype.toString = function () {
+            var vals = [
+                Math.round(this.hShadow) + 'px',
+                Math.round(this.vShadow) + 'px',
+                Math.round(this.blur) + 'px',
+                Math.round(this.spread) + 'px',
+                this.color
+            ];
+            if (this.inset) {
+                vals.push('inset');
+            }
+            return vals.join(' ');
+        };
+
+        return BoxShadow;
+    })(),
+
+
+    //
+    // Usage:
+    // var myColor = new jscolor(<targetElement> [, <options>])
+    //
+
+    jscolor : function (targetElement, options) {
+
+        // General options
+        //
+        this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB()
+        this.valueElement = targetElement; // element that will be used to display and input the color code
+        this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor
+        this.required = true; // whether the associated text <input> can be left empty
+        this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace)
+        this.hash = false; // whether to prefix the HEX color code with # symbol
+        this.uppercase = true; // whether to show the color code in upper case
+        this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code)
+        this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it
+        this.overwriteImportant = false; // whether to overwrite colors of styleElement using !important
+        this.minS = 0; // min allowed saturation (0 - 100)
+        this.maxS = 100; // max allowed saturation (0 - 100)
+        this.minV = 0; // min allowed value (brightness) (0 - 100)
+        this.maxV = 100; // max allowed value (brightness) (0 - 100)
+
+        // Accessing the picked color
+        //
+        this.hsv = [0, 0, 100]; // read-only  [0-360, 0-100, 0-100]
+        this.rgb = [255, 255, 255]; // read-only  [0-255, 0-255, 0-255]
+
+        // Color Picker options
+        //
+        this.width = 181; // width of color palette (in px)
+        this.height = 101; // height of color palette (in px)
+        this.showOnClick = true; // whether to display the color picker when user clicks on its target element
+        this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls
+        this.position = 'bottom'; // left | right | top | bottom - position relative to the target element
+        this.smartPosition = true; // automatically change picker position when there is not enough space for it
+        this.sliderSize = 16; // px
+        this.crossSize = 8; // px
+        this.closable = false; // whether to display the Close button
+        this.closeText = 'Close';
+        this.buttonColor = '#000000'; // CSS color
+        this.buttonHeight = 18; // px
+        this.padding = 12; // px
+        this.backgroundColor = '#FFFFFF'; // CSS color
+        this.borderWidth = 1; // px
+        this.borderColor = '#BBBBBB'; // CSS color
+        this.borderRadius = 8; // px
+        this.insetWidth = 1; // px
+        this.insetColor = '#BBBBBB'; // CSS color
+        this.shadow = true; // whether to display shadow
+        this.shadowBlur = 15; // px
+        this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color
+        this.pointerColor = '#4C4C4C'; // px
+        this.pointerBorderColor = '#FFFFFF'; // px
+        this.pointerBorderWidth = 1; // px
+        this.pointerThickness = 2; // px
+        this.zIndex = 1000;
+        this.container = null; // where to append the color picker (BODY element by default)
+
+
+        for (var opt in options) {
+            if (options.hasOwnProperty(opt)) {
+                this[opt] = options[opt];
+            }
+        }
+
+
+        this.hide = function () {
+            if (isPickerOwner()) {
+                detachPicker();
+            }
+        };
+
+
+        this.show = function () {
+            drawPicker();
+        };
+
+
+        this.redraw = function () {
+            if (isPickerOwner()) {
+                drawPicker();
+            }
+        };
+
+
+        this.importColor = function () {
+            if (!this.valueElement) {
+                this.exportColor();
+            } else {
+                if (jsc.isElementType(this.valueElement, 'input')) {
+                    if (!this.refine) {
+                        if (!this.fromString(this.valueElement.value, jsc.leaveValue)) {
+                            if (this.styleElement) {
+                                this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage;
+                                this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor;
+                                this.styleElement.style.color = this.styleElement._jscOrigStyle.color;
+                            }
+                            this.exportColor(jsc.leaveValue | jsc.leaveStyle);
+                        }
+                    } else if (!this.required && /^\s*$/.test(this.valueElement.value)) {
+                        this.valueElement.value = '';
+                        if (this.styleElement) {
+                            this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage;
+                            this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor;
+                            this.styleElement.style.color = this.styleElement._jscOrigStyle.color;
+                        }
+                        this.exportColor(jsc.leaveValue | jsc.leaveStyle);
+
+                    } else if (this.fromString(this.valueElement.value)) {
+                        // managed to import color successfully from the value -> OK, don't do anything
+                    } else {
+                        this.exportColor();
+                    }
+                } else {
+                    // not an input element -> doesn't have any value
+                    this.exportColor();
+                }
+            }
+        };
+
+
+        this.exportColor = function (flags) {
+            if (!(flags & jsc.leaveValue) && this.valueElement) {
+                var value = this.toString();
+                if (this.uppercase) { value = value.toUpperCase(); }
+                if (this.hash) { value = '#' + value; }
+
+                if (jsc.isElementType(this.valueElement, 'input')) {
+                    this.valueElement.value = value;
+                } else {
+                    this.valueElement.innerHTML = value;
+                }
+            }
+            if (!(flags & jsc.leaveStyle)) {
+                if (this.styleElement) {
+                    var bgColor = '#' + this.toString();
+                    var fgColor = this.isLight() ? '#000' : '#FFF';
+
+                    this.styleElement.style.backgroundImage = 'none';
+                    this.styleElement.style.backgroundColor = bgColor;
+                    this.styleElement.style.color = fgColor;
+
+                    if (this.overwriteImportant) {
+                        this.styleElement.setAttribute('style',
+                            'background: ' + bgColor + ' !important; ' +
+                            'color: ' + fgColor + ' !important;'
+                        );
+                    }
+                }
+            }
+            if (!(flags & jsc.leavePad) && isPickerOwner()) {
+                redrawPad();
+            }
+            if (!(flags & jsc.leaveSld) && isPickerOwner()) {
+                redrawSld();
+            }
+        };
+
+
+        // h: 0-360
+        // s: 0-100
+        // v: 0-100
+        //
+        this.fromHSV = function (h, s, v, flags) { // null = don't change
+            if (h !== null) {
+                if (isNaN(h)) { return false; }
+                h = Math.max(0, Math.min(360, h));
+            }
+            if (s !== null) {
+                if (isNaN(s)) { return false; }
+                s = Math.max(0, Math.min(100, this.maxS, s), this.minS);
+            }
+            if (v !== null) {
+                if (isNaN(v)) { return false; }
+                v = Math.max(0, Math.min(100, this.maxV, v), this.minV);
+            }
+
+            this.rgb = HSV_RGB(
+                h===null ? this.hsv[0] : (this.hsv[0]=h),
+                s===null ? this.hsv[1] : (this.hsv[1]=s),
+                v===null ? this.hsv[2] : (this.hsv[2]=v)
+            );
+
+            this.exportColor(flags);
+        };
+
+
+        // r: 0-255
+        // g: 0-255
+        // b: 0-255
+        //
+        this.fromRGB = function (r, g, b, flags) { // null = don't change
+            if (r !== null) {
+                if (isNaN(r)) { return false; }
+                r = Math.max(0, Math.min(255, r));
+            }
+            if (g !== null) {
+                if (isNaN(g)) { return false; }
+                g = Math.max(0, Math.min(255, g));
+            }
+            if (b !== null) {
+                if (isNaN(b)) { return false; }
+                b = Math.max(0, Math.min(255, b));
+            }
+
+            var hsv = RGB_HSV(
+                r===null ? this.rgb[0] : r,
+                g===null ? this.rgb[1] : g,
+                b===null ? this.rgb[2] : b
+            );
+            if (hsv[0] !== null) {
+                this.hsv[0] = Math.max(0, Math.min(360, hsv[0]));
+            }
+            if (hsv[2] !== 0) {
+                this.hsv[1] = hsv[1]===null ? null : Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1]));
+            }
+            this.hsv[2] = hsv[2]===null ? null : Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2]));
+
+            // update RGB according to final HSV, as some values might be trimmed
+            var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]);
+            this.rgb[0] = rgb[0];
+            this.rgb[1] = rgb[1];
+            this.rgb[2] = rgb[2];
+
+            this.exportColor(flags);
+        };
+
+
+        this.fromString = function (str, flags) {
+            var m;
+            if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) {
+                // HEX notation
+                //
+
+                if (m[1].length === 6) {
+                    // 6-char notation
+                    this.fromRGB(
+                        parseInt(m[1].substr(0,2),16),
+                        parseInt(m[1].substr(2,2),16),
+                        parseInt(m[1].substr(4,2),16),
+                        flags
+                    );
+                } else {
+                    // 3-char notation
+                    this.fromRGB(
+                        parseInt(m[1].charAt(0) + m[1].charAt(0),16),
+                        parseInt(m[1].charAt(1) + m[1].charAt(1),16),
+                        parseInt(m[1].charAt(2) + m[1].charAt(2),16),
+                        flags
+                    );
+                }
+                return true;
+
+            } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) {
+                var params = m[1].split(',');
+                var re = /^\s*(\d*)(\.\d+)?\s*$/;
+                var mR, mG, mB;
+                if (
+                    params.length >= 3 &&
+                    (mR = params[0].match(re)) &&
+                    (mG = params[1].match(re)) &&
+                    (mB = params[2].match(re))
+                ) {
+                    var r = parseFloat((mR[1] || '0') + (mR[2] || ''));
+                    var g = parseFloat((mG[1] || '0') + (mG[2] || ''));
+                    var b = parseFloat((mB[1] || '0') + (mB[2] || ''));
+                    this.fromRGB(r, g, b, flags);
+                    return true;
+                }
+            }
+            return false;
+        };
+
+
+        this.toString = function () {
+            return (
+                (0x100 | Math.round(this.rgb[0])).toString(16).substr(1) +
+                (0x100 | Math.round(this.rgb[1])).toString(16).substr(1) +
+                (0x100 | Math.round(this.rgb[2])).toString(16).substr(1)
+            );
+        };
+
+
+        this.toHEXString = function () {
+            return '#' + this.toString().toUpperCase();
+        };
+
+
+        this.toRGBString = function () {
+            return ('rgb(' +
+                Math.round(this.rgb[0]) + ',' +
+                Math.round(this.rgb[1]) + ',' +
+                Math.round(this.rgb[2]) + ')'
+            );
+        };
+
+
+        this.isLight = function () {
+            return (
+                0.213 * this.rgb[0] +
+                0.715 * this.rgb[1] +
+                0.072 * this.rgb[2] >
+                255 / 2
+            );
+        };
+
+
+        this._processParentElementsInDOM = function () {
+            if (this._linkedElementsProcessed) { return; }
+            this._linkedElementsProcessed = true;
+
+            var elm = this.targetElement;
+            do {
+                // If the target element or one of its parent nodes has fixed position,
+                // then use fixed positioning instead
+                //
+                // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
+                // that's why we need to check if the returned style object is non-empty
+                var currStyle = jsc.getStyle(elm);
+                if (currStyle && currStyle.position.toLowerCase() === 'fixed') {
+                    this.fixed = true;
+                }
+
+                if (elm !== this.targetElement) {
+                    // Ensure to attach onParentScroll only once to each parent element
+                    // (multiple targetElements can share the same parent nodes)
+                    //
+                    // Note: It's not just offsetParents that can be scrollable,
+                    // that's why we loop through all parent nodes
+                    if (!elm._jscEventsAttached) {
+                        jsc.attachEvent(elm, 'scroll', jsc.onParentScroll);
+                        elm._jscEventsAttached = true;
+                    }
+                }
+            } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body'));
+        };
+
+
+        // r: 0-255
+        // g: 0-255
+        // b: 0-255
+        //
+        // returns: [ 0-360, 0-100, 0-100 ]
+        //
+        function RGB_HSV (r, g, b) {
+            r /= 255;
+            g /= 255;
+            b /= 255;
+            var n = Math.min(Math.min(r,g),b);
+            var v = Math.max(Math.max(r,g),b);
+            var m = v - n;
+            if (m === 0) { return [ null, 0, 100 * v ]; }
+            var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
+            return [
+                60 * (h===6?0:h),
+                100 * (m/v),
+                100 * v
+            ];
+        }
+
+
+        // h: 0-360
+        // s: 0-100
+        // v: 0-100
+        //
+        // returns: [ 0-255, 0-255, 0-255 ]
+        //
+        function HSV_RGB (h, s, v) {
+            var u = 255 * (v / 100);
+
+            if (h === null) {
+                return [ u, u, u ];
+            }
+
+            h /= 60;
+            s /= 100;
+
+            var i = Math.floor(h);
+            var f = i%2 ? h-i : 1-(h-i);
+            var m = u * (1 - s);
+            var n = u * (1 - s * f);
+            switch (i) {
+                case 6:
+                case 0: return [u,n,m];
+                case 1: return [n,u,m];
+                case 2: return [m,u,n];
+                case 3: return [m,n,u];
+                case 4: return [n,m,u];
+                case 5: return [u,m,n];
+            }
+        }
+
+
+        function detachPicker () {
+            jsc.unsetClass(THIS.targetElement, THIS.activeClass);
+            jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
+            delete jsc.picker.owner;
+        }
+
+
+        function drawPicker () {
+
+            // At this point, when drawing the picker, we know what the parent elements are
+            // and we can do all related DOM operations, such as registering events on them
+            // or checking their positioning
+            THIS._processParentElementsInDOM();
+
+            if (!jsc.picker) {
+                jsc.picker = {
+                    owner: null,
+                    wrap : document.createElement('div'),
+                    box : document.createElement('div'),
+                    boxS : document.createElement('div'), // shadow area
+                    boxB : document.createElement('div'), // border
+                    pad : document.createElement('div'),
+                    padB : document.createElement('div'), // border
+                    padM : document.createElement('div'), // mouse/touch area
+                    padPal : jsc.createPalette(),
+                    cross : document.createElement('div'),
+                    crossBY : document.createElement('div'), // border Y
+                    crossBX : document.createElement('div'), // border X
+                    crossLY : document.createElement('div'), // line Y
+                    crossLX : document.createElement('div'), // line X
+                    sld : document.createElement('div'),
+                    sldB : document.createElement('div'), // border
+                    sldM : document.createElement('div'), // mouse/touch area
+                    sldGrad : jsc.createSliderGradient(),
+                    sldPtrS : document.createElement('div'), // slider pointer spacer
+                    sldPtrIB : document.createElement('div'), // slider pointer inner border
+                    sldPtrMB : document.createElement('div'), // slider pointer middle border
+                    sldPtrOB : document.createElement('div'), // slider pointer outer border
+                    btn : document.createElement('div'),
+                    btnT : document.createElement('span') // text
+                };
+
+                jsc.picker.pad.appendChild(jsc.picker.padPal.elm);
+                jsc.picker.padB.appendChild(jsc.picker.pad);
+                jsc.picker.cross.appendChild(jsc.picker.crossBY);
+                jsc.picker.cross.appendChild(jsc.picker.crossBX);
+                jsc.picker.cross.appendChild(jsc.picker.crossLY);
+                jsc.picker.cross.appendChild(jsc.picker.crossLX);
+                jsc.picker.padB.appendChild(jsc.picker.cross);
+                jsc.picker.box.appendChild(jsc.picker.padB);
+                jsc.picker.box.appendChild(jsc.picker.padM);
+
+                jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
+                jsc.picker.sldB.appendChild(jsc.picker.sld);
+                jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
+                jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
+                jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
+                jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
+                jsc.picker.box.appendChild(jsc.picker.sldB);
+                jsc.picker.box.appendChild(jsc.picker.sldM);
+
+                jsc.picker.btn.appendChild(jsc.picker.btnT);
+                jsc.picker.box.appendChild(jsc.picker.btn);
+
+                jsc.picker.boxB.appendChild(jsc.picker.box);
+                jsc.picker.wrap.appendChild(jsc.picker.boxS);
+                jsc.picker.wrap.appendChild(jsc.picker.boxB);
+            }
+
+            var p = jsc.picker;
+
+            var displaySlider = !!jsc.getSliderComponent(THIS);
+            var dims = jsc.getPickerDims(THIS);
+            var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
+            var padToSliderPadding = jsc.getPadToSliderPadding(THIS);
+            var borderRadius = Math.min(
+                THIS.borderRadius,
+                Math.round(THIS.padding * Math.PI)); // px
+            var padCursor = 'crosshair';
+
+            // wrap
+            p.wrap.style.clear = 'both';
+            p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px';
+            p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px';
+            p.wrap.style.zIndex = THIS.zIndex;
+
+            // picker
+            p.box.style.width = dims[0] + 'px';
+            p.box.style.height = dims[1] + 'px';
+
+            p.boxS.style.position = 'absolute';
+            p.boxS.style.left = '0';
+            p.boxS.style.top = '0';
+            p.boxS.style.width = '100%';
+            p.boxS.style.height = '100%';
+            jsc.setBorderRadius(p.boxS, borderRadius + 'px');
+
+            // picker border
+            p.boxB.style.position = 'relative';
+            p.boxB.style.border = THIS.borderWidth + 'px solid';
+            p.boxB.style.borderColor = THIS.borderColor;
+            p.boxB.style.background = THIS.backgroundColor;
+            jsc.setBorderRadius(p.boxB, borderRadius + 'px');
+
+            // IE hack:
+            // If the element is transparent, IE will trigger the event on the elements under it,
+            // e.g. on Canvas or on elements with border
+            p.padM.style.background =
+            p.sldM.style.background =
+                '#FFF';
+            jsc.setStyle(p.padM, 'opacity', '0');
+            jsc.setStyle(p.sldM, 'opacity', '0');
+
+            // pad
+            p.pad.style.position = 'relative';
+            p.pad.style.width = THIS.width + 'px';
+            p.pad.style.height = THIS.height + 'px';
+
+            // pad palettes (HSV and HVS)
+            p.padPal.draw(THIS.width, THIS.height, jsc.getPadYComponent(THIS));
+
+            // pad border
+            p.padB.style.position = 'absolute';
+            p.padB.style.left = THIS.padding + 'px';
+            p.padB.style.top = THIS.padding + 'px';
+            p.padB.style.border = THIS.insetWidth + 'px solid';
+            p.padB.style.borderColor = THIS.insetColor;
+
+            // pad mouse area
+            p.padM._jscInstance = THIS;
+            p.padM._jscControlName = 'pad';
+            p.padM.style.position = 'absolute';
+            p.padM.style.left = '0';
+            p.padM.style.top = '0';
+            p.padM.style.width = (THIS.padding + 2 * THIS.insetWidth + THIS.width + padToSliderPadding / 2) + 'px';
+            p.padM.style.height = dims[1] + 'px';
+            p.padM.style.cursor = padCursor;
+
+            // pad cross
+            p.cross.style.position = 'absolute';
+            p.cross.style.left =
+            p.cross.style.top =
+                '0';
+            p.cross.style.width =
+            p.cross.style.height =
+                crossOuterSize + 'px';
+
+            // pad cross border Y and X
+            p.crossBY.style.position =
+            p.crossBX.style.position =
+                'absolute';
+            p.crossBY.style.background =
+            p.crossBX.style.background =
+                THIS.pointerBorderColor;
+            p.crossBY.style.width =
+            p.crossBX.style.height =
+                (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
+            p.crossBY.style.height =
+            p.crossBX.style.width =
+                crossOuterSize + 'px';
+            p.crossBY.style.left =
+            p.crossBX.style.top =
+                (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px';
+            p.crossBY.style.top =
+            p.crossBX.style.left =
+                '0';
+
+            // pad cross line Y and X
+            p.crossLY.style.position =
+            p.crossLX.style.position =
+                'absolute';
+            p.crossLY.style.background =
+            p.crossLX.style.background =
+                THIS.pointerColor;
+            p.crossLY.style.height =
+            p.crossLX.style.width =
+                (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px';
+            p.crossLY.style.width =
+            p.crossLX.style.height =
+                THIS.pointerThickness + 'px';
+            p.crossLY.style.left =
+            p.crossLX.style.top =
+                (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px';
+            p.crossLY.style.top =
+            p.crossLX.style.left =
+                THIS.pointerBorderWidth + 'px';
+
+            // slider
+            p.sld.style.overflow = 'hidden';
+            p.sld.style.width = THIS.sliderSize + 'px';
+            p.sld.style.height = THIS.height + 'px';
+
+            // slider gradient
+            p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000');
+
+            // slider border
+            p.sldB.style.display = displaySlider ? 'block' : 'none';
+            p.sldB.style.position = 'absolute';
+            p.sldB.style.right = THIS.padding + 'px';
+            p.sldB.style.top = THIS.padding + 'px';
+            p.sldB.style.border = THIS.insetWidth + 'px solid';
+            p.sldB.style.borderColor = THIS.insetColor;
+
+            // slider mouse area
+            p.sldM._jscInstance = THIS;
+            p.sldM._jscControlName = 'sld';
+            p.sldM.style.display = displaySlider ? 'block' : 'none';
+            p.sldM.style.position = 'absolute';
+            p.sldM.style.right = '0';
+            p.sldM.style.top = '0';
+            p.sldM.style.width = (THIS.sliderSize + padToSliderPadding / 2 + THIS.padding + 2 * THIS.insetWidth) + 'px';
+            p.sldM.style.height = dims[1] + 'px';
+            p.sldM.style.cursor = 'default';
+
+            // slider pointer inner and outer border
+            p.sldPtrIB.style.border =
+            p.sldPtrOB.style.border =
+                THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;
+
+            // slider pointer outer border
+            p.sldPtrOB.style.position = 'absolute';
+            p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
+            p.sldPtrOB.style.top = '0';
+
+            // slider pointer middle border
+            p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;
+
+            // slider pointer spacer
+            p.sldPtrS.style.width = THIS.sliderSize + 'px';
+            p.sldPtrS.style.height = sliderPtrSpace + 'px';
+
+            // the Close button
+            function setBtnBorder () {
+                var insetColors = THIS.insetColor.split(/\s+/);
+                var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
+                p.btn.style.borderColor = outsetColor;
+            }
+            p.btn.style.display = THIS.closable ? 'block' : 'none';
+            p.btn.style.position = 'absolute';
+            p.btn.style.left = THIS.padding + 'px';
+            p.btn.style.bottom = THIS.padding + 'px';
+            p.btn.style.padding = '0 15px';
+            p.btn.style.height = THIS.buttonHeight + 'px';
+            p.btn.style.border = THIS.insetWidth + 'px solid';
+            setBtnBorder();
+            p.btn.style.color = THIS.buttonColor;
+            p.btn.style.font = '12px sans-serif';
+            p.btn.style.textAlign = 'center';
+            try {
+                p.btn.style.cursor = 'pointer';
+            } catch(eOldIE) {
+                p.btn.style.cursor = 'hand';
+            }
+            p.btn.onmousedown = function () {
+                THIS.hide();
+            };
+            p.btnT.style.lineHeight = THIS.buttonHeight + 'px';
+            p.btnT.innerHTML = '';
+            p.btnT.appendChild(document.createTextNode(THIS.closeText));
+
+            // place pointers
+            redrawPad();
+            redrawSld();
+
+            // If we are changing the owner without first closing the picker,
+            // make sure to first deal with the old owner
+            if (jsc.picker.owner && jsc.picker.owner !== THIS) {
+                jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass);
+            }
+
+            // Set the new picker owner
+            jsc.picker.owner = THIS;
+
+            // The redrawPosition() method needs picker.owner to be set, that's why we call it here,
+            // after setting the owner
+            if (jsc.isElementType(container, 'body')) {
+                jsc.redrawPosition();
+            } else {
+                jsc._drawPosition(THIS, 0, 0, 'relative', false);
+            }
+
+            if (p.wrap.parentNode != container) {
+                container.appendChild(p.wrap);
+            }
+
+            jsc.setClass(THIS.targetElement, THIS.activeClass);
+        }
+
+
+        function redrawPad () {
+            // redraw the pad pointer
+            switch (jsc.getPadYComponent(THIS)) {
+            case 's': var yComponent = 1; break;
+            case 'v': var yComponent = 2; break;
+            }
+            var x = Math.round((THIS.hsv[0] / 360) * (THIS.width - 1));
+            var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1));
+            var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
+            var ofs = -Math.floor(crossOuterSize / 2);
+            jsc.picker.cross.style.left = (x + ofs) + 'px';
+            jsc.picker.cross.style.top = (y + ofs) + 'px';
+
+            // redraw the slider
+            switch (jsc.getSliderComponent(THIS)) {
+            case 's':
+                var rgb1 = HSV_RGB(THIS.hsv[0], 100, THIS.hsv[2]);
+                var rgb2 = HSV_RGB(THIS.hsv[0], 0, THIS.hsv[2]);
+                var color1 = 'rgb(' +
+                    Math.round(rgb1[0]) + ',' +
+                    Math.round(rgb1[1]) + ',' +
+                    Math.round(rgb1[2]) + ')';
+                var color2 = 'rgb(' +
+                    Math.round(rgb2[0]) + ',' +
+                    Math.round(rgb2[1]) + ',' +
+                    Math.round(rgb2[2]) + ')';
+                jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
+                break;
+            case 'v':
+                var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 100);
+                var color1 = 'rgb(' +
+                    Math.round(rgb[0]) + ',' +
+                    Math.round(rgb[1]) + ',' +
+                    Math.round(rgb[2]) + ')';
+                var color2 = '#000';
+                jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
+                break;
+            }
+        }
+
+
+        function redrawSld () {
+            var sldComponent = jsc.getSliderComponent(THIS);
+            if (sldComponent) {
+                // redraw the slider pointer
+                switch (sldComponent) {
+                case 's': var yComponent = 1; break;
+                case 'v': var yComponent = 2; break;
+                }
+                var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1));
+                jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(sliderPtrSpace / 2)) + 'px';
+            }
+        }
+
+
+        function isPickerOwner () {
+            return jsc.picker && jsc.picker.owner === THIS;
+        }
+
+
+        function blurValue () {
+            THIS.importColor();
+        }
+
+
+        // Find the target element
+        if (typeof targetElement === 'string') {
+            var id = targetElement;
+            var elm = document.getElementById(id);
+            if (elm) {
+                this.targetElement = elm;
+            } else {
+                jsc.warn('Could not find target element with ID \'' + id + '\'');
+            }
+        } else if (targetElement) {
+            this.targetElement = targetElement;
+        } else {
+            jsc.warn('Invalid target element: \'' + targetElement + '\'');
+        }
+
+        if (this.targetElement._jscLinkedInstance) {
+            jsc.warn('Cannot link jscolor twice to the same element. Skipping.');
+            return;
+        }
+        this.targetElement._jscLinkedInstance = this;
+
+        // Find the value element
+        this.valueElement = jsc.fetchElement(this.valueElement);
+        // Find the style element
+        this.styleElement = jsc.fetchElement(this.styleElement);
+
+        var THIS = this;
+        var container =
+            this.container ?
+            jsc.fetchElement(this.container) :
+            document.getElementsByTagName('body')[0];
+        var sliderPtrSpace = 3; // px
+
+        // For BUTTON elements it's important to stop them from sending the form when clicked
+        // (e.g. in Safari)
+        if (jsc.isElementType(this.targetElement, 'button')) {
+            if (this.targetElement.onclick) {
+                var origCallback = this.targetElement.onclick;
+                this.targetElement.onclick = function (evt) {
+                    origCallback.call(this, evt);
+                    return false;
+                };
+            } else {
+                this.targetElement.onclick = function () { return false; };
+            }
+        }
+
+        /*
+        var elm = this.targetElement;
+        do {
+            // If the target element or one of its offsetParents has fixed position,
+            // then use fixed positioning instead
+            //
+            // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
+            // that's why we need to check if the returned style object is non-empty
+            var currStyle = jsc.getStyle(elm);
+            if (currStyle && currStyle.position.toLowerCase() === 'fixed') {
+                this.fixed = true;
+            }
+
+            if (elm !== this.targetElement) {
+                // attach onParentScroll so that we can recompute the picker position
+                // when one of the offsetParents is scrolled
+                if (!elm._jscEventsAttached) {
+                    jsc.attachEvent(elm, 'scroll', jsc.onParentScroll);
+                    elm._jscEventsAttached = true;
+                }
+            }
+        } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body'));
+        */
+
+        // valueElement
+        if (this.valueElement) {
+            if (jsc.isElementType(this.valueElement, 'input')) {
+                var updateField = function () {
+                    THIS.fromString(THIS.valueElement.value, jsc.leaveValue);
+                    jsc.dispatchFineChange(THIS);
+                };
+                jsc.attachEvent(this.valueElement, 'keyup', updateField);
+                jsc.attachEvent(this.valueElement, 'input', updateField);
+                jsc.attachEvent(this.valueElement, 'blur', blurValue);
+                this.valueElement.setAttribute('autocomplete', 'off');
+            }
+        }
+
+        // styleElement
+        if (this.styleElement) {
+            this.styleElement._jscOrigStyle = {
+                backgroundImage : this.styleElement.style.backgroundImage,
+                backgroundColor : this.styleElement.style.backgroundColor,
+                color : this.styleElement.style.color
+            };
+        }
+
+        if (this.value) {
+            // Try to set the color from the .value option and if unsuccessful,
+            // export the current color
+            this.fromString(this.value) || this.exportColor();
+        } else {
+            this.importColor();
+        }
+    }
+
+};
+
+
+//================================
+// Public properties and methods
+//================================
+
+
+// By default, search for all elements with class="jscolor" and install a color picker on them.
+//
+// You can change what class name will be looked for by setting the property jscolor.lookupClass
+// anywhere in your HTML document. To completely disable the automatic lookup, set it to null.
+//
+jsc.jscolor.lookupClass = 'jscolor';
+
+
+jsc.jscolor.installByClassName = function (className) {
+    var inputElms = document.getElementsByTagName('input');
+    var buttonElms = document.getElementsByTagName('button');
+
+    jsc.tryInstallOnElements(inputElms, className);
+    jsc.tryInstallOnElements(buttonElms, className);
+};
+
+
+jsc.register();
+
+
+return jsc.jscolor;
+
+
+})(); }
diff --git a/static/colorfield/jscolor/jscolor.min.js b/static/colorfield/jscolor/jscolor.min.js
new file mode 100755
index 00000000..b80f38eb
--- /dev/null
+++ b/static/colorfield/jscolor/jscolor.min.js
@@ -0,0 +1 @@
+"use strict";if(!window.jscolor){window.jscolor=(function(){var jsc={register:function(){jsc.attachDOMReadyEvent(jsc.init);jsc.attachEvent(document,'mousedown',jsc.onDocumentMouseDown);jsc.attachEvent(document,'touchstart',jsc.onDocumentTouchStart);jsc.attachEvent(window,'resize',jsc.onWindowResize)},init:function(){if(jsc.jscolor.lookupClass){jsc.jscolor.installByClassName(jsc.jscolor.lookupClass)}},tryInstallOnElements:function(elms,className){var matchClass=new RegExp('(^|\\s)('+className+')(\\s*(\\{[^}]*\\})|\\s|$)','i');for(var i=0;i<elms.length;i+=1){if(elms[i].type!==undefined&&elms[i].type.toLowerCase()=='color'){if(jsc.isColorAttrSupported){continue}}var m;if(!elms[i].jscolor&&elms[i].className&&(m=elms[i].className.match(matchClass))){var targetElm=elms[i];var optsStr=null;var dataOptions=jsc.getDataAttr(targetElm,'jscolor');if(dataOptions!==null){optsStr=dataOptions}else if(m[4]){optsStr=m[4]}var opts={};if(optsStr){try{opts=(new Function('return ('+optsStr+')'))()}catch(eParseError){jsc.warn('Error parsing jscolor options: '+eParseError+':\n'+optsStr)}}targetElm.jscolor=new jsc.jscolor(targetElm,opts)}}},isColorAttrSupported:(function(){var elm=document.createElement('input');if(elm.setAttribute){elm.setAttribute('type','color');if(elm.type.toLowerCase()=='color'){return true}}return false})(),isCanvasSupported:(function(){var elm=document.createElement('canvas');return!!(elm.getContext&&elm.getContext('2d'))})(),fetchElement:function(mixed){return typeof mixed==='string'?document.getElementById(mixed):mixed},isElementType:function(elm,type){return elm.nodeName.toLowerCase()===type.toLowerCase()},getDataAttr:function(el,name){var attrName='data-'+name;var attrValue=el.getAttribute(attrName);if(attrValue!==null){return attrValue}return null},attachEvent:function(el,evnt,func){if(el.addEventListener){el.addEventListener(evnt,func,false)}else if(el.attachEvent){el.attachEvent('on'+evnt,func)}},detachEvent:function(el,evnt,func){if(el.removeEventListener){el.removeEventListener(evnt,func,false)}else if(el.detachEvent){el.detachEvent('on'+evnt,func)}},_attachedGroupEvents:{},attachGroupEvent:function(groupName,el,evnt,func){if(!jsc._attachedGroupEvents.hasOwnProperty(groupName)){jsc._attachedGroupEvents[groupName]=[]}jsc._attachedGroupEvents[groupName].push([el,evnt,func]);jsc.attachEvent(el,evnt,func)},detachGroupEvents:function(groupName){if(jsc._attachedGroupEvents.hasOwnProperty(groupName)){for(var i=0;i<jsc._attachedGroupEvents[groupName].length;i+=1){var evt=jsc._attachedGroupEvents[groupName][i];jsc.detachEvent(evt[0],evt[1],evt[2])}delete jsc._attachedGroupEvents[groupName]}},attachDOMReadyEvent:function(func){var fired=false;var fireOnce=function(){if(!fired){fired=true;func()}};if(document.readyState==='complete'){setTimeout(fireOnce,1);return}if(document.addEventListener){document.addEventListener('DOMContentLoaded',fireOnce,false);window.addEventListener('load',fireOnce,false)}else if(document.attachEvent){document.attachEvent('onreadystatechange',function(){if(document.readyState==='complete'){document.detachEvent('onreadystatechange',arguments.callee);fireOnce()}});window.attachEvent('onload',fireOnce);if(document.documentElement.doScroll&&window==window.top){var tryScroll=function(){if(!document.body){return}try{document.documentElement.doScroll('left');fireOnce()}catch(e){setTimeout(tryScroll,1)}};tryScroll()}}},warn:function(msg){if(window.console&&window.console.warn){window.console.warn(msg)}},preventDefault:function(e){if(e.preventDefault){e.preventDefault()}e.returnValue=false},captureTarget:function(target){if(target.setCapture){jsc._capturedTarget=target;jsc._capturedTarget.setCapture()}},releaseTarget:function(){if(jsc._capturedTarget){jsc._capturedTarget.releaseCapture();jsc._capturedTarget=null}},fireEvent:function(el,evnt){if(!el){return}if(document.createEvent){var ev=document.createEvent('HTMLEvents');ev.initEvent(evnt,true,true);el.dispatchEvent(ev)}else if(document.createEventObject){var ev=document.createEventObject();el.fireEvent('on'+evnt,ev)}else if(el['on'+evnt]){el['on'+evnt]()}},classNameToList:function(className){return className.replace(/^\s+|\s+$/g,'').split(/\s+/)},hasClass:function(elm,className){if(!className){return false}return -1!=(' '+elm.className.replace(/\s+/g,' ')+' ').indexOf(' '+className+' ')},setClass:function(elm,className){var classList=jsc.classNameToList(className);for(var i=0;i<classList.length;i+=1){if(!jsc.hasClass(elm,classList[i])){elm.className+=(elm.className?' ':'')+classList[i]}}},unsetClass:function(elm,className){var classList=jsc.classNameToList(className);for(var i=0;i<classList.length;i+=1){var repl=new RegExp('^\\s*'+classList[i]+'\\s*|\\s*'+classList[i]+'\\s*$|\\s+'+classList[i]+'(\\s+)','g');elm.className=elm.className.replace(repl,'$1')}},getStyle:function(elm){return window.getComputedStyle?window.getComputedStyle(elm):elm.currentStyle},setStyle:(function(){var helper=document.createElement('div');var getSupportedProp=function(names){for(var i=0;i<names.length;i+=1){if(names[i]in helper.style){return names[i]}}};var props={borderRadius:getSupportedProp(['borderRadius','MozBorderRadius','webkitBorderRadius']),boxShadow:getSupportedProp(['boxShadow','MozBoxShadow','webkitBoxShadow'])};return function(elm,prop,value){switch(prop.toLowerCase()){case 'opacity':var alphaOpacity=Math.round(parseFloat(value)*100);elm.style.opacity=value;elm.style.filter='alpha(opacity='+alphaOpacity+')';break;default:elm.style[props[prop]]=value;break}}})(),setBorderRadius:function(elm,value){jsc.setStyle(elm,'borderRadius',value||'0')},setBoxShadow:function(elm,value){jsc.setStyle(elm,'boxShadow',value||'none')},getElementPos:function(e,relativeToViewport){var x=0,y=0;var rect=e.getBoundingClientRect();x=rect.left;y=rect.top;if(!relativeToViewport){var viewPos=jsc.getViewPos();x+=viewPos[0];y+=viewPos[1]}return[x,y]},getElementSize:function(e){return[e.offsetWidth,e.offsetHeight]},getAbsPointerPos:function(e){if(!e){e=window.event}var x=0,y=0;if(typeof e.changedTouches!=='undefined'&&e.changedTouches.length){x=e.changedTouches[0].clientX;y=e.changedTouches[0].clientY}else if(typeof e.clientX==='number'){x=e.clientX;y=e.clientY}return{x:x,y:y}},getRelPointerPos:function(e){if(!e){e=window.event}var target=e.target||e.srcElement;var targetRect=target.getBoundingClientRect();var x=0,y=0;var clientX=0,clientY=0;if(typeof e.changedTouches!=='undefined'&&e.changedTouches.length){clientX=e.changedTouches[0].clientX;clientY=e.changedTouches[0].clientY}else if(typeof e.clientX==='number'){clientX=e.clientX;clientY=e.clientY}x=clientX-targetRect.left;y=clientY-targetRect.top;return{x:x,y:y}},getViewPos:function(){var doc=document.documentElement;return[(window.pageXOffset||doc.scrollLeft)-(doc.clientLeft||0),(window.pageYOffset||doc.scrollTop)-(doc.clientTop||0)]},getViewSize:function(){var doc=document.documentElement;return[(window.innerWidth||doc.clientWidth),(window.innerHeight||doc.clientHeight)]},redrawPosition:function(){if(jsc.picker&&jsc.picker.owner){var thisObj=jsc.picker.owner;var tp,vp;if(thisObj.fixed){tp=jsc.getElementPos(thisObj.targetElement,true);vp=[0,0];}else{tp=jsc.getElementPos(thisObj.targetElement);vp=jsc.getViewPos();}var ts=jsc.getElementSize(thisObj.targetElement);var vs=jsc.getViewSize();var ps=jsc.getPickerOuterDims(thisObj);var a,b,c;switch(thisObj.position.toLowerCase()){case 'left':a=1;b=0;c=-1;break;case 'right':a=1;b=0;c=1;break;case 'top':a=0;b=1;c=-1;break;default:a=0;b=1;c=1;break}var l=(ts[b]+ps[b])/2;if(!thisObj.smartPosition){var pp=[tp[a],tp[b]+ts[b]-l+l*c]}else{var pp=[-vp[a]+tp[a]+ps[a]>vs[a]?(-vp[a]+tp[a]+ts[a]/2>vs[a]/2&&tp[a]+ts[a]-ps[a]>=0?tp[a]+ts[a]-ps[a]:tp[a]):tp[a],-vp[b]+tp[b]+ts[b]+ps[b]-l+l*c>vs[b]?(-vp[b]+tp[b]+ts[b]/2>vs[b]/2&&tp[b]+ts[b]-l-l*c>=0?tp[b]+ts[b]-l-l*c:tp[b]+ts[b]-l+l*c):(tp[b]+ts[b]-l+l*c>=0?tp[b]+ts[b]-l+l*c:tp[b]+ts[b]-l-l*c)]}var x=pp[a];var y=pp[b];var positionValue=thisObj.fixed?'fixed':'absolute';var contractShadow=(pp[0]+ps[0]>tp[0]||pp[0]<tp[0]+ts[0])&&(pp[1]+ps[1]<tp[1]+ts[1]);jsc._drawPosition(thisObj,x,y,positionValue,contractShadow)}},_drawPosition:function(thisObj,x,y,positionValue,contractShadow){var vShadow=contractShadow?0:thisObj.shadowBlur;jsc.picker.wrap.style.position=positionValue;jsc.picker.wrap.style.left=x+'px';jsc.picker.wrap.style.top=y+'px';jsc.setBoxShadow(jsc.picker.boxS,thisObj.shadow?new jsc.BoxShadow(0,vShadow,thisObj.shadowBlur,0,thisObj.shadowColor):null)},getPickerDims:function(thisObj){var displaySlider=!!jsc.getSliderComponent(thisObj);var dims=[2*thisObj.insetWidth+2*thisObj.padding+thisObj.width+(displaySlider?2*thisObj.insetWidth+jsc.getPadToSliderPadding(thisObj)+thisObj.sliderSize:0),2*thisObj.insetWidth+2*thisObj.padding+thisObj.height+(thisObj.closable?2*thisObj.insetWidth+thisObj.padding+thisObj.buttonHeight:0)];return dims},getPickerOuterDims:function(thisObj){var dims=jsc.getPickerDims(thisObj);return[dims[0]+2*thisObj.borderWidth,dims[1]+2*thisObj.borderWidth]},getPadToSliderPadding:function(thisObj){return Math.max(thisObj.padding,1.5*(2*thisObj.pointerBorderWidth+thisObj.pointerThickness))},getPadYComponent:function(thisObj){switch(thisObj.mode.charAt(1).toLowerCase()){case 'v':return 'v';break}return 's'},getSliderComponent:function(thisObj){if(thisObj.mode.length>2){switch(thisObj.mode.charAt(2).toLowerCase()){case 's':return 's';break;case 'v':return 'v';break}}return null},onDocumentMouseDown:function(e){if(!e){e=window.event}var target=e.target||e.srcElement;if(target._jscLinkedInstance){if(target._jscLinkedInstance.showOnClick){target._jscLinkedInstance.show()}}else if(target._jscControlName){jsc.onControlPointerStart(e,target,target._jscControlName,'mouse')}else{if(jsc.picker&&jsc.picker.owner){jsc.picker.owner.hide()}}},onDocumentTouchStart:function(e){if(!e){e=window.event}var target=e.target||e.srcElement;if(target._jscLinkedInstance){if(target._jscLinkedInstance.showOnClick){target._jscLinkedInstance.show()}}else if(target._jscControlName){jsc.onControlPointerStart(e,target,target._jscControlName,'touch')}else{if(jsc.picker&&jsc.picker.owner){jsc.picker.owner.hide()}}},onWindowResize:function(e){jsc.redrawPosition()},onParentScroll:function(e){if(jsc.picker&&jsc.picker.owner){jsc.picker.owner.hide()}},_pointerMoveEvent:{mouse:'mousemove',touch:'touchmove'},_pointerEndEvent:{mouse:'mouseup',touch:'touchend'},_pointerOrigin:null,_capturedTarget:null,onControlPointerStart:function(e,target,controlName,pointerType){var thisObj=target._jscInstance;jsc.preventDefault(e);jsc.captureTarget(target);var registerDragEvents=function(doc,offset){jsc.attachGroupEvent('drag',doc,jsc._pointerMoveEvent[pointerType],jsc.onDocumentPointerMove(e,target,controlName,pointerType,offset));jsc.attachGroupEvent('drag',doc,jsc._pointerEndEvent[pointerType],jsc.onDocumentPointerEnd(e,target,controlName,pointerType))};registerDragEvents(document,[0,0]);if(window.parent&&window.frameElement){var rect=window.frameElement.getBoundingClientRect();var ofs=[-rect.left,-rect.top];registerDragEvents(window.parent.window.document,ofs)}var abs=jsc.getAbsPointerPos(e);var rel=jsc.getRelPointerPos(e);jsc._pointerOrigin={x:abs.x-rel.x,y:abs.y-rel.y};switch(controlName){case 'pad':switch(jsc.getSliderComponent(thisObj)){case 's':if(thisObj.hsv[1]===0){thisObj.fromHSV(null,100,null)};break;case 'v':if(thisObj.hsv[2]===0){thisObj.fromHSV(null,null,100)};break}jsc.setPad(thisObj,e,0,0);break;case 'sld':jsc.setSld(thisObj,e,0);break}jsc.dispatchFineChange(thisObj)},onDocumentPointerMove:function(e,target,controlName,pointerType,offset){return function(e){var thisObj=target._jscInstance;switch(controlName){case 'pad':if(!e){e=window.event}jsc.setPad(thisObj,e,offset[0],offset[1]);jsc.dispatchFineChange(thisObj);break;case 'sld':if(!e){e=window.event}jsc.setSld(thisObj,e,offset[1]);jsc.dispatchFineChange(thisObj);break}}},onDocumentPointerEnd:function(e,target,controlName,pointerType){return function(e){var thisObj=target._jscInstance;jsc.detachGroupEvents('drag');jsc.releaseTarget();jsc.dispatchChange(thisObj)}},dispatchChange:function(thisObj){if(thisObj.valueElement){if(jsc.isElementType(thisObj.valueElement,'input')){jsc.fireEvent(thisObj.valueElement,'change')}}},dispatchFineChange:function(thisObj){if(thisObj.onFineChange){var callback;if(typeof thisObj.onFineChange==='string'){callback=new Function(thisObj.onFineChange)}else{callback=thisObj.onFineChange}callback.call(thisObj)}},setPad:function(thisObj,e,ofsX,ofsY){var pointerAbs=jsc.getAbsPointerPos(e);var x=ofsX+pointerAbs.x-jsc._pointerOrigin.x-thisObj.padding-thisObj.insetWidth;var y=ofsY+pointerAbs.y-jsc._pointerOrigin.y-thisObj.padding-thisObj.insetWidth;var xVal=x*(360/(thisObj.width-1));var yVal=100-(y*(100/(thisObj.height-1)));switch(jsc.getPadYComponent(thisObj)){case 's':thisObj.fromHSV(xVal,yVal,null,jsc.leaveSld);break;case 'v':thisObj.fromHSV(xVal,null,yVal,jsc.leaveSld);break}},setSld:function(thisObj,e,ofsY){var pointerAbs=jsc.getAbsPointerPos(e);var y=ofsY+pointerAbs.y-jsc._pointerOrigin.y-thisObj.padding-thisObj.insetWidth;var yVal=100-(y*(100/(thisObj.height-1)));switch(jsc.getSliderComponent(thisObj)){case 's':thisObj.fromHSV(null,yVal,null,jsc.leavePad);break;case 'v':thisObj.fromHSV(null,null,yVal,jsc.leavePad);break}},_vmlNS:'jsc_vml_',_vmlCSS:'jsc_vml_css_',_vmlReady:false,initVML:function(){if(!jsc._vmlReady){var doc=document;if(!doc.namespaces[jsc._vmlNS]){doc.namespaces.add(jsc._vmlNS,'urn:schemas-microsoft-com:vml')}if(!doc.styleSheets[jsc._vmlCSS]){var tags=['shape','shapetype','group','background','path','formulas','handles','fill','stroke','shadow','textbox','textpath','imagedata','line','polyline','curve','rect','roundrect','oval','arc','image'];var ss=doc.createStyleSheet();ss.owningElement.id=jsc._vmlCSS;for(var i=0;i<tags.length;i+=1){ss.addRule(jsc._vmlNS+'\\:'+tags[i],'behavior:url(#default#VML);')}}jsc._vmlReady=true}},createPalette:function(){var paletteObj={elm:null,draw:null};if(jsc.isCanvasSupported){var canvas=document.createElement('canvas');var ctx=canvas.getContext('2d');var drawFunc=function(width,height,type){canvas.width=width;canvas.height=height;ctx.clearRect(0,0,canvas.width,canvas.height);var hGrad=ctx.createLinearGradient(0,0,canvas.width,0);hGrad.addColorStop(0/6,'#F00');hGrad.addColorStop(1/6,'#FF0');hGrad.addColorStop(2/6,'#0F0');hGrad.addColorStop(3/6,'#0FF');hGrad.addColorStop(4/6,'#00F');hGrad.addColorStop(5/6,'#F0F');hGrad.addColorStop(6/6,'#F00');ctx.fillStyle=hGrad;ctx.fillRect(0,0,canvas.width,canvas.height);var vGrad=ctx.createLinearGradient(0,0,0,canvas.height);switch(type.toLowerCase()){case 's':vGrad.addColorStop(0,'rgba(255,255,255,0)');vGrad.addColorStop(1,'rgba(255,255,255,1)');break;case 'v':vGrad.addColorStop(0,'rgba(0,0,0,0)');vGrad.addColorStop(1,'rgba(0,0,0,1)');break}ctx.fillStyle=vGrad;ctx.fillRect(0,0,canvas.width,canvas.height)};paletteObj.elm=canvas;paletteObj.draw=drawFunc}else{jsc.initVML();var vmlContainer=document.createElement('div');vmlContainer.style.position='relative';vmlContainer.style.overflow='hidden';var hGrad=document.createElement(jsc._vmlNS+':fill');hGrad.type='gradient';hGrad.method='linear';hGrad.angle='90';hGrad.colors='16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0';var hRect=document.createElement(jsc._vmlNS+':rect');hRect.style.position='absolute';hRect.style.left=-1+'px';hRect.style.top=-1+'px';hRect.stroked=false;hRect.appendChild(hGrad);vmlContainer.appendChild(hRect);var vGrad=document.createElement(jsc._vmlNS+':fill');vGrad.type='gradient';vGrad.method='linear';vGrad.angle='180';vGrad.opacity='0';var vRect=document.createElement(jsc._vmlNS+':rect');vRect.style.position='absolute';vRect.style.left=-1+'px';vRect.style.top=-1+'px';vRect.stroked=false;vRect.appendChild(vGrad);vmlContainer.appendChild(vRect);var drawFunc=function(width,height,type){vmlContainer.style.width=width+'px';vmlContainer.style.height=height+'px';hRect.style.width=vRect.style.width=(width+1)+'px';hRect.style.height=vRect.style.height=(height+1)+'px';hGrad.color='#F00';hGrad.color2='#F00';switch(type.toLowerCase()){case 's':vGrad.color=vGrad.color2='#FFF';break;case 'v':vGrad.color=vGrad.color2='#000';break}};paletteObj.elm=vmlContainer;paletteObj.draw=drawFunc}return paletteObj},createSliderGradient:function(){var sliderObj={elm:null,draw:null};if(jsc.isCanvasSupported){var canvas=document.createElement('canvas');var ctx=canvas.getContext('2d');var drawFunc=function(width,height,color1,color2){canvas.width=width;canvas.height=height;ctx.clearRect(0,0,canvas.width,canvas.height);var grad=ctx.createLinearGradient(0,0,0,canvas.height);grad.addColorStop(0,color1);grad.addColorStop(1,color2);ctx.fillStyle=grad;ctx.fillRect(0,0,canvas.width,canvas.height)};sliderObj.elm=canvas;sliderObj.draw=drawFunc}else{jsc.initVML();var vmlContainer=document.createElement('div');vmlContainer.style.position='relative';vmlContainer.style.overflow='hidden';var grad=document.createElement(jsc._vmlNS+':fill');grad.type='gradient';grad.method='linear';grad.angle='180';var rect=document.createElement(jsc._vmlNS+':rect');rect.style.position='absolute';rect.style.left=-1+'px';rect.style.top=-1+'px';rect.stroked=false;rect.appendChild(grad);vmlContainer.appendChild(rect);var drawFunc=function(width,height,color1,color2){vmlContainer.style.width=width+'px';vmlContainer.style.height=height+'px';rect.style.width=(width+1)+'px';rect.style.height=(height+1)+'px';grad.color=color1;grad.color2=color2};sliderObj.elm=vmlContainer;sliderObj.draw=drawFunc}return sliderObj},leaveValue:1<<0,leaveStyle:1<<1,leavePad:1<<2,leaveSld:1<<3,BoxShadow:(function(){var BoxShadow=function(hShadow,vShadow,blur,spread,color,inset){this.hShadow=hShadow;this.vShadow=vShadow;this.blur=blur;this.spread=spread;this.color=color;this.inset=!!inset};BoxShadow.prototype.toString=function(){var vals=[Math.round(this.hShadow)+'px',Math.round(this.vShadow)+'px',Math.round(this.blur)+'px',Math.round(this.spread)+'px',this.color];if(this.inset){vals.push('inset')}return vals.join(' ')};return BoxShadow})(),jscolor:function(targetElement,options){this.value=null;this.valueElement=targetElement;this.styleElement=targetElement;this.required=true;this.refine=true;this.hash=false;this.uppercase=true;this.onFineChange=null;this.activeClass='jscolor-active';this.overwriteImportant=false;this.minS=0;this.maxS=100;this.minV=0;this.maxV=100;this.hsv=[0,0,100];this.rgb=[255,255,255];this.width=181;this.height=101;this.showOnClick=true;this.mode='HSV';this.position='bottom';this.smartPosition=true;this.sliderSize=16;this.crossSize=8;this.closable=false;this.closeText='Close';this.buttonColor='#000000';this.buttonHeight=18;this.padding=12;this.backgroundColor='#FFFFFF';this.borderWidth=1;this.borderColor='#BBBBBB';this.borderRadius=8;this.insetWidth=1;this.insetColor='#BBBBBB';this.shadow=true;this.shadowBlur=15;this.shadowColor='rgba(0,0,0,0.2)';this.pointerColor='#4C4C4C';this.pointerBorderColor='#FFFFFF';this.pointerBorderWidth=1;this.pointerThickness=2;this.zIndex=1000;this.container=null;for(var opt in options){if(options.hasOwnProperty(opt)){this[opt]=options[opt]}}this.hide=function(){if(isPickerOwner()){detachPicker()}};this.show=function(){drawPicker()};this.redraw=function(){if(isPickerOwner()){drawPicker()}};this.importColor=function(){if(!this.valueElement){this.exportColor()}else{if(jsc.isElementType(this.valueElement,'input')){if(!this.refine){if(!this.fromString(this.valueElement.value,jsc.leaveValue)){if(this.styleElement){this.styleElement.style.backgroundImage=this.styleElement._jscOrigStyle.backgroundImage;this.styleElement.style.backgroundColor=this.styleElement._jscOrigStyle.backgroundColor;this.styleElement.style.color=this.styleElement._jscOrigStyle.color}this.exportColor(jsc.leaveValue|jsc.leaveStyle)}}else if(!this.required&&/^\s*$/.test(this.valueElement.value)){this.valueElement.value='';if(this.styleElement){this.styleElement.style.backgroundImage=this.styleElement._jscOrigStyle.backgroundImage;this.styleElement.style.backgroundColor=this.styleElement._jscOrigStyle.backgroundColor;this.styleElement.style.color=this.styleElement._jscOrigStyle.color}this.exportColor(jsc.leaveValue|jsc.leaveStyle)}else if(this.fromString(this.valueElement.value)){}else{this.exportColor()}}else{this.exportColor()}}};this.exportColor=function(flags){if(!(flags&jsc.leaveValue)&&this.valueElement){var value=this.toString();if(this.uppercase){value=value.toUpperCase()}if(this.hash){value='#'+value}if(jsc.isElementType(this.valueElement,'input')){this.valueElement.value=value}else{this.valueElement.innerHTML=value}}if(!(flags&jsc.leaveStyle)){if(this.styleElement){var bgColor='#'+this.toString();var fgColor=this.isLight()?'#000':'#FFF';this.styleElement.style.backgroundImage='none';this.styleElement.style.backgroundColor=bgColor;this.styleElement.style.color=fgColor;if(this.overwriteImportant){this.styleElement.setAttribute('style','background: '+bgColor+' !important; color: '+fgColor+' !important;')}}}if(!(flags&jsc.leavePad)&&isPickerOwner()){redrawPad()}if(!(flags&jsc.leaveSld)&&isPickerOwner()){redrawSld()}};this.fromHSV=function(h,s,v,flags){if(h!==null){if(isNaN(h)){return false}h=Math.max(0,Math.min(360,h))}if(s!==null){if(isNaN(s)){return false}s=Math.max(0,Math.min(100,this.maxS,s),this.minS)}if(v!==null){if(isNaN(v)){return false}v=Math.max(0,Math.min(100,this.maxV,v),this.minV)}this.rgb=HSV_RGB(h===null?this.hsv[0]:(this.hsv[0]=h),s===null?this.hsv[1]:(this.hsv[1]=s),v===null?this.hsv[2]:(this.hsv[2]=v));this.exportColor(flags)};this.fromRGB=function(r,g,b,flags){if(r!==null){if(isNaN(r)){return false}r=Math.max(0,Math.min(255,r))}if(g!==null){if(isNaN(g)){return false}g=Math.max(0,Math.min(255,g))}if(b!==null){if(isNaN(b)){return false}b=Math.max(0,Math.min(255,b))}var hsv=RGB_HSV(r===null?this.rgb[0]:r,g===null?this.rgb[1]:g,b===null?this.rgb[2]:b);if(hsv[0]!==null){this.hsv[0]=Math.max(0,Math.min(360,hsv[0]))}if(hsv[2]!==0){this.hsv[1]=hsv[1]===null?null:Math.max(0,this.minS,Math.min(100,this.maxS,hsv[1]))}this.hsv[2]=hsv[2]===null?null:Math.max(0,this.minV,Math.min(100,this.maxV,hsv[2]));var rgb=HSV_RGB(this.hsv[0],this.hsv[1],this.hsv[2]);this.rgb[0]=rgb[0];this.rgb[1]=rgb[1];this.rgb[2]=rgb[2];this.exportColor(flags)};this.fromString=function(str,flags){var m;if(m=str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)){if(m[1].length===6){this.fromRGB(parseInt(m[1].substr(0,2),16),parseInt(m[1].substr(2,2),16),parseInt(m[1].substr(4,2),16),flags)}else{this.fromRGB(parseInt(m[1].charAt(0)+m[1].charAt(0),16),parseInt(m[1].charAt(1)+m[1].charAt(1),16),parseInt(m[1].charAt(2)+m[1].charAt(2),16),flags)}return true}else if(m=str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)){var params=m[1].split(',');var re=/^\s*(\d*)(\.\d+)?\s*$/;var mR,mG,mB;if(params.length>=3&&(mR=params[0].match(re))&&(mG=params[1].match(re))&&(mB=params[2].match(re))){var r=parseFloat((mR[1]||'0')+(mR[2]||''));var g=parseFloat((mG[1]||'0')+(mG[2]||''));var b=parseFloat((mB[1]||'0')+(mB[2]||''));this.fromRGB(r,g,b,flags);return true}}return false};this.toString=function(){return((0x100|Math.round(this.rgb[0])).toString(16).substr(1)+(0x100|Math.round(this.rgb[1])).toString(16).substr(1)+(0x100|Math.round(this.rgb[2])).toString(16).substr(1))};this.toHEXString=function(){return '#'+this.toString().toUpperCase()};this.toRGBString=function(){return('rgb('+Math.round(this.rgb[0])+','+Math.round(this.rgb[1])+','+Math.round(this.rgb[2])+')')};this.isLight=function(){return(0.213*this.rgb[0]+0.715*this.rgb[1]+0.072*this.rgb[2]>255/2)};this._processParentElementsInDOM=function(){if(this._linkedElementsProcessed){return}this._linkedElementsProcessed=true;var elm=this.targetElement;do{var currStyle=jsc.getStyle(elm);if(currStyle&&currStyle.position.toLowerCase()==='fixed'){this.fixed=true}if(elm!==this.targetElement){if(!elm._jscEventsAttached){jsc.attachEvent(elm,'scroll',jsc.onParentScroll);elm._jscEventsAttached=true}}}while((elm=elm.parentNode)&&!jsc.isElementType(elm,'body'))};function RGB_HSV(r,g,b){r/=255;g/=255;b/=255;var n=Math.min(Math.min(r,g),b);var v=Math.max(Math.max(r,g),b);var m=v-n;if(m===0){return[null,0,100*v]}var h=r===n?3+(b-g)/m:(g===n?5+(r-b)/m:1+(g-r)/m);return[60*(h===6?0:h),100*(m/v),100*v]}function HSV_RGB(h,s,v){var u=255*(v/100);if(h===null){return[u,u,u]}h/=60;s/=100;var i=Math.floor(h);var f=i%2?h-i:1-(h-i);var m=u*(1-s);var n=u*(1-s*f);switch(i){case 6:case 0:return[u,n,m];case 1:return[n,u,m];case 2:return[m,u,n];case 3:return[m,n,u];case 4:return[n,m,u];case 5:return[u,m,n]}}function detachPicker(){jsc.unsetClass(THIS.targetElement,THIS.activeClass);jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);delete jsc.picker.owner}function drawPicker(){THIS._processParentElementsInDOM();if(!jsc.picker){jsc.picker={owner:null,wrap:document.createElement('div'),box:document.createElement('div'),boxS:document.createElement('div'),boxB:document.createElement('div'),pad:document.createElement('div'),padB:document.createElement('div'),padM:document.createElement('div'),padPal:jsc.createPalette(),cross:document.createElement('div'),crossBY:document.createElement('div'),crossBX:document.createElement('div'),crossLY:document.createElement('div'),crossLX:document.createElement('div'),sld:document.createElement('div'),sldB:document.createElement('div'),sldM:document.createElement('div'),sldGrad:jsc.createSliderGradient(),sldPtrS:document.createElement('div'),sldPtrIB:document.createElement('div'),sldPtrMB:document.createElement('div'),sldPtrOB:document.createElement('div'),btn:document.createElement('div'),btnT:document.createElement('span')};jsc.picker.pad.appendChild(jsc.picker.padPal.elm);jsc.picker.padB.appendChild(jsc.picker.pad);jsc.picker.cross.appendChild(jsc.picker.crossBY);jsc.picker.cross.appendChild(jsc.picker.crossBX);jsc.picker.cross.appendChild(jsc.picker.crossLY);jsc.picker.cross.appendChild(jsc.picker.crossLX);jsc.picker.padB.appendChild(jsc.picker.cross);jsc.picker.box.appendChild(jsc.picker.padB);jsc.picker.box.appendChild(jsc.picker.padM);jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);jsc.picker.sldB.appendChild(jsc.picker.sld);jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);jsc.picker.box.appendChild(jsc.picker.sldB);jsc.picker.box.appendChild(jsc.picker.sldM);jsc.picker.btn.appendChild(jsc.picker.btnT);jsc.picker.box.appendChild(jsc.picker.btn);jsc.picker.boxB.appendChild(jsc.picker.box);jsc.picker.wrap.appendChild(jsc.picker.boxS);jsc.picker.wrap.appendChild(jsc.picker.boxB)}var p=jsc.picker;var displaySlider=!!jsc.getSliderComponent(THIS);var dims=jsc.getPickerDims(THIS);var crossOuterSize=(2*THIS.pointerBorderWidth+THIS.pointerThickness+2*THIS.crossSize);var padToSliderPadding=jsc.getPadToSliderPadding(THIS);var borderRadius=Math.min(THIS.borderRadius,Math.round(THIS.padding*Math.PI));var padCursor='crosshair';p.wrap.style.clear='both';p.wrap.style.width=(dims[0]+2*THIS.borderWidth)+'px';p.wrap.style.height=(dims[1]+2*THIS.borderWidth)+'px';p.wrap.style.zIndex=THIS.zIndex;p.box.style.width=dims[0]+'px';p.box.style.height=dims[1]+'px';p.boxS.style.position='absolute';p.boxS.style.left='0';p.boxS.style.top='0';p.boxS.style.width='100%';p.boxS.style.height='100%';jsc.setBorderRadius(p.boxS,borderRadius+'px');p.boxB.style.position='relative';p.boxB.style.border=THIS.borderWidth+'px solid';p.boxB.style.borderColor=THIS.borderColor;p.boxB.style.background=THIS.backgroundColor;jsc.setBorderRadius(p.boxB,borderRadius+'px');p.padM.style.background=p.sldM.style.background='#FFF';jsc.setStyle(p.padM,'opacity','0');jsc.setStyle(p.sldM,'opacity','0');p.pad.style.position='relative';p.pad.style.width=THIS.width+'px';p.pad.style.height=THIS.height+'px';p.padPal.draw(THIS.width,THIS.height,jsc.getPadYComponent(THIS));p.padB.style.position='absolute';p.padB.style.left=THIS.padding+'px';p.padB.style.top=THIS.padding+'px';p.padB.style.border=THIS.insetWidth+'px solid';p.padB.style.borderColor=THIS.insetColor;p.padM._jscInstance=THIS;p.padM._jscControlName='pad';p.padM.style.position='absolute';p.padM.style.left='0';p.padM.style.top='0';p.padM.style.width=(THIS.padding+2*THIS.insetWidth+THIS.width+padToSliderPadding/2)+'px';p.padM.style.height=dims[1]+'px';p.padM.style.cursor=padCursor;p.cross.style.position='absolute';p.cross.style.left=p.cross.style.top='0';p.cross.style.width=p.cross.style.height=crossOuterSize+'px';p.crossBY.style.position=p.crossBX.style.position='absolute';p.crossBY.style.background=p.crossBX.style.background=THIS.pointerBorderColor;p.crossBY.style.width=p.crossBX.style.height=(2*THIS.pointerBorderWidth+THIS.pointerThickness)+'px';p.crossBY.style.height=p.crossBX.style.width=crossOuterSize+'px';p.crossBY.style.left=p.crossBX.style.top=(Math.floor(crossOuterSize/2)-Math.floor(THIS.pointerThickness/2)-THIS.pointerBorderWidth)+'px';p.crossBY.style.top=p.crossBX.style.left='0';p.crossLY.style.position=p.crossLX.style.position='absolute';p.crossLY.style.background=p.crossLX.style.background=THIS.pointerColor;p.crossLY.style.height=p.crossLX.style.width=(crossOuterSize-2*THIS.pointerBorderWidth)+'px';p.crossLY.style.width=p.crossLX.style.height=THIS.pointerThickness+'px';p.crossLY.style.left=p.crossLX.style.top=(Math.floor(crossOuterSize/2)-Math.floor(THIS.pointerThickness/2))+'px';p.crossLY.style.top=p.crossLX.style.left=THIS.pointerBorderWidth+'px';p.sld.style.overflow='hidden';p.sld.style.width=THIS.sliderSize+'px';p.sld.style.height=THIS.height+'px';p.sldGrad.draw(THIS.sliderSize,THIS.height,'#000','#000');p.sldB.style.display=displaySlider?'block':'none';p.sldB.style.position='absolute';p.sldB.style.right=THIS.padding+'px';p.sldB.style.top=THIS.padding+'px';p.sldB.style.border=THIS.insetWidth+'px solid';p.sldB.style.borderColor=THIS.insetColor;p.sldM._jscInstance=THIS;p.sldM._jscControlName='sld';p.sldM.style.display=displaySlider?'block':'none';p.sldM.style.position='absolute';p.sldM.style.right='0';p.sldM.style.top='0';p.sldM.style.width=(THIS.sliderSize+padToSliderPadding/2+THIS.padding+2*THIS.insetWidth)+'px';p.sldM.style.height=dims[1]+'px';p.sldM.style.cursor='default';p.sldPtrIB.style.border=p.sldPtrOB.style.border=THIS.pointerBorderWidth+'px solid '+THIS.pointerBorderColor;p.sldPtrOB.style.position='absolute';p.sldPtrOB.style.left= -(2*THIS.pointerBorderWidth+THIS.pointerThickness)+'px';p.sldPtrOB.style.top='0';p.sldPtrMB.style.border=THIS.pointerThickness+'px solid '+THIS.pointerColor;p.sldPtrS.style.width=THIS.sliderSize+'px';p.sldPtrS.style.height=sliderPtrSpace+'px';function setBtnBorder(){var insetColors=THIS.insetColor.split(/\s+/);var outsetColor=insetColors.length<2?insetColors[0]:insetColors[1]+' '+insetColors[0]+' '+insetColors[0]+' '+insetColors[1];p.btn.style.borderColor=outsetColor}p.btn.style.display=THIS.closable?'block':'none';p.btn.style.position='absolute';p.btn.style.left=THIS.padding+'px';p.btn.style.bottom=THIS.padding+'px';p.btn.style.padding='0 15px';p.btn.style.height=THIS.buttonHeight+'px';p.btn.style.border=THIS.insetWidth+'px solid';setBtnBorder();p.btn.style.color=THIS.buttonColor;p.btn.style.font='12px sans-serif';p.btn.style.textAlign='center';try{p.btn.style.cursor='pointer'}catch(eOldIE){p.btn.style.cursor='hand'}p.btn.onmousedown=function(){THIS.hide()};p.btnT.style.lineHeight=THIS.buttonHeight+'px';p.btnT.innerHTML='';p.btnT.appendChild(document.createTextNode(THIS.closeText));redrawPad();redrawSld();if(jsc.picker.owner&&jsc.picker.owner!==THIS){jsc.unsetClass(jsc.picker.owner.targetElement,THIS.activeClass)}jsc.picker.owner=THIS;if(jsc.isElementType(container,'body')){jsc.redrawPosition()}else{jsc._drawPosition(THIS,0,0,'relative',false)}if(p.wrap.parentNode!=container){container.appendChild(p.wrap)}jsc.setClass(THIS.targetElement,THIS.activeClass)}function redrawPad(){switch(jsc.getPadYComponent(THIS)){case 's':var yComponent=1;break;case 'v':var yComponent=2;break}var x=Math.round((THIS.hsv[0]/360)*(THIS.width-1));var y=Math.round((1-THIS.hsv[yComponent]/100)*(THIS.height-1));var crossOuterSize=(2*THIS.pointerBorderWidth+THIS.pointerThickness+2*THIS.crossSize);var ofs= -Math.floor(crossOuterSize/2);jsc.picker.cross.style.left=(x+ofs)+'px';jsc.picker.cross.style.top=(y+ofs)+'px';switch(jsc.getSliderComponent(THIS)){case 's':var rgb1=HSV_RGB(THIS.hsv[0],100,THIS.hsv[2]);var rgb2=HSV_RGB(THIS.hsv[0],0,THIS.hsv[2]);var color1='rgb('+Math.round(rgb1[0])+','+Math.round(rgb1[1])+','+Math.round(rgb1[2])+')';var color2='rgb('+Math.round(rgb2[0])+','+Math.round(rgb2[1])+','+Math.round(rgb2[2])+')';jsc.picker.sldGrad.draw(THIS.sliderSize,THIS.height,color1,color2);break;case 'v':var rgb=HSV_RGB(THIS.hsv[0],THIS.hsv[1],100);var color1='rgb('+Math.round(rgb[0])+','+Math.round(rgb[1])+','+Math.round(rgb[2])+')';var color2='#000';jsc.picker.sldGrad.draw(THIS.sliderSize,THIS.height,color1,color2);break}}function redrawSld(){var sldComponent=jsc.getSliderComponent(THIS);if(sldComponent){switch(sldComponent){case 's':var yComponent=1;break;case 'v':var yComponent=2;break}var y=Math.round((1-THIS.hsv[yComponent]/100)*(THIS.height-1));jsc.picker.sldPtrOB.style.top=(y-(2*THIS.pointerBorderWidth+THIS.pointerThickness)-Math.floor(sliderPtrSpace/2))+'px'}}function isPickerOwner(){return jsc.picker&&jsc.picker.owner===THIS}function blurValue(){THIS.importColor()}if(typeof targetElement==='string'){var id=targetElement;var elm=document.getElementById(id);if(elm){this.targetElement=elm}else{jsc.warn('Could not find target element with ID \''+id+'\'')}}else if(targetElement){this.targetElement=targetElement}else{jsc.warn('Invalid target element: \''+targetElement+'\'')}if(this.targetElement._jscLinkedInstance){jsc.warn('Cannot link jscolor twice to the same element. Skipping.');return}this.targetElement._jscLinkedInstance=this;this.valueElement=jsc.fetchElement(this.valueElement);this.styleElement=jsc.fetchElement(this.styleElement);var THIS=this;var container=this.container?jsc.fetchElement(this.container):document.getElementsByTagName('body')[0];var sliderPtrSpace=3;if(jsc.isElementType(this.targetElement,'button')){if(this.targetElement.onclick){var origCallback=this.targetElement.onclick;this.targetElement.onclick=function(evt){origCallback.call(this,evt);return false}}else{this.targetElement.onclick=function(){return false}}}if(this.valueElement){if(jsc.isElementType(this.valueElement,'input')){var updateField=function(){THIS.fromString(THIS.valueElement.value,jsc.leaveValue);jsc.dispatchFineChange(THIS)};jsc.attachEvent(this.valueElement,'keyup',updateField);jsc.attachEvent(this.valueElement,'input',updateField);jsc.attachEvent(this.valueElement,'blur',blurValue);this.valueElement.setAttribute('autocomplete','off')}}if(this.styleElement){this.styleElement._jscOrigStyle={backgroundImage:this.styleElement.style.backgroundImage,backgroundColor:this.styleElement.style.backgroundColor,color:this.styleElement.style.color}}if(this.value){this.fromString(this.value)||this.exportColor()}else{this.importColor()}}};jsc.jscolor.lookupClass='jscolor';jsc.jscolor.installByClassName=function(className){var inputElms=document.getElementsByTagName('input');var buttonElms=document.getElementsByTagName('button');jsc.tryInstallOnElements(inputElms,className);jsc.tryInstallOnElements(buttonElms,className)};jsc.register();return jsc.jscolor})()}
\ No newline at end of file
diff --git a/templates/colorfield/color.html b/templates/colorfield/color.html
new file mode 100755
index 00000000..27433598
--- /dev/null
+++ b/templates/colorfield/color.html
@@ -0,0 +1,8 @@
+<input type="text"
+ id="{{ widget.attrs.id }}"
+ class="form-control colorfield_field jscolor"
+ name="{{ widget.name }}"
+ value="{% firstof widget.value widget.attrs.default '' %}"
+ placeholder="{% firstof widget.value widget.attrs.default '' %}"
+ data-jscolor="{hash:true,width:225,height:150,required:{% if widget.attrs.required %}true{% else %}false{% endif %}}"
+ {% if widget.attrs.required %}required{% endif %} />
diff --git a/templates/wei/bus_detail.html b/templates/wei/bus_detail.html
new file mode 100644
index 00000000..1b335be8
--- /dev/null
+++ b/templates/wei/bus_detail.html
@@ -0,0 +1,9 @@
+{% extends "member/noteowner_detail.html" %}
+
+{% block profile_info %}
+{% include "wei/weiclub_info.html" %}
+{% endblock %}
+
+{% block profile_content %}
+{% include "wei/bus_tables.html" %}
+{% endblock %}
diff --git a/templates/wei/bus_form.html b/templates/wei/bus_form.html
index 7fa2b01c..4c7b22ce 100644
--- a/templates/wei/bus_form.html
+++ b/templates/wei/bus_form.html
@@ -3,9 +3,7 @@
 {% load i18n %}
 
 {% block profile_info %}
-    {% if club %}
-        {% include "wei/weiclub_info.html" %}
-    {% endif %}
+    {% include "wei/weiclub_info.html" %}
 {% endblock %}
 
 {% block profile_content %}
diff --git a/templates/wei/bus_tables.html b/templates/wei/bus_tables.html
new file mode 100644
index 00000000..e567c5f3
--- /dev/null
+++ b/templates/wei/bus_tables.html
@@ -0,0 +1,33 @@
+{% load render_table from django_tables2 %}
+{% load i18n %}
+
+<div class="card">
+    <div class="card-header text-center">
+        <h4>{{ object.name }}</h4>
+    </div>
+
+
+    <div class="card-body">
+        {{ object.description }}
+    </div>
+
+    <div class="card-footer text-center">
+        <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus' pk=object.pk %}">{% trans "Edit" %}</a>
+        <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:add_team' pk=object.pk %}">{% trans "Add team" %}</a>
+    </div>
+</div>
+
+<hr>
+
+{% if teams.data or True %}
+    <div class="card">
+        <div class="card-header position-relative" id="clubListHeading">
+            <a class="btn btn-link stretched-link font-weight-bold">
+                <i class="fa fa-bus"></i> {% trans "Teams" %}
+            </a>
+        </div>
+        {% render_table teams %}
+    </div>
+
+    <hr>
+{% endif %}
diff --git a/templates/wei/busteam_form.html b/templates/wei/busteam_form.html
new file mode 100644
index 00000000..4c7b22ce
--- /dev/null
+++ b/templates/wei/busteam_form.html
@@ -0,0 +1,15 @@
+{% extends "member/noteowner_detail.html" %}
+{% load crispy_forms_tags %}
+{% load i18n %}
+
+{% block profile_info %}
+    {% include "wei/weiclub_info.html" %}
+{% endblock %}
+
+{% block profile_content %}
+<form method="post">
+{% csrf_token %}
+{{ form|crispy }}
+<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
+</form>
+{% endblock %}
diff --git a/templates/wei/weiclub_tables.html b/templates/wei/weiclub_tables.html
index 50fb1a81..3f513d2f 100644
--- a/templates/wei/weiclub_tables.html
+++ b/templates/wei/weiclub_tables.html
@@ -59,7 +59,7 @@
 
 <hr>
 
-{% if buses.data or True %}
+{% if buses.data %}
     <div class="card">
         <div class="card-header position-relative" id="clubListHeading">
             <a class="btn btn-link stretched-link font-weight-bold">
-- 
GitLab