
Avoiding TypeError of accessing property ‘data’ on an undefined error. This error occurs when destroying an editable with input type select2 before the select2 has been created.
351 lines
12 KiB
JavaScript
351 lines
12 KiB
JavaScript
/**
|
|
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
|
|
Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
|
|
|
|
You should manually download and include select2 distributive:
|
|
|
|
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
|
|
<script src="select2/select2.js"></script>
|
|
|
|
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
|
|
|
|
<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
|
|
|
|
**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
|
|
You need initially put both `data-value` and element's text youself:
|
|
|
|
<a href="#" data-type="select2" data-value="1">Text1</a>
|
|
|
|
|
|
@class select2
|
|
@extends abstractinput
|
|
@since 1.4.1
|
|
@final
|
|
@example
|
|
<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
|
|
<script>
|
|
$(function(){
|
|
//local source
|
|
$('#country').editable({
|
|
source: [
|
|
{id: 'gb', text: 'Great Britain'},
|
|
{id: 'us', text: 'United States'},
|
|
{id: 'ru', text: 'Russia'}
|
|
],
|
|
select2: {
|
|
multiple: true
|
|
}
|
|
});
|
|
//remote source (simple)
|
|
$('#country').editable({
|
|
source: '/getCountries',
|
|
select2: {
|
|
placeholder: 'Select Country',
|
|
minimumInputLength: 1
|
|
}
|
|
});
|
|
//remote source (advanced)
|
|
$('#country').editable({
|
|
select2: {
|
|
placeholder: 'Select Country',
|
|
allowClear: true,
|
|
minimumInputLength: 3,
|
|
id: function (item) {
|
|
return item.CountryId;
|
|
},
|
|
ajax: {
|
|
url: '/getCountries',
|
|
dataType: 'json',
|
|
data: function (term, page) {
|
|
return { query: term };
|
|
},
|
|
results: function (data, page) {
|
|
return { results: data };
|
|
}
|
|
},
|
|
formatResult: function (item) {
|
|
return item.CountryName;
|
|
},
|
|
formatSelection: function (item) {
|
|
return item.CountryName;
|
|
},
|
|
initSelection: function (element, callback) {
|
|
return $.get('/getCountryById', { query: element.val() }, function (data) {
|
|
callback(data);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
**/
|
|
(function ($) {
|
|
"use strict";
|
|
|
|
var Constructor = function (options) {
|
|
this.init('select2', options, Constructor.defaults);
|
|
|
|
options.select2 = options.select2 || {};
|
|
|
|
this.sourceData = null;
|
|
|
|
//placeholder
|
|
if(options.placeholder) {
|
|
options.select2.placeholder = options.placeholder;
|
|
}
|
|
|
|
//if not `tags` mode, use source
|
|
if(!options.select2.tags && options.source) {
|
|
var source = options.source;
|
|
//if source is function, call it (once!)
|
|
if ($.isFunction(options.source)) {
|
|
source = options.source.call(options.scope);
|
|
}
|
|
|
|
if (typeof source === 'string') {
|
|
options.select2.ajax = options.select2.ajax || {};
|
|
//some default ajax params
|
|
if(!options.select2.ajax.data) {
|
|
options.select2.ajax.data = function(term) {return { query:term };};
|
|
}
|
|
if(!options.select2.ajax.results) {
|
|
options.select2.ajax.results = function(data) { return {results:data };};
|
|
}
|
|
options.select2.ajax.url = source;
|
|
} else {
|
|
//check format and convert x-editable format to select2 format (if needed)
|
|
this.sourceData = this.convertSource(source);
|
|
options.select2.data = this.sourceData;
|
|
}
|
|
}
|
|
|
|
//overriding objects in config (as by default jQuery extend() is not recursive)
|
|
this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
|
|
|
|
//detect whether it is multi-valued
|
|
this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
|
|
this.isRemote = ('ajax' in this.options.select2);
|
|
|
|
//store function returning ID of item
|
|
//should be here as used inautotext for local source
|
|
this.idFunc = this.options.select2.id;
|
|
if (typeof(this.idFunc) !== "function") {
|
|
var idKey = this.idFunc || 'id';
|
|
this.idFunc = function (e) { return e[idKey]; };
|
|
}
|
|
|
|
//store function that renders text in select2
|
|
this.formatSelection = this.options.select2.formatSelection;
|
|
if (typeof(this.formatSelection) !== "function") {
|
|
this.formatSelection = function (e) { return e.text; };
|
|
}
|
|
};
|
|
|
|
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
|
|
|
|
$.extend(Constructor.prototype, {
|
|
render: function() {
|
|
this.setClass();
|
|
|
|
//can not apply select2 here as it calls initSelection
|
|
//over input that does not have correct value yet.
|
|
//apply select2 only in value2input
|
|
//this.$input.select2(this.options.select2);
|
|
|
|
//when data is loaded via ajax, we need to know when it's done to populate listData
|
|
if(this.isRemote) {
|
|
//listen to loaded event to populate data
|
|
this.$input.on('select2-loaded', $.proxy(function(e) {
|
|
this.sourceData = e.items.results;
|
|
}, this));
|
|
}
|
|
|
|
//trigger resize of editableform to re-position container in multi-valued mode
|
|
if(this.isMultiple) {
|
|
this.$input.on('change', function() {
|
|
$(this).closest('form').parent().triggerHandler('resize');
|
|
});
|
|
}
|
|
},
|
|
|
|
value2html: function(value, element) {
|
|
var text = '', data,
|
|
that = this;
|
|
|
|
if(this.options.select2.tags) { //in tags mode just assign value
|
|
data = value;
|
|
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
|
|
} else if(this.sourceData) {
|
|
data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
|
|
} else {
|
|
//can not get list of possible values
|
|
//(e.g. autotext for select2 with ajax source)
|
|
}
|
|
|
|
//data may be array (when multiple values allowed)
|
|
if($.isArray(data)) {
|
|
//collect selected data and show with separator
|
|
text = [];
|
|
$.each(data, function(k, v){
|
|
text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);
|
|
});
|
|
} else if(data) {
|
|
text = that.formatSelection(data);
|
|
}
|
|
|
|
text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
|
|
|
|
//$(element).text(text);
|
|
Constructor.superclass.value2html.call(this, text, element);
|
|
},
|
|
|
|
html2value: function(html) {
|
|
return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
|
|
},
|
|
|
|
value2input: function(value) {
|
|
// if value array => join it anyway
|
|
if($.isArray(value)) {
|
|
value = value.join(this.getSeparator());
|
|
}
|
|
|
|
//for remote source just set value, text is updated by initSelection
|
|
if(!this.$input.data('select2')) {
|
|
this.$input.val(value);
|
|
this.$input.select2(this.options.select2);
|
|
} else {
|
|
//second argument needed to separate initial change from user's click (for autosubmit)
|
|
this.$input.val(value).trigger('change', true);
|
|
|
|
//Uncaught Error: cannot call val() if initSelection() is not defined
|
|
//this.$input.select2('val', value);
|
|
}
|
|
|
|
// if defined remote source AND no multiple mode AND no user's initSelection provided -->
|
|
// we should somehow get text for provided id.
|
|
// The solution is to use element's text as text for that id (exclude empty)
|
|
if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
|
|
// customId and customText are methods to extract `id` and `text` from data object
|
|
// we can use this workaround only if user did not define these methods
|
|
// otherwise we cant construct data object
|
|
var customId = this.options.select2.id,
|
|
customText = this.options.select2.formatSelection;
|
|
|
|
if(!customId && !customText) {
|
|
var $el = $(this.options.scope);
|
|
if (!$el.data('editable').isEmpty) {
|
|
var data = {id: value, text: $el.text()};
|
|
this.$input.select2('data', data);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
input2value: function() {
|
|
return this.$input.select2('val');
|
|
},
|
|
|
|
str2value: function(str, separator) {
|
|
if(typeof str !== 'string' || !this.isMultiple) {
|
|
return str;
|
|
}
|
|
|
|
separator = separator || this.getSeparator();
|
|
|
|
var val, i, l;
|
|
|
|
if (str === null || str.length < 1) {
|
|
return null;
|
|
}
|
|
val = str.split(separator);
|
|
for (i = 0, l = val.length; i < l; i = i + 1) {
|
|
val[i] = $.trim(val[i]);
|
|
}
|
|
|
|
return val;
|
|
},
|
|
|
|
autosubmit: function() {
|
|
this.$input.on('change', function(e, isInitial){
|
|
if(!isInitial) {
|
|
$(this).closest('form').submit();
|
|
}
|
|
});
|
|
},
|
|
|
|
getSeparator: function() {
|
|
return this.options.select2.separator || $.fn.select2.defaults.separator;
|
|
},
|
|
|
|
/*
|
|
Converts source from x-editable format: {value: 1, text: "1"} to
|
|
select2 format: {id: 1, text: "1"}
|
|
*/
|
|
convertSource: function(source) {
|
|
if($.isArray(source) && source.length && source[0].value !== undefined) {
|
|
for(var i = 0; i<source.length; i++) {
|
|
if(source[i].value !== undefined) {
|
|
source[i].id = source[i].value;
|
|
delete source[i].value;
|
|
}
|
|
}
|
|
}
|
|
return source;
|
|
},
|
|
|
|
destroy: function() {
|
|
if(this.$input) {
|
|
if(this.$input.data('select2')) {
|
|
this.$input.select2('destroy');
|
|
}
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
|
/**
|
|
@property tpl
|
|
@default <input type="hidden">
|
|
**/
|
|
tpl:'<input type="hidden">',
|
|
/**
|
|
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
|
|
|
|
@property select2
|
|
@type object
|
|
@default null
|
|
**/
|
|
select2: null,
|
|
/**
|
|
Placeholder attribute of select
|
|
|
|
@property placeholder
|
|
@type string
|
|
@default null
|
|
**/
|
|
placeholder: null,
|
|
/**
|
|
Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
|
|
Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
|
|
E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
|
|
|
|
@property source
|
|
@type array|string|function
|
|
@default null
|
|
**/
|
|
source: null,
|
|
/**
|
|
Separator used to display tags.
|
|
|
|
@property viewseparator
|
|
@type string
|
|
@default ', '
|
|
**/
|
|
viewseparator: ', '
|
|
});
|
|
|
|
$.fn.editabletypes.select2 = Constructor;
|
|
|
|
}(window.jQuery));
|