Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to use form_ajax_refs fields as editable columns #2164

Closed
wants to merge 10 commits into from
Closed
36 changes: 35 additions & 1 deletion flask_admin/model/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,46 @@ class XEditableWidget(object):
def __call__(self, field, **kwargs):
display_value = kwargs.pop('display_value', '')
kwargs.setdefault('data-value', display_value)

kwargs.setdefault('data-role', 'x-editable')
kwargs.setdefault('data-url', './ajax/update/')

kwargs.setdefault('id', field.id)
kwargs.setdefault('name', field.name)
kwargs.setdefault('href', '#')

if field.type in ('AjaxSelectField', 'AjaxSelectMultipleField'):
kwargs.setdefault('data-url-lookup', get_url('.ajax_lookup', name=field.loader.name))
kwargs.setdefault('type', 'hidden')
kwargs.setdefault(
'data-placeholder',
field.loader.options.get('placeholder', gettext('Search'))
)
kwargs.setdefault(
'data-minimum-input-length',
int(field.loader.options.get('minimum_input_length', 0))
)

if field.type == 'AjaxSelectMultipleField':
result = []
ids = []

for value in field.data:
data = field.loader.format(value)
result.append(data)
ids.append(as_unicode(data[0]))

separator = getattr(field, 'separator', ',')

kwargs['value'] = separator.join(ids)
kwargs['data-json'] = json.dumps(result)
kwargs['data-multiple'] = u'1'
else:
data = field.loader.format(field.data)

if data:
kwargs['value'] = data[0]
kwargs['data-json'] = json.dumps(data)

if not kwargs.get('pk'):
raise Exception('pk required')
kwargs['data-pk'] = str(kwargs.pop("pk"))
Expand Down Expand Up @@ -148,6 +180,8 @@ def get_kwargs(self, field, kwargs):
elif field.type in ['FloatField', 'DecimalField']:
kwargs['data-type'] = 'number'
kwargs['data-step'] = 'any'
elif field.type in ['AjaxSelectField', 'AjaxSelectMultipleField']:
kwargs['data-type'] = 'select2'
elif field.type in ['QuerySelectField', 'ModelSelectField',
'QuerySelectMultipleField', 'KeyPropertyField']:
# QuerySelectField and ModelSelectField are for relations
Expand Down
115 changes: 115 additions & 0 deletions flask_admin/static/admin/js/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,12 +455,127 @@
processLeafletWidget($el, name);
return true;
case 'x-editable':
var choices = {};
$el.editable({
params: overrideXeditableParams,
combodate: {
// prevent minutes from showing in 5 minute increments
minuteStep: 1,
maxYear: 2030,
},
ajaxOptions: {
// prevents keys with the same value from getting converted into arrays
traditional: $el.attr("data-multiple") == "1"
},
select2: {
// institute delay and cache ajax calls to prevent overloading server
delay: 250,
cacheDatasource: true,
dropdownAutoWidth: true,
placeholder: "data-placeholder",
minimumInputLength: $el.attr("data-minimum-input-length"),
allowClear: $el.attr("data-allow-blank") == "1",
multiple: $el.attr("data-multiple") == "1",
closeOnSelect: $el.attr("data-multiple") != "1",
ajax: {
// Special data-url just for the GET request
url: $el.attr("data-url-lookup"),
data: function (term, page) {
return {
query: term,
offset: (page - 1) * 10,
limit: 10,
};
},
results: function (data, page) {
var results = [];

for (var k in data) {
var v = data[k];
choices[v[0]] = v[1];
results.push({ id: v[0], text: v[1] });
}

return {
results: results,
more: results.length == 10,
};
},
},
initSelection: function(_, callback) {
var value = JSON.parse($el.attr('data-json'));
var result = null;

if (value) {
if ($el.attr("data-multiple") == "1") {
result = [];

for (var k in value) {
var v = value[k];
choices[v[0]] = v[1];
result.push({id: v[0], text: v[1]});
}

callback(result);
} else {
result = {id: value[0], text: value[1]};
}
}

callback(result);
},
},
display: function(selections) {
var unique = (value, index, self) =>{
var findIndex = (element) => element[0] == value[0];
return self.findIndex(findIndex) === index;
}
var escapedValue;
var updatedDataJson = [];
if (!(Array.isArray(selections)))
selections = [selections];
if (selections.length) {
var html = []
$.each(selections, function(i, v) {
if (v in choices){
escapedValue = $.fn.editableutils.escape(choices[v]);
if (!(choices[v] in selections))
// pk present, text not present - value was newly-added
updatedDataJson.push([parseInt(v), escapedValue]);
if (!html.includes(escapedValue))
html.push(escapedValue);
} else {
escapedValue = $.fn.editableutils.escape(v);
if (html.includes(escapedValue)) {
// value was just deleted, remove from html
html = html.filter(function(value, index, arr){
return value != escapedValue;
});
} else {
// page-load, field being instantiated
html.push(escapedValue);
}
}
});
if (html.length)
$(this).html(html.join(', '));
$(this).attr('data-value', html.join(','));
if (updatedDataJson.length) {
updatedDataJson = updatedDataJson.filter(unique);
$(this).attr('data-json', JSON.stringify(updatedDataJson));
$(this).attr('value', updatedDataJson.map(function(value){
return value[0];
}).join(','));
} else if ($el.attr("data-multiple") == "1") {
// handle initialization
$(this).attr('data-json', $(this).attr('data-json'));
$(this).attr('value', JSON.parse($(this).attr('data-json')).map(function(value){
return value[0];
}).join(','));
}
} else {
$(this).empty();
}
}
});
return true;
Expand Down