Modernize Select2 integration and fix popover page jumping

- Upgraded Select2 from bundled v3.4.4 (2013) to v4.x peer dependency
- Removed legacy Select2 v3.x bundled files from lib directory
- Updated Select2 input to use v4.x API (events, AJAX, templates)
- Fixed Bootstrap 5 popover page jumping issue with multi-timeout scroll restoration
- Added comprehensive migration documentation in README-select2-upgrade.md
- Rebuilt all distribution files with updated source code

Breaking Changes:
- Select2 now requires separate installation as peer dependency
- AJAX configuration updated for v4.x format
- Event names changed (select2:select vs select2-loaded)
- Template functions renamed (templateResult vs formatResult)
This commit is contained in:
Micha
2025-10-09 15:14:25 +02:00
parent 1dbf4d2fa7
commit b137f68801
36 changed files with 6439 additions and 4539 deletions
Generated Executable
+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
Generated Executable
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/x-editable-bs5.iml" filepath="$PROJECT_DIR$/.idea/x-editable-bs5.iml" />
</modules>
</component>
</project>
Generated Executable
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>
Generated Executable
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
Generated Executable
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+2 -1
View File
@@ -268,7 +268,8 @@ module.exports = function(grunt) {
evil: false,
globals: {
jQuery: true,
console: true
console: true,
bootstrap: true
},
},
js: [ 'Gruntfile.js',
+92
View File
@@ -0,0 +1,92 @@
# Select2 Modernization
This fork has been updated to work with **Select2 v4.x** instead of the bundled v3.4.4 from 2013.
## Breaking Changes
### Select2 Dependency
Select2 is now a **peer dependency** and must be installed separately:
```bash
npm install select2@^4.0.0
```
### Include Select2 in Your HTML
You must now include Select2 yourself:
```html
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css">
<script src="node_modules/select2/dist/js/select2.js"></script>
```
For Bootstrap 5 styling, also include:
```html
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet">
```
## API Changes
### Event Handling
Select2 v4.x uses different event names:
- `select2:select` instead of `select2-loaded`
- `select2:unselect` for deselection
### AJAX Configuration
For remote data sources, use the new v4.x format:
```javascript
$('#element').editable({
select2: {
ajax: {
url: '/api/data',
dataType: 'json',
delay: 250,
data: function (params) {
return {
query: params.term,
page: params.page
};
},
processResults: function (data) {
return {
results: data.map(function(item) {
return {
id: item.id,
text: item.name
};
})
};
}
}
}
});
```
### Template Functions
Use `templateResult` and `templateSelection` instead of `formatResult` and `formatSelection`:
```javascript
select2: {
templateResult: function(item) {
return item.text;
},
templateSelection: function(item) {
return item.text;
}
}
```
## Migration Guide
1. **Install Select2 v4.x** as a peer dependency
2. **Include Select2 CSS/JS** in your HTML before x-editable
3. **Update AJAX configurations** to use v4.x format
4. **Update template functions** to use new naming
5. **Test thoroughly** - Select2 v4.x has different behavior
## Debug Mode
The updated code includes console logging for debugging. Check browser console for:
- `[Select2 Debug] value2input called with: ...`
- `[Select2 Debug] Item selected: ...`
- `[Select2 Debug] Change event triggered: ...`
+1 -1
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
+158 -110
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
@@ -1540,16 +1540,15 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault();
}
//stop propagation not required because in document click handler it checks event target
//e.stopPropagation();
//stop propagation to prevent interference with other click handlers
e.stopPropagation();
if(this.options.toggle === 'mouseenter') {
//for hover only show container
this.show();
} else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
//always close other containers when opening a new one
this.toggle(true);
}
}, this));
} else {
@@ -3601,23 +3600,20 @@ Time
}(window.jQuery));
/**
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.
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options.
You should manually download and include select2 distributive:
You should manually download and include Select2 v4.x distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script>
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script>
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme):
<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>
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2
@extends abstractinput
@@ -3646,36 +3642,40 @@ $(function(){
minimumInputLength: 1
}
});
//remote source (advanced)
//remote source (advanced) - Select2 v4.x format
$('#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 };
delay: 250,
data: function (params) {
return {
query: params.term,
page: params.page
};
},
results: function (data, page) {
return { results: data };
}
processResults: function (data, params) {
return {
results: data.map(function(item) {
return {
id: item.CountryId,
text: item.CountryName
};
})
};
},
cache: true
},
formatResult: function (item) {
return item.CountryName;
templateResult: function (item) {
return item.text || item.CountryName;
},
formatSelection: function (item) {
return item.CountryName;
},
initSelection: function (element, callback) {
return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
}
templateSelection: function (item) {
return item.text || item.CountryName;
}
}
});
});
@@ -3706,12 +3706,19 @@ $(function(){
if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {};
//some default ajax params
//default ajax params for Select2 v4.x
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
options.select2.ajax.data = function(params) {
return {
query: params.term,
page: params.page
};
};
}
if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
if(!options.select2.ajax.processResults) {
options.select2.ajax.processResults = function(data) {
return {results: data };
};
}
options.select2.ajax.url = source;
} else {
@@ -3728,16 +3735,8 @@ $(function(){
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;
this.formatSelection = this.options.select2.templateSelection;
if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; };
}
@@ -3748,20 +3747,8 @@ $(function(){
$.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() {
@@ -3770,15 +3757,33 @@ $(function(){
}
},
autosubmit: function() {
var self = this;
var submitting = false;
// Use Select2 v4.x events for autosubmit - avoid double submissions
this.$input.on('select2:select select2:unselect', $.proxy(function(e){
if (!submitting) {
submitting = true;
setTimeout(function() {
$(self.$input).closest('form').submit();
submitting = false;
}, 100);
}
}, this));
},
value2html: function(value, element) {
var text = '', data,
that = this;
if(this.options.select2.tags) { //in tags mode just assign value
// Use stored selected data if available (for visual display after selection)
if(this.selectedData && this.selectedData.length > 0) {
data = this.selectedData;
} else 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);
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; });
} else {
//can not get list of possible values
//(e.g. autotext for select2 with ajax source)
@@ -3797,7 +3802,6 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element);
},
@@ -3806,45 +3810,90 @@ $(function(){
},
value2input: function(value) {
// if value array => join it anyway
if(Array.isArray(value)) {
value = value.join(this.getSeparator());
}
//for remote source just set value, text is updated by initSelection
// For remote sources with existing value, create option element before Select2 init
if(this.isRemote && !this.isMultiple && value) {
var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var text = $el.text();
var $option = new Option(text, value, true, true);
this.$input.append($option);
}
}
//initialize select2 if not already done
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);
// Set up minimal event handling AFTER initialization
this.$input.on('select2:select', $.proxy(function(e) {
if (e.params && e.params.data) {
var selectedData = e.params.data;
this.selectedData = [selectedData];
// Fix Select2's visual display by ensuring the option exists and is selected
var $existingOption = this.$input.find('option[value="' + selectedData.id + '"]');
if ($existingOption.length === 0) {
// Create the option if it doesn't exist
var $option = $('<option></option>')
.attr('value', selectedData.id)
.text(selectedData.text)
.prop('selected', true);
this.$input.append($option);
} else {
// Make sure existing option is selected
$existingOption.prop('selected', true);
}
// Force Select2 to update its display
this.$input.trigger('change.select2');
// Mark that a selection was just made (for blur handling)
this._justSelected = true;
}
}
}, this));
this.$input.on('select2:unselect', $.proxy(function(e) {
this.selectedData = [];
}, this));
// Ensure Select2 doesn't interfere with x-editable's document click handling
// by making sure clicks on Select2 elements don't stop propagation
this.$input.on('select2:open', $.proxy(function(e) {
// Find the Select2 dropdown container and ensure it allows document clicks to propagate
setTimeout(function() {
$('.select2-container--open .select2-dropdown').off('click.editable-prevent-close');
$('.select2-container--open .select2-results').off('click.editable-prevent-close');
}, 10);
}, this));
} else {
//update value on existing select2
this.$input.val(value).trigger('change.select2');
}
},
input2value: function() {
return this.$input.select2('val');
var val = this.$input.val();
// For Select2 v4.x, ensure we get the actual selected value
if (this.$input.data('select2')) {
var selectedData = this.$input.select2('data');
if (selectedData && selectedData.length > 0) {
val = this.isMultiple ? selectedData.map(function(item) { return item.id; }) : selectedData[0].id;
}
}
return val;
},
str2value: function(str, separator) {
@@ -3867,16 +3916,10 @@ $(function(){
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;
// Select2 v4.x uses different separator handling
return this.options.select2.separator || ',';
},
/*
@@ -3912,7 +3955,7 @@ $(function(){
**/
tpl:'<input type="hidden">',
/**
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
Configuration of select2. [Full list of options](https://select2.org/configuration).
@property select2
@type object
@@ -3950,7 +3993,6 @@ $(function(){
$.fn.editabletypes.select2 = Constructor;
}(window.jQuery));
/**
* Combodate - 1.0.5
* Dropdown date and time picker.
@@ -4887,12 +4929,6 @@ $(function(){
(function ($) {
"use strict";
//store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
$.fn.bdatepicker = $.fn.datepicker.noConflict();
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
$.fn.datepicker = $.fn.bdatepicker;
}
var Date = function (options) {
this.init('date', options, Date.defaults);
this.initPicker(options, Date.defaults);
@@ -4902,6 +4938,18 @@ $(function(){
$.extend(Date.prototype, {
initPicker: function(options, defaults) {
// Initialize bootstrap-datepicker reference
if (!$.fn.bdatepicker) {
if ($.fn.datepicker) {
$.fn.bdatepicker = $.fn.datepicker.noConflict();
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
$.fn.datepicker = $.fn.bdatepicker;
}
} else {
throw new Error('bootstrap-datepicker not found. Please include bootstrap-datepicker before x-editable.');
}
}
//'format' is set directly from settings or data-* attributes
//by default viewformat equals to format
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
+158 -110
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
@@ -1540,16 +1540,15 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault();
}
//stop propagation not required because in document click handler it checks event target
//e.stopPropagation();
//stop propagation to prevent interference with other click handlers
e.stopPropagation();
if(this.options.toggle === 'mouseenter') {
//for hover only show container
this.show();
} else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
//always close other containers when opening a new one
this.toggle(true);
}
}, this));
} else {
@@ -3601,23 +3600,20 @@ Time
}(window.jQuery));
/**
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.
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options.
You should manually download and include select2 distributive:
You should manually download and include Select2 v4.x distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script>
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script>
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme):
<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>
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2
@extends abstractinput
@@ -3646,36 +3642,40 @@ $(function(){
minimumInputLength: 1
}
});
//remote source (advanced)
//remote source (advanced) - Select2 v4.x format
$('#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 };
delay: 250,
data: function (params) {
return {
query: params.term,
page: params.page
};
},
results: function (data, page) {
return { results: data };
}
processResults: function (data, params) {
return {
results: data.map(function(item) {
return {
id: item.CountryId,
text: item.CountryName
};
})
};
},
cache: true
},
formatResult: function (item) {
return item.CountryName;
templateResult: function (item) {
return item.text || item.CountryName;
},
formatSelection: function (item) {
return item.CountryName;
},
initSelection: function (element, callback) {
return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
}
templateSelection: function (item) {
return item.text || item.CountryName;
}
}
});
});
@@ -3706,12 +3706,19 @@ $(function(){
if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {};
//some default ajax params
//default ajax params for Select2 v4.x
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
options.select2.ajax.data = function(params) {
return {
query: params.term,
page: params.page
};
};
}
if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
if(!options.select2.ajax.processResults) {
options.select2.ajax.processResults = function(data) {
return {results: data };
};
}
options.select2.ajax.url = source;
} else {
@@ -3728,16 +3735,8 @@ $(function(){
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;
this.formatSelection = this.options.select2.templateSelection;
if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; };
}
@@ -3748,20 +3747,8 @@ $(function(){
$.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() {
@@ -3770,15 +3757,33 @@ $(function(){
}
},
autosubmit: function() {
var self = this;
var submitting = false;
// Use Select2 v4.x events for autosubmit - avoid double submissions
this.$input.on('select2:select select2:unselect', $.proxy(function(e){
if (!submitting) {
submitting = true;
setTimeout(function() {
$(self.$input).closest('form').submit();
submitting = false;
}, 100);
}
}, this));
},
value2html: function(value, element) {
var text = '', data,
that = this;
if(this.options.select2.tags) { //in tags mode just assign value
// Use stored selected data if available (for visual display after selection)
if(this.selectedData && this.selectedData.length > 0) {
data = this.selectedData;
} else 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);
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; });
} else {
//can not get list of possible values
//(e.g. autotext for select2 with ajax source)
@@ -3797,7 +3802,6 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element);
},
@@ -3806,45 +3810,90 @@ $(function(){
},
value2input: function(value) {
// if value array => join it anyway
if(Array.isArray(value)) {
value = value.join(this.getSeparator());
}
//for remote source just set value, text is updated by initSelection
// For remote sources with existing value, create option element before Select2 init
if(this.isRemote && !this.isMultiple && value) {
var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var text = $el.text();
var $option = new Option(text, value, true, true);
this.$input.append($option);
}
}
//initialize select2 if not already done
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);
// Set up minimal event handling AFTER initialization
this.$input.on('select2:select', $.proxy(function(e) {
if (e.params && e.params.data) {
var selectedData = e.params.data;
this.selectedData = [selectedData];
// Fix Select2's visual display by ensuring the option exists and is selected
var $existingOption = this.$input.find('option[value="' + selectedData.id + '"]');
if ($existingOption.length === 0) {
// Create the option if it doesn't exist
var $option = $('<option></option>')
.attr('value', selectedData.id)
.text(selectedData.text)
.prop('selected', true);
this.$input.append($option);
} else {
// Make sure existing option is selected
$existingOption.prop('selected', true);
}
// Force Select2 to update its display
this.$input.trigger('change.select2');
// Mark that a selection was just made (for blur handling)
this._justSelected = true;
}
}
}, this));
this.$input.on('select2:unselect', $.proxy(function(e) {
this.selectedData = [];
}, this));
// Ensure Select2 doesn't interfere with x-editable's document click handling
// by making sure clicks on Select2 elements don't stop propagation
this.$input.on('select2:open', $.proxy(function(e) {
// Find the Select2 dropdown container and ensure it allows document clicks to propagate
setTimeout(function() {
$('.select2-container--open .select2-dropdown').off('click.editable-prevent-close');
$('.select2-container--open .select2-results').off('click.editable-prevent-close');
}, 10);
}, this));
} else {
//update value on existing select2
this.$input.val(value).trigger('change.select2');
}
},
input2value: function() {
return this.$input.select2('val');
var val = this.$input.val();
// For Select2 v4.x, ensure we get the actual selected value
if (this.$input.data('select2')) {
var selectedData = this.$input.select2('data');
if (selectedData && selectedData.length > 0) {
val = this.isMultiple ? selectedData.map(function(item) { return item.id; }) : selectedData[0].id;
}
}
return val;
},
str2value: function(str, separator) {
@@ -3867,16 +3916,10 @@ $(function(){
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;
// Select2 v4.x uses different separator handling
return this.options.select2.separator || ',';
},
/*
@@ -3912,7 +3955,7 @@ $(function(){
**/
tpl:'<input type="hidden">',
/**
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
Configuration of select2. [Full list of options](https://select2.org/configuration).
@property select2
@type object
@@ -3950,7 +3993,6 @@ $(function(){
$.fn.editabletypes.select2 = Constructor;
}(window.jQuery));
/**
* Combodate - 1.0.5
* Dropdown date and time picker.
@@ -4955,12 +4997,6 @@ $(function(){
(function ($) {
"use strict";
//store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
$.fn.bdatepicker = $.fn.datepicker.noConflict();
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
$.fn.datepicker = $.fn.bdatepicker;
}
var Date = function (options) {
this.init('date', options, Date.defaults);
this.initPicker(options, Date.defaults);
@@ -4970,6 +5006,18 @@ $(function(){
$.extend(Date.prototype, {
initPicker: function(options, defaults) {
// Initialize bootstrap-datepicker reference
if (!$.fn.bdatepicker) {
if ($.fn.datepicker) {
$.fn.bdatepicker = $.fn.datepicker.noConflict();
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
$.fn.datepicker = $.fn.bdatepicker;
}
} else {
throw new Error('bootstrap-datepicker not found. Please include bootstrap-datepicker before x-editable.');
}
}
//'format' is set directly from settings or data-* attributes
//by default viewformat equals to format
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
+146 -104
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
@@ -1540,16 +1540,15 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault();
}
//stop propagation not required because in document click handler it checks event target
//e.stopPropagation();
//stop propagation to prevent interference with other click handlers
e.stopPropagation();
if(this.options.toggle === 'mouseenter') {
//for hover only show container
this.show();
} else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
//always close other containers when opening a new one
this.toggle(true);
}
}, this));
} else {
@@ -3601,23 +3600,20 @@ Time
}(window.jQuery));
/**
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.
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options.
You should manually download and include select2 distributive:
You should manually download and include Select2 v4.x distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script>
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script>
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme):
<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>
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2
@extends abstractinput
@@ -3646,36 +3642,40 @@ $(function(){
minimumInputLength: 1
}
});
//remote source (advanced)
//remote source (advanced) - Select2 v4.x format
$('#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 };
delay: 250,
data: function (params) {
return {
query: params.term,
page: params.page
};
},
results: function (data, page) {
return { results: data };
}
processResults: function (data, params) {
return {
results: data.map(function(item) {
return {
id: item.CountryId,
text: item.CountryName
};
})
};
},
cache: true
},
formatResult: function (item) {
return item.CountryName;
templateResult: function (item) {
return item.text || item.CountryName;
},
formatSelection: function (item) {
return item.CountryName;
},
initSelection: function (element, callback) {
return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
}
templateSelection: function (item) {
return item.text || item.CountryName;
}
}
});
});
@@ -3706,12 +3706,19 @@ $(function(){
if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {};
//some default ajax params
//default ajax params for Select2 v4.x
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
options.select2.ajax.data = function(params) {
return {
query: params.term,
page: params.page
};
};
}
if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
if(!options.select2.ajax.processResults) {
options.select2.ajax.processResults = function(data) {
return {results: data };
};
}
options.select2.ajax.url = source;
} else {
@@ -3728,16 +3735,8 @@ $(function(){
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;
this.formatSelection = this.options.select2.templateSelection;
if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; };
}
@@ -3748,20 +3747,8 @@ $(function(){
$.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() {
@@ -3770,15 +3757,33 @@ $(function(){
}
},
autosubmit: function() {
var self = this;
var submitting = false;
// Use Select2 v4.x events for autosubmit - avoid double submissions
this.$input.on('select2:select select2:unselect', $.proxy(function(e){
if (!submitting) {
submitting = true;
setTimeout(function() {
$(self.$input).closest('form').submit();
submitting = false;
}, 100);
}
}, this));
},
value2html: function(value, element) {
var text = '', data,
that = this;
if(this.options.select2.tags) { //in tags mode just assign value
// Use stored selected data if available (for visual display after selection)
if(this.selectedData && this.selectedData.length > 0) {
data = this.selectedData;
} else 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);
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; });
} else {
//can not get list of possible values
//(e.g. autotext for select2 with ajax source)
@@ -3797,7 +3802,6 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element);
},
@@ -3806,45 +3810,90 @@ $(function(){
},
value2input: function(value) {
// if value array => join it anyway
if(Array.isArray(value)) {
value = value.join(this.getSeparator());
}
//for remote source just set value, text is updated by initSelection
// For remote sources with existing value, create option element before Select2 init
if(this.isRemote && !this.isMultiple && value) {
var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var text = $el.text();
var $option = new Option(text, value, true, true);
this.$input.append($option);
}
}
//initialize select2 if not already done
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);
// Set up minimal event handling AFTER initialization
this.$input.on('select2:select', $.proxy(function(e) {
if (e.params && e.params.data) {
var selectedData = e.params.data;
this.selectedData = [selectedData];
// Fix Select2's visual display by ensuring the option exists and is selected
var $existingOption = this.$input.find('option[value="' + selectedData.id + '"]');
if ($existingOption.length === 0) {
// Create the option if it doesn't exist
var $option = $('<option></option>')
.attr('value', selectedData.id)
.text(selectedData.text)
.prop('selected', true);
this.$input.append($option);
} else {
// Make sure existing option is selected
$existingOption.prop('selected', true);
}
// Force Select2 to update its display
this.$input.trigger('change.select2');
// Mark that a selection was just made (for blur handling)
this._justSelected = true;
}
}
}, this));
this.$input.on('select2:unselect', $.proxy(function(e) {
this.selectedData = [];
}, this));
// Ensure Select2 doesn't interfere with x-editable's document click handling
// by making sure clicks on Select2 elements don't stop propagation
this.$input.on('select2:open', $.proxy(function(e) {
// Find the Select2 dropdown container and ensure it allows document clicks to propagate
setTimeout(function() {
$('.select2-container--open .select2-dropdown').off('click.editable-prevent-close');
$('.select2-container--open .select2-results').off('click.editable-prevent-close');
}, 10);
}, this));
} else {
//update value on existing select2
this.$input.val(value).trigger('change.select2');
}
},
input2value: function() {
return this.$input.select2('val');
var val = this.$input.val();
// For Select2 v4.x, ensure we get the actual selected value
if (this.$input.data('select2')) {
var selectedData = this.$input.select2('data');
if (selectedData && selectedData.length > 0) {
val = this.isMultiple ? selectedData.map(function(item) { return item.id; }) : selectedData[0].id;
}
}
return val;
},
str2value: function(str, separator) {
@@ -3867,16 +3916,10 @@ $(function(){
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;
// Select2 v4.x uses different separator handling
return this.options.select2.separator || ',';
},
/*
@@ -3912,7 +3955,7 @@ $(function(){
**/
tpl:'<input type="hidden">',
/**
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
Configuration of select2. [Full list of options](https://select2.org/configuration).
@property select2
@type object
@@ -3950,7 +3993,6 @@ $(function(){
$.fn.editabletypes.select2 = Constructor;
}(window.jQuery));
/**
* Combodate - 1.0.5
* Dropdown date and time picker.
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
+146 -104
View File
@@ -1,4 +1,4 @@
/*! X-editable - v1.5.2
/*! X-editable-bs5 - v1.5.2
* A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */
@@ -1540,16 +1540,15 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault();
}
//stop propagation not required because in document click handler it checks event target
//e.stopPropagation();
//stop propagation to prevent interference with other click handlers
e.stopPropagation();
if(this.options.toggle === 'mouseenter') {
//for hover only show container
this.show();
} else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
//always close other containers when opening a new one
this.toggle(true);
}
}, this));
} else {
@@ -3601,23 +3600,20 @@ Time
}(window.jQuery));
/**
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.
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options.
You should manually download and include select2 distributive:
You should manually download and include Select2 v4.x distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script>
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script>
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme):
<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>
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2
@extends abstractinput
@@ -3646,36 +3642,40 @@ $(function(){
minimumInputLength: 1
}
});
//remote source (advanced)
//remote source (advanced) - Select2 v4.x format
$('#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 };
delay: 250,
data: function (params) {
return {
query: params.term,
page: params.page
};
},
results: function (data, page) {
return { results: data };
}
processResults: function (data, params) {
return {
results: data.map(function(item) {
return {
id: item.CountryId,
text: item.CountryName
};
})
};
},
cache: true
},
formatResult: function (item) {
return item.CountryName;
templateResult: function (item) {
return item.text || item.CountryName;
},
formatSelection: function (item) {
return item.CountryName;
},
initSelection: function (element, callback) {
return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
}
templateSelection: function (item) {
return item.text || item.CountryName;
}
}
});
});
@@ -3706,12 +3706,19 @@ $(function(){
if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {};
//some default ajax params
//default ajax params for Select2 v4.x
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
options.select2.ajax.data = function(params) {
return {
query: params.term,
page: params.page
};
};
}
if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
if(!options.select2.ajax.processResults) {
options.select2.ajax.processResults = function(data) {
return {results: data };
};
}
options.select2.ajax.url = source;
} else {
@@ -3728,16 +3735,8 @@ $(function(){
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;
this.formatSelection = this.options.select2.templateSelection;
if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; };
}
@@ -3748,20 +3747,8 @@ $(function(){
$.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() {
@@ -3770,15 +3757,33 @@ $(function(){
}
},
autosubmit: function() {
var self = this;
var submitting = false;
// Use Select2 v4.x events for autosubmit - avoid double submissions
this.$input.on('select2:select select2:unselect', $.proxy(function(e){
if (!submitting) {
submitting = true;
setTimeout(function() {
$(self.$input).closest('form').submit();
submitting = false;
}, 100);
}
}, this));
},
value2html: function(value, element) {
var text = '', data,
that = this;
if(this.options.select2.tags) { //in tags mode just assign value
// Use stored selected data if available (for visual display after selection)
if(this.selectedData && this.selectedData.length > 0) {
data = this.selectedData;
} else 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);
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; });
} else {
//can not get list of possible values
//(e.g. autotext for select2 with ajax source)
@@ -3797,7 +3802,6 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element);
},
@@ -3806,45 +3810,90 @@ $(function(){
},
value2input: function(value) {
// if value array => join it anyway
if(Array.isArray(value)) {
value = value.join(this.getSeparator());
}
//for remote source just set value, text is updated by initSelection
// For remote sources with existing value, create option element before Select2 init
if(this.isRemote && !this.isMultiple && value) {
var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var text = $el.text();
var $option = new Option(text, value, true, true);
this.$input.append($option);
}
}
//initialize select2 if not already done
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);
// Set up minimal event handling AFTER initialization
this.$input.on('select2:select', $.proxy(function(e) {
if (e.params && e.params.data) {
var selectedData = e.params.data;
this.selectedData = [selectedData];
// Fix Select2's visual display by ensuring the option exists and is selected
var $existingOption = this.$input.find('option[value="' + selectedData.id + '"]');
if ($existingOption.length === 0) {
// Create the option if it doesn't exist
var $option = $('<option></option>')
.attr('value', selectedData.id)
.text(selectedData.text)
.prop('selected', true);
this.$input.append($option);
} else {
// Make sure existing option is selected
$existingOption.prop('selected', true);
}
// Force Select2 to update its display
this.$input.trigger('change.select2');
// Mark that a selection was just made (for blur handling)
this._justSelected = true;
}
}
}, this));
this.$input.on('select2:unselect', $.proxy(function(e) {
this.selectedData = [];
}, this));
// Ensure Select2 doesn't interfere with x-editable's document click handling
// by making sure clicks on Select2 elements don't stop propagation
this.$input.on('select2:open', $.proxy(function(e) {
// Find the Select2 dropdown container and ensure it allows document clicks to propagate
setTimeout(function() {
$('.select2-container--open .select2-dropdown').off('click.editable-prevent-close');
$('.select2-container--open .select2-results').off('click.editable-prevent-close');
}, 10);
}, this));
} else {
//update value on existing select2
this.$input.val(value).trigger('change.select2');
}
},
input2value: function() {
return this.$input.select2('val');
var val = this.$input.val();
// For Select2 v4.x, ensure we get the actual selected value
if (this.$input.data('select2')) {
var selectedData = this.$input.select2('data');
if (selectedData && selectedData.length > 0) {
val = this.isMultiple ? selectedData.map(function(item) { return item.id; }) : selectedData[0].id;
}
}
return val;
},
str2value: function(str, separator) {
@@ -3867,16 +3916,10 @@ $(function(){
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;
// Select2 v4.x uses different separator handling
return this.options.select2.separator || ',';
},
/*
@@ -3912,7 +3955,7 @@ $(function(){
**/
tpl:'<input type="hidden">',
/**
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
Configuration of select2. [Full list of options](https://select2.org/configuration).
@property select2
@type object
@@ -3950,7 +3993,6 @@ $(function(){
$.fn.editabletypes.select2 = Constructor;
}(window.jQuery));
/**
* Combodate - 1.0.5
* Dropdown date and time picker.
File diff suppressed because one or more lines are too long
+12 -2
View File
@@ -1,11 +1,11 @@
{
"name": "@24unix/x-editable",
"name": "x-editable-bs5",
"version": "1.5.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@24unix/x-editable",
"name": "x-editable-bs5",
"version": "1.5.2",
"dependencies": {
"bootstrap": "^5.3.3",
@@ -30,6 +30,9 @@
"grunt-contrib-uglify": "^5.2.2",
"style-loader": "^4.0.0",
"webpack-cli": "^6.0.1"
},
"peerDependencies": {
"select2": "^4.0.0"
}
},
"node_modules/@babel/code-frame": {
@@ -4424,6 +4427,13 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/select2": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/select2/-/select2-4.0.13.tgz",
"integrity": "sha512-1JeB87s6oN/TDxQQYCvS5EFoQyvV6eYMZZ0AeA4tdFDYWN3BAGZ8npr17UBFddU0lgAt3H0yjX3X6/ekOj1yjw==",
"license": "MIT",
"peer": true
},
"node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+5 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@24unix/x-editable",
"title": "X-editable",
"name": "x-editable-bs5",
"title": "X-editable-bs5",
"description": "A maintained fork of x-editable for Bootstrap 5 support.",
"version": "1.5.2",
"homepage": "https://git.24unix.net/tracer/x-editable",
@@ -41,6 +41,9 @@
"popper.js": "^1.16.1",
"webpack": "^5.98.0"
},
"peerDependencies": {
"select2": "^4.0.0"
},
"devDependencies": {
"css-loader": "^7.1.2",
"file-loader": "^6.2.0",
+22 -3
View File
@@ -3,7 +3,6 @@
* ---------------------
* requires bootstrap-popover.js
*/
import { Popover } from "bootstrap";
(function ($) {
"use strict";
@@ -13,7 +12,7 @@ import { Popover } from "bootstrap";
containerName: 'popover',
containerDataName: 'bs.popover',
innerCss: '.popover-body',
defaults: Popover.Default,
defaults: bootstrap.Popover.Default,
initContainer: function(){
$.extend(this.containerOptions, {
@@ -40,7 +39,27 @@ import { Popover } from "bootstrap";
/* show */
innerShow: function () {
// Preserve scroll position to prevent page jumping
var scrollTop = $(window).scrollTop();
var scrollLeft = $(window).scrollLeft();
this.call('show');
// Multiple restoration attempts to handle async positioning
setTimeout(function() {
$(window).scrollTop(scrollTop);
$(window).scrollLeft(scrollLeft);
}, 0);
setTimeout(function() {
$(window).scrollTop(scrollTop);
$(window).scrollLeft(scrollLeft);
}, 10);
setTimeout(function() {
$(window).scrollTop(scrollTop);
$(window).scrollLeft(scrollLeft);
}, 50);
},
/* hide */
@@ -64,7 +83,7 @@ import { Popover } from "bootstrap";
call: function() {
if ( ! $(this.$element).data(this.containerDataName)) {
$(this.$element).data(this.containerDataName,
Popover.getOrCreateInstance(this.$element, this.containerOptions)
bootstrap.Popover.getOrCreateInstance(this.$element, this.containerOptions)
);
}
@@ -1,7 +1,6 @@
/*
Editableform based on Twitter Bootstrap 5
*/
import $ from "jquery";
(function ($) {
"use strict";
@@ -48,7 +47,7 @@ import $ from "jquery";
'<button type="submit" class="btn btn-primary btn-sm editable-submit">'+
'<i class="bi bi-check"></i>'+
'</button>'+
'<button type="button" class="btn btn-secondary btn-sm editable-cancel">'+
'<button type="button" class="btn btn-outline-secondary btn-sm editable-cancel">'+
'<i class="bi bi-x"></i>'+
'</button>';
+4 -5
View File
@@ -75,16 +75,15 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault();
}
//stop propagation not required because in document click handler it checks event target
//e.stopPropagation();
//stop propagation to prevent interference with other click handlers
e.stopPropagation();
if(this.options.toggle === 'mouseenter') {
//for hover only show container
this.show();
} else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
//always close other containers when opening a new one
this.toggle(true);
}
}, this));
} else {
+12 -6
View File
@@ -26,12 +26,6 @@ $(function(){
(function ($) {
"use strict";
//store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
$.fn.bdatepicker = $.fn.datepicker.noConflict();
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
$.fn.datepicker = $.fn.bdatepicker;
}
var Date = function (options) {
this.init('date', options, Date.defaults);
this.initPicker(options, Date.defaults);
@@ -41,6 +35,18 @@ $(function(){
$.extend(Date.prototype, {
initPicker: function(options, defaults) {
// Initialize bootstrap-datepicker reference
if (!$.fn.bdatepicker) {
if ($.fn.datepicker) {
$.fn.bdatepicker = $.fn.datepicker.noConflict();
if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
$.fn.datepicker = $.fn.bdatepicker;
}
} else {
throw new Error('bootstrap-datepicker not found. Please include bootstrap-datepicker before x-editable.');
}
}
//'format' is set directly from settings or data-* attributes
//by default viewformat equals to format
-87
View File
@@ -1,87 +0,0 @@
.form-control .select2-choice {
border: 0;
border-radius: 2px;
}
.form-control .select2-choice .select2-arrow {
border-radius: 0 2px 2px 0;
}
.form-control.select2-container {
height: auto !important;
padding: 0px;
}
.form-control.select2-container.select2-dropdown-open {
border-color: #5897FB;
border-radius: 3px 3px 0 0;
}
.form-control .select2-container.select2-dropdown-open .select2-choices {
border-radius: 3px 3px 0 0;
}
.form-control.select2-container .select2-choices {
border: 0 !important;
border-radius: 3px;
}
.control-group.warning .select2-container .select2-choice,
.control-group.warning .select2-container .select2-choices,
.control-group.warning .select2-container-active .select2-choice,
.control-group.warning .select2-container-active .select2-choices,
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.warning .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #C09853 !important;
}
.control-group.warning .select2-container .select2-choice div {
border-left: 1px solid #C09853 !important;
background: #FCF8E3 !important;
}
.control-group.error .select2-container .select2-choice,
.control-group.error .select2-container .select2-choices,
.control-group.error .select2-container-active .select2-choice,
.control-group.error .select2-container-active .select2-choices,
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.error .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.error .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #B94A48 !important;
}
.control-group.error .select2-container .select2-choice div {
border-left: 1px solid #B94A48 !important;
background: #F2DEDE !important;
}
.control-group.info .select2-container .select2-choice,
.control-group.info .select2-container .select2-choices,
.control-group.info .select2-container-active .select2-choice,
.control-group.info .select2-container-active .select2-choices,
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.info .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.info .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #3A87AD !important;
}
.control-group.info .select2-container .select2-choice div {
border-left: 1px solid #3A87AD !important;
background: #D9EDF7 !important;
}
.control-group.success .select2-container .select2-choice,
.control-group.success .select2-container .select2-choices,
.control-group.success .select2-container-active .select2-choice,
.control-group.success .select2-container-active .select2-choices,
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choice,
.control-group.success .select2-dropdown-open.select2-drop-above .select2-choices,
.control-group.success .select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #468847 !important;
}
.control-group.success .select2-container .select2-choice div {
border-left: 1px solid #468847 !important;
background: #DFF0D8 !important;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

-615
View File
@@ -1,615 +0,0 @@
/*
Version: 3.4.4 Timestamp: Thu Oct 24 13:23:11 PDT 2013
*/
.select2-container {
margin: 0;
position: relative;
display: inline-block;
/* inline-block for ie7 */
zoom: 1;
*display: inline;
vertical-align: middle;
}
.select2-container,
.select2-drop,
.select2-search,
.select2-search input {
/*
Force border-box so that % widths fit the parent
container without overlap because of margin/padding.
More Info : http://www.quirksmode.org/css/box.html
*/
-webkit-box-sizing: border-box; /* webkit */
-moz-box-sizing: border-box; /* firefox */
box-sizing: border-box; /* css3 */
}
.select2-container .select2-choice {
display: block;
height: 26px;
padding: 0 0 0 8px;
overflow: hidden;
position: relative;
border: 1px solid #aaa;
white-space: nowrap;
line-height: 26px;
color: #444;
text-decoration: none;
border-radius: 4px;
background-clip: padding-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #fff;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
background-image: linear-gradient(top, #fff 0%, #eee 50%);
}
.select2-container.select2-drop-above .select2-choice {
border-bottom-color: #aaa;
border-radius: 0 0 4px 4px;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
background-image: linear-gradient(top, #eee 0%, #fff 90%);
}
.select2-container.select2-allowclear .select2-choice .select2-chosen {
margin-right: 42px;
}
.select2-container .select2-choice > .select2-chosen {
margin-right: 26px;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.select2-container .select2-choice abbr {
display: none;
width: 12px;
height: 12px;
position: absolute;
right: 24px;
top: 8px;
font-size: 1px;
text-decoration: none;
border: 0;
background: url('select2.png') right top no-repeat;
cursor: pointer;
outline: 0;
}
.select2-container.select2-allowclear .select2-choice abbr {
display: inline-block;
}
.select2-container .select2-choice abbr:hover {
background-position: right -11px;
cursor: pointer;
}
.select2-drop-mask {
border: 0;
margin: 0;
padding: 0;
position: fixed;
left: 0;
top: 0;
min-height: 100%;
min-width: 100%;
height: auto;
width: auto;
opacity: 0;
z-index: 9998;
/* styles required for IE to work */
background-color: #fff;
filter: alpha(opacity=0);
}
.select2-drop {
width: 100%;
margin-top: -1px;
position: absolute;
z-index: 9999;
top: 100%;
background: #fff;
color: #000;
border: 1px solid #aaa;
border-top: 0;
border-radius: 0 0 4px 4px;
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
}
.select2-drop-auto-width {
border-top: 1px solid #aaa;
width: auto;
}
.select2-drop-auto-width .select2-search {
padding-top: 4px;
}
.select2-drop.select2-drop-above {
margin-top: 1px;
border-top: 1px solid #aaa;
border-bottom: 0;
border-radius: 4px 4px 0 0;
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
}
.select2-drop-active {
border: 1px solid #5897fb;
border-top: none;
}
.select2-drop.select2-drop-above.select2-drop-active {
border-top: 1px solid #5897fb;
}
.select2-container .select2-choice .select2-arrow {
display: inline-block;
width: 18px;
height: 100%;
position: absolute;
right: 0;
top: 0;
border-left: 1px solid #aaa;
border-radius: 0 4px 4px 0;
background-clip: padding-box;
background: #ccc;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
background-image: linear-gradient(top, #ccc 0%, #eee 60%);
}
.select2-container .select2-choice .select2-arrow b {
display: block;
width: 100%;
height: 100%;
background: url('select2.png') no-repeat 0 1px;
}
.select2-search {
display: inline-block;
width: 100%;
min-height: 26px;
margin: 0;
padding-left: 4px;
padding-right: 4px;
position: relative;
z-index: 10000;
white-space: nowrap;
}
.select2-search input {
width: 100%;
height: auto !important;
min-height: 26px;
padding: 4px 20px 4px 5px;
margin: 0;
outline: 0;
font-family: sans-serif;
font-size: 1em;
border: 1px solid #aaa;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
background: #fff url('select2.png') no-repeat 100% -22px;
background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #fff 85%, #eee 99%);
}
.select2-drop.select2-drop-above .select2-search input {
margin-top: 4px;
}
.select2-search input.select2-active {
background: #fff url('select2-spinner.gif') no-repeat 100%;
background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(top, #fff 85%, #eee 99%);
}
.select2-container-active .select2-choice,
.select2-container-active .select2-choices {
border: 1px solid #5897fb;
outline: none;
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
}
.select2-dropdown-open .select2-choice {
border-bottom-color: transparent;
-webkit-box-shadow: 0 1px 0 #fff inset;
box-shadow: 0 1px 0 #fff inset;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-color: #eee;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
background-image: linear-gradient(top, #fff 0%, #eee 50%);
}
.select2-dropdown-open.select2-drop-above .select2-choice,
.select2-dropdown-open.select2-drop-above .select2-choices {
border: 1px solid #5897fb;
border-top-color: transparent;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
background-image: linear-gradient(bottom, #fff 0%, #eee 50%);
}
.select2-dropdown-open .select2-choice .select2-arrow {
background: transparent;
border-left: none;
filter: none;
}
.select2-dropdown-open .select2-choice .select2-arrow b {
background-position: -18px 1px;
}
/* results */
.select2-results {
max-height: 200px;
padding: 0 0 0 4px;
margin: 4px 4px 4px 0;
position: relative;
overflow-x: hidden;
overflow-y: auto;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.select2-results ul.select2-result-sub {
margin: 0;
padding-left: 0;
}
.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px }
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px }
.select2-results li {
list-style: none;
display: list-item;
background-image: none;
}
.select2-results li.select2-result-with-children > .select2-result-label {
font-weight: bold;
}
.select2-results .select2-result-label {
padding: 3px 7px 4px;
margin: 0;
cursor: pointer;
min-height: 1em;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.select2-results .select2-highlighted {
background: #3875d7;
color: #fff;
}
.select2-results li em {
background: #feffde;
font-style: normal;
}
.select2-results .select2-highlighted em {
background: transparent;
}
.select2-results .select2-highlighted ul {
background: #fff;
color: #000;
}
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-selection-limit {
background: #f4f4f4;
display: list-item;
}
/*
disabled look for disabled choices in the results dropdown
*/
.select2-results .select2-disabled.select2-highlighted {
color: #666;
background: #f4f4f4;
display: list-item;
cursor: default;
}
.select2-results .select2-disabled {
background: #f4f4f4;
display: list-item;
cursor: default;
}
.select2-results .select2-selected {
display: none;
}
.select2-more-results.select2-active {
background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
}
.select2-more-results {
background: #f4f4f4;
display: list-item;
}
/* disabled styles */
.select2-container.select2-container-disabled .select2-choice {
background-color: #f4f4f4;
background-image: none;
border: 1px solid #ddd;
cursor: default;
}
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
background-color: #f4f4f4;
background-image: none;
border-left: 0;
}
.select2-container.select2-container-disabled .select2-choice abbr {
display: none;
}
/* multiselect */
.select2-container-multi .select2-choices {
height: auto !important;
height: 1%;
margin: 0;
padding: 0;
position: relative;
border: 1px solid #aaa;
cursor: text;
overflow: hidden;
background-color: #fff;
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
background-image: linear-gradient(top, #eee 1%, #fff 15%);
}
.select2-locked {
padding: 3px 5px 3px 5px !important;
}
.select2-container-multi .select2-choices {
min-height: 26px;
}
.select2-container-multi.select2-container-active .select2-choices {
border: 1px solid #5897fb;
outline: none;
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
}
.select2-container-multi .select2-choices li {
float: left;
list-style: none;
}
.select2-container-multi .select2-choices .select2-search-field {
margin: 0;
padding: 0;
white-space: nowrap;
}
.select2-container-multi .select2-choices .select2-search-field input {
padding: 5px;
margin: 1px 0;
font-family: sans-serif;
font-size: 100%;
color: #666;
outline: 0;
border: 0;
-webkit-box-shadow: none;
box-shadow: none;
background: transparent !important;
}
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
background: #fff url('select2-spinner.gif') no-repeat 100% !important;
}
.select2-default {
color: #999 !important;
}
.select2-container-multi .select2-choices .select2-search-choice {
padding: 3px 5px 3px 18px;
margin: 3px 0 3px 5px;
position: relative;
line-height: 13px;
color: #333;
cursor: default;
border: 1px solid #aaaaaa;
border-radius: 3px;
-webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
background-clip: padding-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-color: #e4e4e4;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
}
.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
cursor: default;
}
.select2-container-multi .select2-choices .select2-search-choice-focus {
background: #d4d4d4;
}
.select2-search-choice-close {
display: block;
width: 12px;
height: 13px;
position: absolute;
right: 3px;
top: 4px;
font-size: 1px;
outline: none;
background: url('select2.png') right top no-repeat;
}
.select2-container-multi .select2-search-choice-close {
left: 3px;
}
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
background-position: right -11px;
}
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
background-position: right -11px;
}
/* disabled styles */
.select2-container-multi.select2-container-disabled .select2-choices {
background-color: #f4f4f4;
background-image: none;
border: 1px solid #ddd;
cursor: default;
}
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
padding: 3px 5px 3px 5px;
border: 1px solid #ddd;
background-image: none;
background-color: #f4f4f4;
}
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
background: none;
}
/* end multiselect */
.select2-result-selectable .select2-match,
.select2-result-unselectable .select2-match {
text-decoration: underline;
}
.select2-offscreen, .select2-offscreen:focus {
clip: rect(0 0 0 0) !important;
width: 1px !important;
height: 1px !important;
border: 0 !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
position: absolute !important;
outline: 0 !important;
left: 0px !important;
top: 0px !important;
}
.select2-display-none {
display: none;
}
.select2-measure-scrollbar {
position: absolute;
top: -10000px;
left: -10000px;
width: 100px;
height: 100px;
overflow: scroll;
}
/* Retina-ize icons */
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) {
.select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice .select2-arrow b {
background-image: url('select2x2.png') !important;
background-repeat: no-repeat !important;
background-size: 60px 40px !important;
}
.select2-search input {
background-position: 100% -21px !important;
}
}
-3251
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 845 B

+142 -98
View File
@@ -1,21 +1,18 @@
/**
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.
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options.
You should manually download and include select2 distributive:
You should manually download and include Select2 v4.x distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script>
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script>
To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme):
<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>
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2
@extends abstractinput
@@ -44,36 +41,40 @@ $(function(){
minimumInputLength: 1
}
});
//remote source (advanced)
//remote source (advanced) - Select2 v4.x format
$('#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 };
delay: 250,
data: function (params) {
return {
query: params.term,
page: params.page
};
},
results: function (data, page) {
return { results: data };
}
processResults: function (data, params) {
return {
results: data.map(function(item) {
return {
id: item.CountryId,
text: item.CountryName
};
})
};
},
cache: true
},
formatResult: function (item) {
return item.CountryName;
templateResult: function (item) {
return item.text || item.CountryName;
},
formatSelection: function (item) {
return item.CountryName;
},
initSelection: function (element, callback) {
return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
}
templateSelection: function (item) {
return item.text || item.CountryName;
}
}
});
});
@@ -104,12 +105,19 @@ $(function(){
if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {};
//some default ajax params
//default ajax params for Select2 v4.x
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
options.select2.ajax.data = function(params) {
return {
query: params.term,
page: params.page
};
};
}
if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
if(!options.select2.ajax.processResults) {
options.select2.ajax.processResults = function(data) {
return {results: data };
};
}
options.select2.ajax.url = source;
} else {
@@ -126,16 +134,8 @@ $(function(){
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;
this.formatSelection = this.options.select2.templateSelection;
if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; };
}
@@ -146,20 +146,8 @@ $(function(){
$.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() {
@@ -168,15 +156,33 @@ $(function(){
}
},
autosubmit: function() {
var self = this;
var submitting = false;
// Use Select2 v4.x events for autosubmit - avoid double submissions
this.$input.on('select2:select select2:unselect', $.proxy(function(e){
if (!submitting) {
submitting = true;
setTimeout(function() {
$(self.$input).closest('form').submit();
submitting = false;
}, 100);
}
}, this));
},
value2html: function(value, element) {
var text = '', data,
that = this;
if(this.options.select2.tags) { //in tags mode just assign value
// Use stored selected data if available (for visual display after selection)
if(this.selectedData && this.selectedData.length > 0) {
data = this.selectedData;
} else 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);
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; });
} else {
//can not get list of possible values
//(e.g. autotext for select2 with ajax source)
@@ -195,7 +201,6 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element);
},
@@ -204,45 +209,90 @@ $(function(){
},
value2input: function(value) {
// if value array => join it anyway
if(Array.isArray(value)) {
value = value.join(this.getSeparator());
}
//for remote source just set value, text is updated by initSelection
// For remote sources with existing value, create option element before Select2 init
if(this.isRemote && !this.isMultiple && value) {
var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var text = $el.text();
var $option = new Option(text, value, true, true);
this.$input.append($option);
}
}
//initialize select2 if not already done
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);
// Set up minimal event handling AFTER initialization
this.$input.on('select2:select', $.proxy(function(e) {
if (e.params && e.params.data) {
var selectedData = e.params.data;
this.selectedData = [selectedData];
// Fix Select2's visual display by ensuring the option exists and is selected
var $existingOption = this.$input.find('option[value="' + selectedData.id + '"]');
if ($existingOption.length === 0) {
// Create the option if it doesn't exist
var $option = $('<option></option>')
.attr('value', selectedData.id)
.text(selectedData.text)
.prop('selected', true);
this.$input.append($option);
} else {
// Make sure existing option is selected
$existingOption.prop('selected', true);
}
// Force Select2 to update its display
this.$input.trigger('change.select2');
// Mark that a selection was just made (for blur handling)
this._justSelected = true;
}
}
}, this));
this.$input.on('select2:unselect', $.proxy(function(e) {
this.selectedData = [];
}, this));
// Ensure Select2 doesn't interfere with x-editable's document click handling
// by making sure clicks on Select2 elements don't stop propagation
this.$input.on('select2:open', $.proxy(function(e) {
// Find the Select2 dropdown container and ensure it allows document clicks to propagate
setTimeout(function() {
$('.select2-container--open .select2-dropdown').off('click.editable-prevent-close');
$('.select2-container--open .select2-results').off('click.editable-prevent-close');
}, 10);
}, this));
} else {
//update value on existing select2
this.$input.val(value).trigger('change.select2');
}
},
input2value: function() {
return this.$input.select2('val');
var val = this.$input.val();
// For Select2 v4.x, ensure we get the actual selected value
if (this.$input.data('select2')) {
var selectedData = this.$input.select2('data');
if (selectedData && selectedData.length > 0) {
val = this.isMultiple ? selectedData.map(function(item) { return item.id; }) : selectedData[0].id;
}
}
return val;
},
str2value: function(str, separator) {
@@ -265,16 +315,10 @@ $(function(){
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;
// Select2 v4.x uses different separator handling
return this.options.select2.separator || ',';
},
/*
@@ -310,7 +354,7 @@ $(function(){
**/
tpl:'<input type="hidden">',
/**
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
Configuration of select2. [Full list of options](https://select2.org/configuration).
@property select2
@type object
@@ -347,4 +391,4 @@ $(function(){
$.fn.editabletypes.select2 = Constructor;
}(window.jQuery));
}(window.jQuery));