Preserving the ordering in selectmultiple

This commit is contained in:
Maxime Beauchemin 2015-11-24 10:52:32 -08:00
parent de52449c2a
commit c8faeed5b3
6 changed files with 198 additions and 9 deletions

View File

@ -1,22 +1,40 @@
from wtforms import ( from wtforms import (
Field, Form, SelectMultipleField, SelectField, TextField, TextAreaField, Field, Form, SelectMultipleField, SelectField, TextField, TextAreaField,
BooleanField, IntegerField, HiddenField) BooleanField, IntegerField, HiddenField)
from wtforms import validators from wtforms import validators, widgets
from wtforms.widgets import HTMLString
from copy import copy from copy import copy
from panoramix import app from panoramix import app
from six import string_types from six import string_types
from collections import OrderedDict
config = app.config config = app.config
# Fixes behavior of html forms omitting non checked <input>
# (which doesn't distinguish False from NULL/missing )
# If value is unchecked, this hidden <input> fills in False value
class BetterBooleanField(BooleanField): class BetterBooleanField(BooleanField):
"""
Fixes behavior of html forms omitting non checked <input>
(which doesn't distinguish False from NULL/missing )
If value is unchecked, this hidden <input> fills in False value
"""
def __call__(self, **kwargs): def __call__(self, **kwargs):
html = super(BetterBooleanField, self).__call__(**kwargs) html = super(BetterBooleanField, self).__call__(**kwargs)
html += u'<input type="hidden" name="show_brush" value="false">' html += u'<input type="hidden" name="show_brush" value="false">'
return HTMLString(html) return widgets.HTMLString(html)
class BetterSelectMultipleField(SelectMultipleField):
"""
Works along with select2sortable to preserves the sort order
"""
def iter_choices(self):
d = OrderedDict()
for value, label in self.choices:
selected = self.data is not None and self.coerce(value) in self.data
d[value] = (value, label, selected)
if self.data:
for value in self.data:
yield d.pop(value)
while d:
yield d.pop(d.keys()[0])
class OmgWtForm(Form): class OmgWtForm(Form):
@ -61,7 +79,7 @@ class FormFactory(object):
default='table', default='table',
choices=[(k, v.verbose_name) for k, v in viz_types.items()], choices=[(k, v.verbose_name) for k, v in viz_types.items()],
description="The type of visualization to display"), description="The type of visualization to display"),
'metrics': SelectMultipleField( 'metrics': BetterSelectMultipleField(
'Metrics', choices=datasource.metrics_combo, 'Metrics', choices=datasource.metrics_combo,
default=[default_metric], default=[default_metric],
description="One or many metrics to display"), description="One or many metrics to display"),
@ -69,7 +87,7 @@ class FormFactory(object):
'Metric', choices=datasource.metrics_combo, 'Metric', choices=datasource.metrics_combo,
default=default_metric, default=default_metric,
description="One or many metrics to display"), description="One or many metrics to display"),
'groupby': SelectMultipleField( 'groupby': BetterSelectMultipleField(
'Group by', 'Group by',
choices=self.choicify(datasource.groupby_column_names), choices=self.choicify(datasource.groupby_column_names),
description="One or many fields to group by"), description="One or many fields to group by"),
@ -221,7 +239,7 @@ class FormFactory(object):
datasource = viz.datasource datasource = viz.datasource
field_css_classes = {k: ['form-control'] for k in px_form_fields.keys()} field_css_classes = {k: ['form-control'] for k in px_form_fields.keys()}
select2 = [ select2 = [
'viz_type', 'metrics', 'groupby', 'viz_type', 'groupby',
'row_limit', 'rolling_type', 'series', 'row_limit', 'rolling_type', 'series',
'entity', 'x', 'y', 'size', 'rotation', 'metric', 'limit', 'entity', 'x', 'y', 'size', 'rotation', 'metric', 'limit',
'markup_type',] 'markup_type',]
@ -232,6 +250,7 @@ class FormFactory(object):
field_css_classes[field] += ['input-sm'] field_css_classes[field] += ['input-sm']
for field in select2: for field in select2:
field_css_classes[field] += ['select2'] field_css_classes[field] += ['select2']
field_css_classes['metrics'] += ['select2Sortable']
class QueryForm(OmgWtForm): class QueryForm(OmgWtForm):

7
panoramix/static/lib/jquery-ui.min.css vendored Executable file

File diff suppressed because one or more lines are too long

13
panoramix/static/lib/jquery-ui.min.js vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,146 @@
/**
* jQuery Select2 Sortable
* - enable select2 to be sortable via normal select element
*
* author : Vafour
* modified : Kevin Provance (kprovance)
* inspired by : jQuery Chosen Sortable (https://github.com/mrhenry/jquery-chosen-sortable)
* License : GPL
*/
(function ($) {
$.fn.extend({
select2SortableOrder: function () {
var $this = this.filter('[multiple]');
$this.each(function () {
var $select = $(this);
// skip elements not select2-ed
if (typeof ($select.data('select2')) !== 'object') {
return false;
}
var $select2 = $select.siblings('.select2-container');
var sorted;
// Opt group names
var optArr = [];
$select.find('optgroup').each(function(idx, val) {
optArr.push (val);
});
$select.find('option').each(function(idx, val) {
var groupName = $(this).parent('optgroup').prop('label');
var optVal = this;
if (groupName === undefined) {
if (this.value !== '' && !this.selected) {
optArr.push (optVal);
}
}
});
sorted = $($select2.find('.select2-choices li[class!="select2-search-field"]').map(function () {
if (!this) {
return undefined;
}
var id = $(this).data('select2Data').id;
return $select.find('option[value="' + id + '"]')[0];
}));
sorted.push.apply(sorted, optArr);
$select.children().remove();
$select.append(sorted);
});
return $this;
},
select2Sortable: function () {
var args = Array.prototype.slice.call(arguments, 0);
$this = this.filter('[multiple]'),
validMethods = ['destroy'];
if (args.length === 0 || typeof (args[0]) === 'object') {
var defaultOptions = {
bindOrder: 'formSubmit', // or sortableStop
sortableOptions: {
placeholder: 'ui-state-highlight',
items: 'li:not(.select2-search-field)',
tolerance: 'pointer'
}
};
var options = $.extend(defaultOptions, args[0]);
// Init select2 only if not already initialized to prevent select2 configuration loss
if (typeof ($this.data('select2')) !== 'object') {
$this.select2();
}
$this.each(function () {
var $select = $(this)
var $select2choices = $select.siblings('.select2-container').find('.select2-choices');
// Init jQuery UI Sortable
$select2choices.sortable(options.sortableOptions);
switch (options.bindOrder) {
case 'sortableStop':
// apply options ordering in sortstop event
$select2choices.on("sortstop.select2sortable", function (event, ui) {
$select.select2SortableOrder();
});
$select.on('change', function (e) {
$(this).select2SortableOrder();
});
break;
default:
// apply options ordering in form submit
$select.closest('form').unbind('submit.select2sortable').on('submit.select2sortable', function () {
$select.select2SortableOrder();
});
break;
}
});
}
else if (typeof (args[0] === 'string')) {
if ($.inArray(args[0], validMethods) == -1) {
throw "Unknown method: " + args[0];
}
if (args[0] === 'destroy') {
$this.select2SortableDestroy();
}
}
return $this;
},
select2SortableDestroy: function () {
var $this = this.filter('[multiple]');
$this.each(function () {
var $select = $(this)
var $select2choices = $select.parent().find('.select2-choices');
// unbind form submit event
$select.closest('form').unbind('submit.select2sortable');
// unbind sortstop event
$select2choices.unbind("sortstop.select2sortable");
// destroy select2Sortable
$select2choices.sortable('destroy');
});
return $this;
}
});
}(jQuery));

View File

@ -29,6 +29,7 @@ function initializeDatasourceView() {
} }
$(".select2").select2(); $(".select2").select2();
$(".select2Sortable").select2Sortable();
$("form").show(); $("form").show();
$('[data-toggle="tooltip"]').tooltip({container: 'body'}); $('[data-toggle="tooltip"]').tooltip({container: 'body'});

View File

@ -4,9 +4,12 @@
{{super()}} {{super()}}
<link rel="shortcut icon" href="{{ url_for('static', filename='chaudron.png') }}" /> <link rel="shortcut icon" href="{{ url_for('static', filename='chaudron.png') }}" />
<link rel="stylesheet" type="text/css" href="/static/panoramix.css" /> <link rel="stylesheet" type="text/css" href="/static/panoramix.css" />
<link rel="stylesheet" type="text/css" href="/static/lib/select2.sortable.css" />
{% endblock %} {% endblock %}
{% block tail_js %} {% block tail_js %}
{{ super() }} {{ super() }}
<script src="/static/panoramix.js"></script> <script src="/static/panoramix.js"></script>
<script src="/static/lib/jquery-ui.min.js"></script>
<script src="/static/lib/select2.sortable.js"></script>
{% endblock %} {% endblock %}