3 Commits

Author SHA1 Message Date
Micha ab45b130b1 Clean up debug code: remove all console.log statements
- Removed all debug console.log statements from source files
- Cleaned up date.js and datefield.js of debugging output
- Simplified test.js by removing extensive debug logging
- Reduced bundle size and improved production readiness
- Maintained all functionality while removing debug clutter

Code is now production-ready without console noise.
2025-07-26 15:46:30 +02:00
Micha 3eacb7c438 Enhance datepicker UX: position above input with proper styling
- Added CSS positioning to display inline datepicker above input field
- Enhanced datepicker styling with background, border and shadow
- Improved template with datepicker-above class for better positioning
- Maintains Bootstrap 5 compatibility and inline mode functionality

This provides a better user experience by preventing the datepicker
from appearing below and potentially being cut off by page boundaries.
2025-07-26 15:33:02 +02:00
Micha f4dc8a3dd0 Fix datepicker functionality for Bootstrap 5 x-editable
- Updated Gruntfile to use npm bootstrap-datepicker instead of bundled version
- Fixed DPGlobal reference from bdatepicker to datepicker
- Changed mode from inline to popup to ensure date input type is used
- Fixed missing type assignment in editable element initialization
- Enhanced input2value function with fallback logic to extract dates from dates array
- Added comprehensive debugging and error handling
- Datepicker now properly opens, captures selections, and submits formatted dates
2025-07-26 14:25:39 +02:00
45 changed files with 5371 additions and 6677 deletions
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CakeStormCakeSettings">
<option name="cakeVersionAbsorption">
<map>
<entry key="Controller" value="/controllers/" />
<entry key="View" value="/views/" />
<entry key="Model" value="/models/" />
<entry key="Helper" value="/views/helpers/" />
<entry key="Component" value="/controllers/components/" />
<entry key="Behavior" value="/models/behaviors/" />
<entry key="Plugin" value="plugins/" />
<entry key="Shell" value="/vendors/shell" />
<entry key="Task" value="/vendors/Shell/Task" />
<entry key="ControllerTest" value="/tests/cases/controllers/" />
<entry key="ModelTest" value="/tests/cases/models/" />
<entry key="BehaviorTest" value="/tests/cases/behaviors/" />
<entry key="ComponentTest" value="/tests/cases/components/" />
<entry key="HelperTest" value="/tests/cases/helpers/" />
<entry key="TestFile" value="test" />
<entry key="FileSeparator" value="." />
<entry key="FileWordSeparator" value="_" />
<entry key="Fixture" value="/tests/fixtures/" />
<entry key="Element" value="elements/" />
<entry key="FixtureFile" value="fixture" />
<entry key="Layout" value="layouts/" />
</map>
</option>
<option name="cakeVersion" value="1" />
</component>
</project>
+1 -1
View File
@@ -2,7 +2,7 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/x-editable-bs5.iml" filepath="$PROJECT_DIR$/.idea/x-editable-bs5.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/x-editable.iml" filepath="$PROJECT_DIR$/.idea/x-editable.iml" />
</modules> </modules>
</component> </component>
</project> </project>
Generated
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>
View File
+3 -4
View File
@@ -53,7 +53,7 @@ function getFiles() {
form: [forms+'editable-form-bootstrap5.js'], form: [forms+'editable-form-bootstrap5.js'],
container: [containers+'editable-popover5.js'], container: [containers+'editable-popover5.js'],
inputs: [ inputs: [
inputs+'date/bootstrap-datepicker/js/bootstrap-datepicker.js', // Bootstrap-datepicker now loaded from npm, not bundled
inputs+'date/date.js', inputs+'date/date.js',
inputs+'date/datefield.js', inputs+'date/datefield.js',
inputs+'datetime/datetime.js', inputs+'datetime/datetime.js',
@@ -64,7 +64,7 @@ function getFiles() {
//inputs+'typeahead.js' //inputs+'typeahead.js'
], ],
css: [ css: [
inputs+'date/bootstrap-datepicker/css/datepicker.css' // Bootstrap-datepicker CSS now loaded from npm, not bundled
//don't build datetime lib, should be included manually //don't build datetime lib, should be included manually
//inputs+'datetime/bootstrap-datetimepicker/css/datetimepicker.css' //inputs+'datetime/bootstrap-datetimepicker/css/datetimepicker.css'
] ]
@@ -268,8 +268,7 @@ module.exports = function(grunt) {
evil: false, evil: false,
globals: { globals: {
jQuery: true, jQuery: true,
console: true, console: true
bootstrap: true
}, },
}, },
js: [ 'Gruntfile.js', js: [ 'Gruntfile.js',
-92
View File
@@ -1,92 +0,0 @@
# 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: ...`
Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+16 -2
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
.editableform { .editableform {
@@ -156,6 +156,20 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Position datepicker above input for datepicker-above class */
.datepicker-above .datepicker-inline {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.editable-container.editable-popup { .editable-container.editable-popup {
max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
} }
+212 -199
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
/** /**
@@ -1505,6 +1505,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return; return;
} }
// Set the editable's type from the input's type
this.type = this.input.type;
//set value from settings or by element's text //set value from settings or by element's text
if (this.options.value === undefined || this.options.value === null) { if (this.options.value === undefined || this.options.value === null) {
this.value = this.input.html2value(this.$element.html().trim()); this.value = this.input.html2value(this.$element.html().trim());
@@ -1540,15 +1543,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault(); e.preventDefault();
} }
//stop propagation to prevent interference with other click handlers //stop propagation not required because in document click handler it checks event target
e.stopPropagation(); //e.stopPropagation();
if(this.options.toggle === 'mouseenter') { if(this.options.toggle === 'mouseenter') {
//for hover only show container //for hover only show container
this.show(); this.show();
} else { } else {
//always close other containers when opening a new one //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
this.toggle(true); var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
} }
}, this)); }, this));
} else { } else {
@@ -3600,20 +3604,23 @@ Time
}(window.jQuery)); }(window.jQuery));
/** /**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2. Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options. Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
You should manually download and include Select2 v4.x distributive: You should manually download and include select2 distributive:
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script> <script src="select2/select2.js"></script>
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme): To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link> <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>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2 @class select2
@extends abstractinput @extends abstractinput
@@ -3642,39 +3649,35 @@ $(function(){
minimumInputLength: 1 minimumInputLength: 1
} }
}); });
//remote source (advanced) - Select2 v4.x format //remote source (advanced)
$('#country').editable({ $('#country').editable({
select2: { select2: {
placeholder: 'Select Country', placeholder: 'Select Country',
allowClear: true, allowClear: true,
minimumInputLength: 3, minimumInputLength: 3,
id: function (item) {
return item.CountryId;
},
ajax: { ajax: {
url: '/getCountries', url: '/getCountries',
dataType: 'json', dataType: 'json',
delay: 250, data: function (term, page) {
data: function (params) { return { query: term };
return {
query: params.term,
page: params.page
};
}, },
processResults: function (data, params) { results: function (data, page) {
return { return { results: data };
results: data.map(function(item) { }
return {
id: item.CountryId,
text: item.CountryName
};
})
};
}, },
cache: true formatResult: function (item) {
return item.CountryName;
}, },
templateResult: function (item) { formatSelection: function (item) {
return item.text || item.CountryName; return item.CountryName;
}, },
templateSelection: function (item) { initSelection: function (element, callback) {
return item.text || item.CountryName; return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
} }
} }
}); });
@@ -3706,19 +3709,12 @@ $(function(){
if (typeof source === 'string') { if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {}; options.select2.ajax = options.select2.ajax || {};
//default ajax params for Select2 v4.x //some default ajax params
if(!options.select2.ajax.data) { if(!options.select2.ajax.data) {
options.select2.ajax.data = function(params) { options.select2.ajax.data = function(term) {return { query:term };};
return {
query: params.term,
page: params.page
};
};
} }
if(!options.select2.ajax.processResults) { if(!options.select2.ajax.results) {
options.select2.ajax.processResults = function(data) { options.select2.ajax.results = function(data) { return {results:data };};
return {results: data };
};
} }
options.select2.ajax.url = source; options.select2.ajax.url = source;
} else { } else {
@@ -3735,8 +3731,16 @@ $(function(){
this.isMultiple = this.options.select2.tags || this.options.select2.multiple; this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
this.isRemote = ('ajax' in this.options.select2); 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 //store function that renders text in select2
this.formatSelection = this.options.select2.templateSelection; this.formatSelection = this.options.select2.formatSelection;
if (typeof(this.formatSelection) !== "function") { if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; }; this.formatSelection = function (e) { return e.text; };
} }
@@ -3748,6 +3752,18 @@ $(function(){
render: function() { render: function() {
this.setClass(); 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 //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@@ -3757,33 +3773,15 @@ $(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) { value2html: function(value, element) {
var text = '', data, var text = '', data,
that = this; that = this;
// Use stored selected data if available (for visual display after selection) if(this.options.select2.tags) { //in tags mode just assign value
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 = value;
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
} else if(this.sourceData) { } else if(this.sourceData) {
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; }); data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
} else { } else {
//can not get list of possible values //can not get list of possible values
//(e.g. autotext for select2 with ajax source) //(e.g. autotext for select2 with ajax source)
@@ -3802,6 +3800,7 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text; text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element); Constructor.superclass.value2html.call(this, text, element);
}, },
@@ -3810,96 +3809,45 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
// if value array => join it anyway // if value array => join it anyway
if(Array.isArray(value)) { if(Array.isArray(value)) {
value = value.join(this.getSeparator()); value = value.join(this.getSeparator());
} }
// For remote sources with existing value, create option element before Select2 init //for remote source just set value, text is updated by initSelection
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')) { if(!this.$input.data('select2')) {
this.$input.val(value); this.$input.val(value);
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
// 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 { } else {
// Make sure existing option is selected //second argument needed to separate initial change from user's click (for autosubmit)
$existingOption.prop('selected', true); this.$input.val(value).trigger('change', true);
//Uncaught Error: cannot call val() if initSelection() is not defined
//this.$input.select2('val', value);
} }
// Force Select2 to update its display // if defined remote source AND no multiple mode AND no user's initSelection provided -->
this.$input.trigger('change.select2'); // 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;
// Mark that a selection was just made (for blur handling) if(!customId && !customText) {
this._justSelected = true; var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var data = {id: value, text: $el.text()};
this.$input.select2('data', data);
}
} }
}, 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() { input2value: function() {
var val = this.$input.val(); return this.$input.select2('val');
// --- Handle Bootstrap Datepicker ---
var dp = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
val = dp.getFormattedDate(this.options.format || 'yyyy-mm-dd');
}
// --- Handle Select2 v4.x ---
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) { str2value: function(str, separator) {
@@ -3922,10 +3870,16 @@ $(function(){
return val; return val;
}, },
autosubmit: function() {
this.$input.on('change', function(e, isInitial){
if(!isInitial) {
$(this).closest('form').submit();
}
});
},
getSeparator: function() { getSeparator: function() {
// Select2 v4.x uses different separator handling return this.options.select2.separator || $.fn.select2.defaults.separator;
return this.options.select2.separator || ',';
}, },
/* /*
@@ -3961,7 +3915,7 @@ $(function(){
**/ **/
tpl:'<input type="hidden">', tpl:'<input type="hidden">',
/** /**
Configuration of select2. [Full list of options](https://select2.org/configuration). Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
@property select2 @property select2
@type object @type object
@@ -3999,6 +3953,7 @@ $(function(){
$.fn.editabletypes.select2 = Constructor; $.fn.editabletypes.select2 = Constructor;
}(window.jQuery)); }(window.jQuery));
/** /**
* Combodate - 1.0.5 * Combodate - 1.0.5
* Dropdown date and time picker. * Dropdown date and time picker.
@@ -4935,27 +4890,29 @@ $(function(){
(function ($) { (function ($) {
"use strict"; "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) { var Date = function (options) {
this.init('date', options, Date.defaults); this.init('date', options, Date.defaults);
this.initPicker(options, Date.defaults); this.initPicker(options, Date.defaults);
// Ensure type is set correctly
this.type = 'date';
}; };
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput); $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
$.extend(Date.prototype, { $.extend(Date.prototype, {
initPicker: function(options, defaults) { prerender: function() {
// Initialize bootstrap-datepicker reference // Call parent prerender
if (!$.fn.bdatepicker) { Date.superclass.prerender.call(this);
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.');
}
}
initPicker: function(options, defaults) {
//'format' is set directly from settings or data-* attributes //'format' is set directly from settings or data-* attributes
//by default viewformat equals to format //by default viewformat equals to format
@@ -4975,8 +4932,8 @@ $(function(){
//language //language
this.options.datepicker.language = this.options.datepicker.language || 'en'; this.options.datepicker.language = this.options.datepicker.language || 'en';
//store DPglobal //store DPglobal - use datepicker instead of bdatepicker
this.dpg = $.fn.bdatepicker.DPGlobal; this.dpg = $.fn.datepicker.DPGlobal;
//store parsed formats //store parsed formats
this.parsedFormat = this.dpg.parseFormat(this.options.format); this.parsedFormat = this.dpg.parseFormat(this.options.format);
@@ -4984,7 +4941,22 @@ $(function(){
}, },
render: function () { render: function () {
this.$input.bdatepicker(this.options.datepicker); // Ensure we have an input element
if (!this.$input || !this.$input.length) {
return;
}
// Initialize datepicker immediately
try {
this.$input.datepicker(this.options.datepicker);
// Force set the initial value if we have one
if (this.value) {
this.$input.datepicker('setDate', this.value);
}
} catch (error) {
// Silently handle datepicker initialization errors
}
//"clear" link //"clear" link
if(this.options.clear) { if(this.options.clear) {
@@ -4999,20 +4971,8 @@ $(function(){
}, },
value2html: function(value, element) { value2html: function(value, element) {
let text = ''; var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
Date.superclass.value2html.call(this, text, element);
if (value) {
if (typeof value === 'string') {
text = value;
} else if (value instanceof Date && typeof value.getUTCDate === 'function') {
text = this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language);
}
}
// direct fallback: set text without using editableutils
if (element) {
element.textContent = text;
}
}, },
html2value: function(html) { html2value: function(html) {
@@ -5020,17 +4980,7 @@ $(function(){
}, },
value2str: function(value) { value2str: function(value) {
if (!value) { return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
return '';
}
// If value is already a string (like "2025-11-27"), just return it.
if (typeof value === 'string') {
return value;
}
// Otherwise, assume it's a Date object and format it.
return this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language);
}, },
str2value: function(str) { str2value: function(str) {
@@ -5042,15 +4992,42 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
this.$input.bdatepicker('update', value); // Ensure datepicker is initialized before trying to update
if (!this.$input.data('datepicker')) {
this.$input.datepicker(this.options.datepicker);
}
this.$input.datepicker('update', value);
}, },
input2value: function() { input2value: function() {
const dp = this.$input.data('datepicker'); var datepicker = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
return dp.getFormattedDate(this.options.format || 'yyyy-mm-dd'); if (datepicker) {
if (datepicker.date) {
return datepicker.date;
} }
return this.$input.val();
// Try getting date from dates array
if (datepicker.dates && datepicker.dates.length > 0) {
return datepicker.dates[0];
}
// Try using getDate method
if (typeof datepicker.getDate === 'function') {
var dateFromMethod = datepicker.getDate();
return dateFromMethod;
}
}
// Fallback: try to parse the input value directly
var inputVal = this.$input.val();
if (inputVal) {
var parsedDate = this.parseDate(inputVal, this.parsedViewFormat);
return parsedDate;
}
return null;
}, },
activate: function() { activate: function() {
@@ -5185,6 +5162,9 @@ Automatically shown in inline mode.
var DateField = function (options) { var DateField = function (options) {
this.init('datefield', options, DateField.defaults); this.init('datefield', options, DateField.defaults);
this.initPicker(options, DateField.defaults); this.initPicker(options, DateField.defaults);
// Ensure type is set correctly
this.type = 'datefield';
}; };
$.fn.editableutils.inherit(DateField, $.fn.editabletypes.date); $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
@@ -5195,8 +5175,8 @@ Automatically shown in inline mode.
this.setClass(); this.setClass();
this.setAttr('placeholder'); this.setAttr('placeholder');
//bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js) //use datepicker instead of bdatepicker
this.$tpl.bdatepicker(this.options.datepicker); this.$tpl.datepicker(this.options.datepicker);
//need to disable original event handlers //need to disable original event handlers
this.$input.off('focus keydown'); this.$input.off('focus keydown');
@@ -5204,17 +5184,48 @@ Automatically shown in inline mode.
//update value of datepicker //update value of datepicker
this.$input.keyup($.proxy(function(){ this.$input.keyup($.proxy(function(){
this.$tpl.removeData('date'); this.$tpl.removeData('date');
this.$tpl.bdatepicker('update'); this.$tpl.datepicker('update');
}, this)); }, this));
}, },
value2input: function(value) { value2input: function(value) {
this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : ''); var formattedValue = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
this.$tpl.bdatepicker('update'); this.$input.val(formattedValue);
this.$tpl.datepicker('update');
}, },
input2value: function() { input2value: function() {
// First try the container datepicker (ideal case)
var containerDatepicker = this.$tpl.data('datepicker');
if (containerDatepicker && containerDatepicker.dates && containerDatepicker.dates.length > 0) {
return containerDatepicker.dates[0];
}
// Fallback: try the input datepicker (in case manual init worked)
var inputDatepicker = this.$input.data('datepicker');
if (inputDatepicker && inputDatepicker.dates && inputDatepicker.dates.length > 0) {
return inputDatepicker.dates[0];
}
// Try getDate methods
if (containerDatepicker && typeof containerDatepicker.getDate === 'function') {
var containerDate = containerDatepicker.getDate();
if (containerDate) {
return containerDate;
}
}
if (inputDatepicker && typeof inputDatepicker.getDate === 'function') {
var inputDate = inputDatepicker.getDate();
if (inputDate) {
return inputDate;
}
}
// Final fallback to text parsing
return this.html2value(this.$input.val()); return this.html2value(this.$input.val());
}, },
@@ -5231,19 +5242,21 @@ Automatically shown in inline mode.
/** /**
@property tpl @property tpl
**/ **/
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>', tpl:'<div class="input-group input-group-sm date datepicker-above" style="width: 200px; border: 1px solid #dee2e6; border-radius: 0.375rem; position: relative;"><input type="text" class="form-control form-control-sm" style="border: none;"/><span class="input-group-text" style="border: none; background: transparent;"><i class="bi bi-calendar"></i></span></div>',
/** /**
@property inputclass @property inputclass
@default 'input-small' @default 'form-control form-control-sm'
**/ **/
inputclass: 'input-small', inputclass: 'form-control form-control-sm',
/* datepicker config */ /* datepicker config */
datepicker: { datepicker: {
weekStart: 0, weekStart: 0,
startView: 0, startView: 0,
minViewMode: 0, minViewMode: 0,
autoclose: true autoclose: true,
orientation: 'top',
container: 'body'
} }
}); });
File diff suppressed because one or more lines are too long
+16 -2
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
.editableform { .editableform {
@@ -156,6 +156,20 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Position datepicker above input for datepicker-above class */
.datepicker-above .datepicker-inline {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.editable-container.editable-popup { .editable-container.editable-popup {
max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
} }
+212 -199
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
/** /**
@@ -1505,6 +1505,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return; return;
} }
// Set the editable's type from the input's type
this.type = this.input.type;
//set value from settings or by element's text //set value from settings or by element's text
if (this.options.value === undefined || this.options.value === null) { if (this.options.value === undefined || this.options.value === null) {
this.value = this.input.html2value(this.$element.html().trim()); this.value = this.input.html2value(this.$element.html().trim());
@@ -1540,15 +1543,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault(); e.preventDefault();
} }
//stop propagation to prevent interference with other click handlers //stop propagation not required because in document click handler it checks event target
e.stopPropagation(); //e.stopPropagation();
if(this.options.toggle === 'mouseenter') { if(this.options.toggle === 'mouseenter') {
//for hover only show container //for hover only show container
this.show(); this.show();
} else { } else {
//always close other containers when opening a new one //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
this.toggle(true); var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
} }
}, this)); }, this));
} else { } else {
@@ -3600,20 +3604,23 @@ Time
}(window.jQuery)); }(window.jQuery));
/** /**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2. Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options. Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
You should manually download and include Select2 v4.x distributive: You should manually download and include select2 distributive:
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script> <script src="select2/select2.js"></script>
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme): To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link> <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>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2 @class select2
@extends abstractinput @extends abstractinput
@@ -3642,39 +3649,35 @@ $(function(){
minimumInputLength: 1 minimumInputLength: 1
} }
}); });
//remote source (advanced) - Select2 v4.x format //remote source (advanced)
$('#country').editable({ $('#country').editable({
select2: { select2: {
placeholder: 'Select Country', placeholder: 'Select Country',
allowClear: true, allowClear: true,
minimumInputLength: 3, minimumInputLength: 3,
id: function (item) {
return item.CountryId;
},
ajax: { ajax: {
url: '/getCountries', url: '/getCountries',
dataType: 'json', dataType: 'json',
delay: 250, data: function (term, page) {
data: function (params) { return { query: term };
return {
query: params.term,
page: params.page
};
}, },
processResults: function (data, params) { results: function (data, page) {
return { return { results: data };
results: data.map(function(item) { }
return {
id: item.CountryId,
text: item.CountryName
};
})
};
}, },
cache: true formatResult: function (item) {
return item.CountryName;
}, },
templateResult: function (item) { formatSelection: function (item) {
return item.text || item.CountryName; return item.CountryName;
}, },
templateSelection: function (item) { initSelection: function (element, callback) {
return item.text || item.CountryName; return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
} }
} }
}); });
@@ -3706,19 +3709,12 @@ $(function(){
if (typeof source === 'string') { if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {}; options.select2.ajax = options.select2.ajax || {};
//default ajax params for Select2 v4.x //some default ajax params
if(!options.select2.ajax.data) { if(!options.select2.ajax.data) {
options.select2.ajax.data = function(params) { options.select2.ajax.data = function(term) {return { query:term };};
return {
query: params.term,
page: params.page
};
};
} }
if(!options.select2.ajax.processResults) { if(!options.select2.ajax.results) {
options.select2.ajax.processResults = function(data) { options.select2.ajax.results = function(data) { return {results:data };};
return {results: data };
};
} }
options.select2.ajax.url = source; options.select2.ajax.url = source;
} else { } else {
@@ -3735,8 +3731,16 @@ $(function(){
this.isMultiple = this.options.select2.tags || this.options.select2.multiple; this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
this.isRemote = ('ajax' in this.options.select2); 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 //store function that renders text in select2
this.formatSelection = this.options.select2.templateSelection; this.formatSelection = this.options.select2.formatSelection;
if (typeof(this.formatSelection) !== "function") { if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; }; this.formatSelection = function (e) { return e.text; };
} }
@@ -3748,6 +3752,18 @@ $(function(){
render: function() { render: function() {
this.setClass(); 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 //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@@ -3757,33 +3773,15 @@ $(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) { value2html: function(value, element) {
var text = '', data, var text = '', data,
that = this; that = this;
// Use stored selected data if available (for visual display after selection) if(this.options.select2.tags) { //in tags mode just assign value
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 = value;
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
} else if(this.sourceData) { } else if(this.sourceData) {
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; }); data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
} else { } else {
//can not get list of possible values //can not get list of possible values
//(e.g. autotext for select2 with ajax source) //(e.g. autotext for select2 with ajax source)
@@ -3802,6 +3800,7 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text; text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element); Constructor.superclass.value2html.call(this, text, element);
}, },
@@ -3810,96 +3809,45 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
// if value array => join it anyway // if value array => join it anyway
if(Array.isArray(value)) { if(Array.isArray(value)) {
value = value.join(this.getSeparator()); value = value.join(this.getSeparator());
} }
// For remote sources with existing value, create option element before Select2 init //for remote source just set value, text is updated by initSelection
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')) { if(!this.$input.data('select2')) {
this.$input.val(value); this.$input.val(value);
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
// 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 { } else {
// Make sure existing option is selected //second argument needed to separate initial change from user's click (for autosubmit)
$existingOption.prop('selected', true); this.$input.val(value).trigger('change', true);
//Uncaught Error: cannot call val() if initSelection() is not defined
//this.$input.select2('val', value);
} }
// Force Select2 to update its display // if defined remote source AND no multiple mode AND no user's initSelection provided -->
this.$input.trigger('change.select2'); // 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;
// Mark that a selection was just made (for blur handling) if(!customId && !customText) {
this._justSelected = true; var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var data = {id: value, text: $el.text()};
this.$input.select2('data', data);
}
} }
}, 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() { input2value: function() {
var val = this.$input.val(); return this.$input.select2('val');
// --- Handle Bootstrap Datepicker ---
var dp = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
val = dp.getFormattedDate(this.options.format || 'yyyy-mm-dd');
}
// --- Handle Select2 v4.x ---
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) { str2value: function(str, separator) {
@@ -3922,10 +3870,16 @@ $(function(){
return val; return val;
}, },
autosubmit: function() {
this.$input.on('change', function(e, isInitial){
if(!isInitial) {
$(this).closest('form').submit();
}
});
},
getSeparator: function() { getSeparator: function() {
// Select2 v4.x uses different separator handling return this.options.select2.separator || $.fn.select2.defaults.separator;
return this.options.select2.separator || ',';
}, },
/* /*
@@ -3961,7 +3915,7 @@ $(function(){
**/ **/
tpl:'<input type="hidden">', tpl:'<input type="hidden">',
/** /**
Configuration of select2. [Full list of options](https://select2.org/configuration). Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
@property select2 @property select2
@type object @type object
@@ -3999,6 +3953,7 @@ $(function(){
$.fn.editabletypes.select2 = Constructor; $.fn.editabletypes.select2 = Constructor;
}(window.jQuery)); }(window.jQuery));
/** /**
* Combodate - 1.0.5 * Combodate - 1.0.5
* Dropdown date and time picker. * Dropdown date and time picker.
@@ -5003,27 +4958,29 @@ $(function(){
(function ($) { (function ($) {
"use strict"; "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) { var Date = function (options) {
this.init('date', options, Date.defaults); this.init('date', options, Date.defaults);
this.initPicker(options, Date.defaults); this.initPicker(options, Date.defaults);
// Ensure type is set correctly
this.type = 'date';
}; };
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput); $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
$.extend(Date.prototype, { $.extend(Date.prototype, {
initPicker: function(options, defaults) { prerender: function() {
// Initialize bootstrap-datepicker reference // Call parent prerender
if (!$.fn.bdatepicker) { Date.superclass.prerender.call(this);
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.');
}
}
initPicker: function(options, defaults) {
//'format' is set directly from settings or data-* attributes //'format' is set directly from settings or data-* attributes
//by default viewformat equals to format //by default viewformat equals to format
@@ -5043,8 +5000,8 @@ $(function(){
//language //language
this.options.datepicker.language = this.options.datepicker.language || 'en'; this.options.datepicker.language = this.options.datepicker.language || 'en';
//store DPglobal //store DPglobal - use datepicker instead of bdatepicker
this.dpg = $.fn.bdatepicker.DPGlobal; this.dpg = $.fn.datepicker.DPGlobal;
//store parsed formats //store parsed formats
this.parsedFormat = this.dpg.parseFormat(this.options.format); this.parsedFormat = this.dpg.parseFormat(this.options.format);
@@ -5052,7 +5009,22 @@ $(function(){
}, },
render: function () { render: function () {
this.$input.bdatepicker(this.options.datepicker); // Ensure we have an input element
if (!this.$input || !this.$input.length) {
return;
}
// Initialize datepicker immediately
try {
this.$input.datepicker(this.options.datepicker);
// Force set the initial value if we have one
if (this.value) {
this.$input.datepicker('setDate', this.value);
}
} catch (error) {
// Silently handle datepicker initialization errors
}
//"clear" link //"clear" link
if(this.options.clear) { if(this.options.clear) {
@@ -5067,20 +5039,8 @@ $(function(){
}, },
value2html: function(value, element) { value2html: function(value, element) {
let text = ''; var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
Date.superclass.value2html.call(this, text, element);
if (value) {
if (typeof value === 'string') {
text = value;
} else if (value instanceof Date && typeof value.getUTCDate === 'function') {
text = this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language);
}
}
// direct fallback: set text without using editableutils
if (element) {
element.textContent = text;
}
}, },
html2value: function(html) { html2value: function(html) {
@@ -5088,17 +5048,7 @@ $(function(){
}, },
value2str: function(value) { value2str: function(value) {
if (!value) { return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
return '';
}
// If value is already a string (like "2025-11-27"), just return it.
if (typeof value === 'string') {
return value;
}
// Otherwise, assume it's a Date object and format it.
return this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language);
}, },
str2value: function(str) { str2value: function(str) {
@@ -5110,15 +5060,42 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
this.$input.bdatepicker('update', value); // Ensure datepicker is initialized before trying to update
if (!this.$input.data('datepicker')) {
this.$input.datepicker(this.options.datepicker);
}
this.$input.datepicker('update', value);
}, },
input2value: function() { input2value: function() {
const dp = this.$input.data('datepicker'); var datepicker = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
return dp.getFormattedDate(this.options.format || 'yyyy-mm-dd'); if (datepicker) {
if (datepicker.date) {
return datepicker.date;
} }
return this.$input.val();
// Try getting date from dates array
if (datepicker.dates && datepicker.dates.length > 0) {
return datepicker.dates[0];
}
// Try using getDate method
if (typeof datepicker.getDate === 'function') {
var dateFromMethod = datepicker.getDate();
return dateFromMethod;
}
}
// Fallback: try to parse the input value directly
var inputVal = this.$input.val();
if (inputVal) {
var parsedDate = this.parseDate(inputVal, this.parsedViewFormat);
return parsedDate;
}
return null;
}, },
activate: function() { activate: function() {
@@ -5253,6 +5230,9 @@ Automatically shown in inline mode.
var DateField = function (options) { var DateField = function (options) {
this.init('datefield', options, DateField.defaults); this.init('datefield', options, DateField.defaults);
this.initPicker(options, DateField.defaults); this.initPicker(options, DateField.defaults);
// Ensure type is set correctly
this.type = 'datefield';
}; };
$.fn.editableutils.inherit(DateField, $.fn.editabletypes.date); $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
@@ -5263,8 +5243,8 @@ Automatically shown in inline mode.
this.setClass(); this.setClass();
this.setAttr('placeholder'); this.setAttr('placeholder');
//bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js) //use datepicker instead of bdatepicker
this.$tpl.bdatepicker(this.options.datepicker); this.$tpl.datepicker(this.options.datepicker);
//need to disable original event handlers //need to disable original event handlers
this.$input.off('focus keydown'); this.$input.off('focus keydown');
@@ -5272,17 +5252,48 @@ Automatically shown in inline mode.
//update value of datepicker //update value of datepicker
this.$input.keyup($.proxy(function(){ this.$input.keyup($.proxy(function(){
this.$tpl.removeData('date'); this.$tpl.removeData('date');
this.$tpl.bdatepicker('update'); this.$tpl.datepicker('update');
}, this)); }, this));
}, },
value2input: function(value) { value2input: function(value) {
this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : ''); var formattedValue = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
this.$tpl.bdatepicker('update'); this.$input.val(formattedValue);
this.$tpl.datepicker('update');
}, },
input2value: function() { input2value: function() {
// First try the container datepicker (ideal case)
var containerDatepicker = this.$tpl.data('datepicker');
if (containerDatepicker && containerDatepicker.dates && containerDatepicker.dates.length > 0) {
return containerDatepicker.dates[0];
}
// Fallback: try the input datepicker (in case manual init worked)
var inputDatepicker = this.$input.data('datepicker');
if (inputDatepicker && inputDatepicker.dates && inputDatepicker.dates.length > 0) {
return inputDatepicker.dates[0];
}
// Try getDate methods
if (containerDatepicker && typeof containerDatepicker.getDate === 'function') {
var containerDate = containerDatepicker.getDate();
if (containerDate) {
return containerDate;
}
}
if (inputDatepicker && typeof inputDatepicker.getDate === 'function') {
var inputDate = inputDatepicker.getDate();
if (inputDate) {
return inputDate;
}
}
// Final fallback to text parsing
return this.html2value(this.$input.val()); return this.html2value(this.$input.val());
}, },
@@ -5299,19 +5310,21 @@ Automatically shown in inline mode.
/** /**
@property tpl @property tpl
**/ **/
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>', tpl:'<div class="input-group input-group-sm date datepicker-above" style="width: 200px; border: 1px solid #dee2e6; border-radius: 0.375rem; position: relative;"><input type="text" class="form-control form-control-sm" style="border: none;"/><span class="input-group-text" style="border: none; background: transparent;"><i class="bi bi-calendar"></i></span></div>',
/** /**
@property inputclass @property inputclass
@default 'input-small' @default 'form-control form-control-sm'
**/ **/
inputclass: 'input-small', inputclass: 'form-control form-control-sm',
/* datepicker config */ /* datepicker config */
datepicker: { datepicker: {
weekStart: 0, weekStart: 0,
startView: 0, startView: 0,
minViewMode: 0, minViewMode: 0,
autoclose: true autoclose: true,
orientation: 'top',
container: 'body'
} }
}); });
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16 -2
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
.editableform { .editableform {
@@ -156,6 +156,20 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Position datepicker above input for datepicker-above class */
.datepicker-above .datepicker-inline {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.editable-container.editable-popup { .editable-container.editable-popup {
max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
} }
+14
View File
@@ -151,3 +151,17 @@
.editable-pre-wrapped { .editable-pre-wrapped {
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Position datepicker above input for datepicker-above class */
.datepicker-above .datepicker-inline {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
File diff suppressed because one or more lines are too long
@@ -1,3 +1,15 @@
/*!
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
* Datepicker for Bootstrap v1.10.0 (https://github.com/uxsolutions/bootstrap-datepicker)
*
* Licensed under the Apache License v2.0 (https://www.apache.org/licenses/LICENSE-2.0)
*/
/*! /*!
* jQuery JavaScript Library v3.7.1 * jQuery JavaScript Library v3.7.1
* https://jquery.com/ * https://jquery.com/
File diff suppressed because one or more lines are too long
+16 -2
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
.editableform { .editableform {
@@ -156,6 +156,20 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Position datepicker above input for datepicker-above class */
.datepicker-above .datepicker-inline {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.editable-container.editable-popup { .editable-container.editable-popup {
max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
} }
+100 -145
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
/** /**
@@ -1505,6 +1505,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return; return;
} }
// Set the editable's type from the input's type
this.type = this.input.type;
//set value from settings or by element's text //set value from settings or by element's text
if (this.options.value === undefined || this.options.value === null) { if (this.options.value === undefined || this.options.value === null) {
this.value = this.input.html2value(this.$element.html().trim()); this.value = this.input.html2value(this.$element.html().trim());
@@ -1540,15 +1543,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault(); e.preventDefault();
} }
//stop propagation to prevent interference with other click handlers //stop propagation not required because in document click handler it checks event target
e.stopPropagation(); //e.stopPropagation();
if(this.options.toggle === 'mouseenter') { if(this.options.toggle === 'mouseenter') {
//for hover only show container //for hover only show container
this.show(); this.show();
} else { } else {
//always close other containers when opening a new one //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
this.toggle(true); var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
} }
}, this)); }, this));
} else { } else {
@@ -3600,20 +3604,23 @@ Time
}(window.jQuery)); }(window.jQuery));
/** /**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2. Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options. Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
You should manually download and include Select2 v4.x distributive: You should manually download and include select2 distributive:
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script> <script src="select2/select2.js"></script>
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme): To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link> <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>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2 @class select2
@extends abstractinput @extends abstractinput
@@ -3642,39 +3649,35 @@ $(function(){
minimumInputLength: 1 minimumInputLength: 1
} }
}); });
//remote source (advanced) - Select2 v4.x format //remote source (advanced)
$('#country').editable({ $('#country').editable({
select2: { select2: {
placeholder: 'Select Country', placeholder: 'Select Country',
allowClear: true, allowClear: true,
minimumInputLength: 3, minimumInputLength: 3,
id: function (item) {
return item.CountryId;
},
ajax: { ajax: {
url: '/getCountries', url: '/getCountries',
dataType: 'json', dataType: 'json',
delay: 250, data: function (term, page) {
data: function (params) { return { query: term };
return {
query: params.term,
page: params.page
};
}, },
processResults: function (data, params) { results: function (data, page) {
return { return { results: data };
results: data.map(function(item) { }
return {
id: item.CountryId,
text: item.CountryName
};
})
};
}, },
cache: true formatResult: function (item) {
return item.CountryName;
}, },
templateResult: function (item) { formatSelection: function (item) {
return item.text || item.CountryName; return item.CountryName;
}, },
templateSelection: function (item) { initSelection: function (element, callback) {
return item.text || item.CountryName; return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
} }
} }
}); });
@@ -3706,19 +3709,12 @@ $(function(){
if (typeof source === 'string') { if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {}; options.select2.ajax = options.select2.ajax || {};
//default ajax params for Select2 v4.x //some default ajax params
if(!options.select2.ajax.data) { if(!options.select2.ajax.data) {
options.select2.ajax.data = function(params) { options.select2.ajax.data = function(term) {return { query:term };};
return {
query: params.term,
page: params.page
};
};
} }
if(!options.select2.ajax.processResults) { if(!options.select2.ajax.results) {
options.select2.ajax.processResults = function(data) { options.select2.ajax.results = function(data) { return {results:data };};
return {results: data };
};
} }
options.select2.ajax.url = source; options.select2.ajax.url = source;
} else { } else {
@@ -3735,8 +3731,16 @@ $(function(){
this.isMultiple = this.options.select2.tags || this.options.select2.multiple; this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
this.isRemote = ('ajax' in this.options.select2); 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 //store function that renders text in select2
this.formatSelection = this.options.select2.templateSelection; this.formatSelection = this.options.select2.formatSelection;
if (typeof(this.formatSelection) !== "function") { if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; }; this.formatSelection = function (e) { return e.text; };
} }
@@ -3748,6 +3752,18 @@ $(function(){
render: function() { render: function() {
this.setClass(); 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 //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@@ -3757,33 +3773,15 @@ $(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) { value2html: function(value, element) {
var text = '', data, var text = '', data,
that = this; that = this;
// Use stored selected data if available (for visual display after selection) if(this.options.select2.tags) { //in tags mode just assign value
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 = value;
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
} else if(this.sourceData) { } else if(this.sourceData) {
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; }); data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
} else { } else {
//can not get list of possible values //can not get list of possible values
//(e.g. autotext for select2 with ajax source) //(e.g. autotext for select2 with ajax source)
@@ -3802,6 +3800,7 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text; text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element); Constructor.superclass.value2html.call(this, text, element);
}, },
@@ -3810,96 +3809,45 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
// if value array => join it anyway // if value array => join it anyway
if(Array.isArray(value)) { if(Array.isArray(value)) {
value = value.join(this.getSeparator()); value = value.join(this.getSeparator());
} }
// For remote sources with existing value, create option element before Select2 init //for remote source just set value, text is updated by initSelection
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')) { if(!this.$input.data('select2')) {
this.$input.val(value); this.$input.val(value);
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
// 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 { } else {
// Make sure existing option is selected //second argument needed to separate initial change from user's click (for autosubmit)
$existingOption.prop('selected', true); this.$input.val(value).trigger('change', true);
//Uncaught Error: cannot call val() if initSelection() is not defined
//this.$input.select2('val', value);
} }
// Force Select2 to update its display // if defined remote source AND no multiple mode AND no user's initSelection provided -->
this.$input.trigger('change.select2'); // 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;
// Mark that a selection was just made (for blur handling) if(!customId && !customText) {
this._justSelected = true; var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var data = {id: value, text: $el.text()};
this.$input.select2('data', data);
}
} }
}, 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() { input2value: function() {
var val = this.$input.val(); return this.$input.select2('val');
// --- Handle Bootstrap Datepicker ---
var dp = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
val = dp.getFormattedDate(this.options.format || 'yyyy-mm-dd');
}
// --- Handle Select2 v4.x ---
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) { str2value: function(str, separator) {
@@ -3922,10 +3870,16 @@ $(function(){
return val; return val;
}, },
autosubmit: function() {
this.$input.on('change', function(e, isInitial){
if(!isInitial) {
$(this).closest('form').submit();
}
});
},
getSeparator: function() { getSeparator: function() {
// Select2 v4.x uses different separator handling return this.options.select2.separator || $.fn.select2.defaults.separator;
return this.options.select2.separator || ',';
}, },
/* /*
@@ -3961,7 +3915,7 @@ $(function(){
**/ **/
tpl:'<input type="hidden">', tpl:'<input type="hidden">',
/** /**
Configuration of select2. [Full list of options](https://select2.org/configuration). Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
@property select2 @property select2
@type object @type object
@@ -3999,6 +3953,7 @@ $(function(){
$.fn.editabletypes.select2 = Constructor; $.fn.editabletypes.select2 = Constructor;
}(window.jQuery)); }(window.jQuery));
/** /**
* Combodate - 1.0.5 * Combodate - 1.0.5
* Dropdown date and time picker. * Dropdown date and time picker.
File diff suppressed because one or more lines are too long
+16 -2
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
.editableform { .editableform {
@@ -156,6 +156,20 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Position datepicker above input for datepicker-above class */
.datepicker-above .datepicker-inline {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.editable-container.editable-popup { .editable-container.editable-popup {
max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
} }
+100 -145
View File
@@ -1,5 +1,5 @@
/*! X-editable-bootstrap5 - v1.5.7 /*! X-editable - v1.5.2
* A fork of x-editable for Bootstrap 5 support. * A maintained fork of x-editable for Bootstrap 5 support.
* https://git.24unix.net/tracer/x-editable * https://git.24unix.net/tracer/x-editable
* Copyright (c) 2025 Micha Espey; Licensed MIT */ * Copyright (c) 2025 Micha Espey; Licensed MIT */
/** /**
@@ -1505,6 +1505,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return; return;
} }
// Set the editable's type from the input's type
this.type = this.input.type;
//set value from settings or by element's text //set value from settings or by element's text
if (this.options.value === undefined || this.options.value === null) { if (this.options.value === undefined || this.options.value === null) {
this.value = this.input.html2value(this.$element.html().trim()); this.value = this.input.html2value(this.$element.html().trim());
@@ -1540,15 +1543,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault(); e.preventDefault();
} }
//stop propagation to prevent interference with other click handlers //stop propagation not required because in document click handler it checks event target
e.stopPropagation(); //e.stopPropagation();
if(this.options.toggle === 'mouseenter') { if(this.options.toggle === 'mouseenter') {
//for hover only show container //for hover only show container
this.show(); this.show();
} else { } else {
//always close other containers when opening a new one //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
this.toggle(true); var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
} }
}, this)); }, this));
} else { } else {
@@ -3600,20 +3604,23 @@ Time
}(window.jQuery)); }(window.jQuery));
/** /**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2. Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options. Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
You should manually download and include Select2 v4.x distributive: You should manually download and include select2 distributive:
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script> <script src="select2/select2.js"></script>
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme): To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link> <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>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2 @class select2
@extends abstractinput @extends abstractinput
@@ -3642,39 +3649,35 @@ $(function(){
minimumInputLength: 1 minimumInputLength: 1
} }
}); });
//remote source (advanced) - Select2 v4.x format //remote source (advanced)
$('#country').editable({ $('#country').editable({
select2: { select2: {
placeholder: 'Select Country', placeholder: 'Select Country',
allowClear: true, allowClear: true,
minimumInputLength: 3, minimumInputLength: 3,
id: function (item) {
return item.CountryId;
},
ajax: { ajax: {
url: '/getCountries', url: '/getCountries',
dataType: 'json', dataType: 'json',
delay: 250, data: function (term, page) {
data: function (params) { return { query: term };
return {
query: params.term,
page: params.page
};
}, },
processResults: function (data, params) { results: function (data, page) {
return { return { results: data };
results: data.map(function(item) { }
return {
id: item.CountryId,
text: item.CountryName
};
})
};
}, },
cache: true formatResult: function (item) {
return item.CountryName;
}, },
templateResult: function (item) { formatSelection: function (item) {
return item.text || item.CountryName; return item.CountryName;
}, },
templateSelection: function (item) { initSelection: function (element, callback) {
return item.text || item.CountryName; return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
} }
} }
}); });
@@ -3706,19 +3709,12 @@ $(function(){
if (typeof source === 'string') { if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {}; options.select2.ajax = options.select2.ajax || {};
//default ajax params for Select2 v4.x //some default ajax params
if(!options.select2.ajax.data) { if(!options.select2.ajax.data) {
options.select2.ajax.data = function(params) { options.select2.ajax.data = function(term) {return { query:term };};
return {
query: params.term,
page: params.page
};
};
} }
if(!options.select2.ajax.processResults) { if(!options.select2.ajax.results) {
options.select2.ajax.processResults = function(data) { options.select2.ajax.results = function(data) { return {results:data };};
return {results: data };
};
} }
options.select2.ajax.url = source; options.select2.ajax.url = source;
} else { } else {
@@ -3735,8 +3731,16 @@ $(function(){
this.isMultiple = this.options.select2.tags || this.options.select2.multiple; this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
this.isRemote = ('ajax' in this.options.select2); 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 //store function that renders text in select2
this.formatSelection = this.options.select2.templateSelection; this.formatSelection = this.options.select2.formatSelection;
if (typeof(this.formatSelection) !== "function") { if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; }; this.formatSelection = function (e) { return e.text; };
} }
@@ -3748,6 +3752,18 @@ $(function(){
render: function() { render: function() {
this.setClass(); 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 //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@@ -3757,33 +3773,15 @@ $(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) { value2html: function(value, element) {
var text = '', data, var text = '', data,
that = this; that = this;
// Use stored selected data if available (for visual display after selection) if(this.options.select2.tags) { //in tags mode just assign value
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 = value;
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
} else if(this.sourceData) { } else if(this.sourceData) {
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; }); data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
} else { } else {
//can not get list of possible values //can not get list of possible values
//(e.g. autotext for select2 with ajax source) //(e.g. autotext for select2 with ajax source)
@@ -3802,6 +3800,7 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text; text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element); Constructor.superclass.value2html.call(this, text, element);
}, },
@@ -3810,96 +3809,45 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
// if value array => join it anyway // if value array => join it anyway
if(Array.isArray(value)) { if(Array.isArray(value)) {
value = value.join(this.getSeparator()); value = value.join(this.getSeparator());
} }
// For remote sources with existing value, create option element before Select2 init //for remote source just set value, text is updated by initSelection
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')) { if(!this.$input.data('select2')) {
this.$input.val(value); this.$input.val(value);
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
// 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 { } else {
// Make sure existing option is selected //second argument needed to separate initial change from user's click (for autosubmit)
$existingOption.prop('selected', true); this.$input.val(value).trigger('change', true);
//Uncaught Error: cannot call val() if initSelection() is not defined
//this.$input.select2('val', value);
} }
// Force Select2 to update its display // if defined remote source AND no multiple mode AND no user's initSelection provided -->
this.$input.trigger('change.select2'); // 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;
// Mark that a selection was just made (for blur handling) if(!customId && !customText) {
this._justSelected = true; var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var data = {id: value, text: $el.text()};
this.$input.select2('data', data);
}
} }
}, 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() { input2value: function() {
var val = this.$input.val(); return this.$input.select2('val');
// --- Handle Bootstrap Datepicker ---
var dp = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
val = dp.getFormattedDate(this.options.format || 'yyyy-mm-dd');
}
// --- Handle Select2 v4.x ---
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) { str2value: function(str, separator) {
@@ -3922,10 +3870,16 @@ $(function(){
return val; return val;
}, },
autosubmit: function() {
this.$input.on('change', function(e, isInitial){
if(!isInitial) {
$(this).closest('form').submit();
}
});
},
getSeparator: function() { getSeparator: function() {
// Select2 v4.x uses different separator handling return this.options.select2.separator || $.fn.select2.defaults.separator;
return this.options.select2.separator || ',';
}, },
/* /*
@@ -3961,7 +3915,7 @@ $(function(){
**/ **/
tpl:'<input type="hidden">', tpl:'<input type="hidden">',
/** /**
Configuration of select2. [Full list of options](https://select2.org/configuration). Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
@property select2 @property select2
@type object @type object
@@ -3999,6 +3953,7 @@ $(function(){
$.fn.editabletypes.select2 = Constructor; $.fn.editabletypes.select2 = Constructor;
}(window.jQuery)); }(window.jQuery));
/** /**
* Combodate - 1.0.5 * Combodate - 1.0.5
* Dropdown date and time picker. * Dropdown date and time picker.
File diff suppressed because one or more lines are too long
+246 -26
View File
@@ -1,13 +1,14 @@
{ {
"name": "x-editable-bootstrap5", "name": "@24unix/x-editable",
"version": "1.5.3", "version": "1.5.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "x-editable-bootstrap5", "name": "@24unix/x-editable",
"version": "1.5.3", "version": "1.5.2",
"dependencies": { "dependencies": {
"@anthropic-ai/claude-code": "^1.0.61",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"bootstrap-datepicker": "^1.10.0", "bootstrap-datepicker": "^1.10.0",
"bootstrap-icons": "^1.11.3", "bootstrap-icons": "^1.11.3",
@@ -30,9 +31,26 @@
"grunt-contrib-uglify": "^5.2.2", "grunt-contrib-uglify": "^5.2.2",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"webpack-cli": "^6.0.1" "webpack-cli": "^6.0.1"
}
}, },
"peerDependencies": { "node_modules/@anthropic-ai/claude-code": {
"select2": ">=4.0.0 || 4.1.0-rc.0" "version": "1.0.61",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.61.tgz",
"integrity": "sha512-+gjKzY1hsWfHoH52SgKR6E0ujCDPyyRsjyRShtZfS0urKd8VQq3D/DF3hvT3P4JJeW0YuWp5Dep0aSRON+/FFA==",
"license": "SEE LICENSE IN README.md",
"bin": {
"claude": "cli.js"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "^0.33.5",
"@img/sharp-darwin-x64": "^0.33.5",
"@img/sharp-linux-arm": "^0.33.5",
"@img/sharp-linux-arm64": "^0.33.5",
"@img/sharp-linux-x64": "^0.33.5",
"@img/sharp-win32-x64": "^0.33.5"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@@ -70,6 +88,215 @@
"node": ">=14.17.0" "node": ">=14.17.0"
} }
}, },
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8", "version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -804,9 +1031,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3412,9 +3639,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/morgan": { "node_modules/morgan": {
"version": "1.10.0", "version": "1.10.1",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3422,7 +3649,7 @@
"debug": "2.6.9", "debug": "2.6.9",
"depd": "~2.0.0", "depd": "~2.0.0",
"on-finished": "~2.3.0", "on-finished": "~2.3.0",
"on-headers": "~1.0.2" "on-headers": "~1.1.0"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
@@ -3565,9 +3792,9 @@
} }
}, },
"node_modules/on-headers": { "node_modules/on-headers": {
"version": "1.0.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4427,13 +4654,6 @@
"url": "https://opencollective.com/webpack" "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": { "node_modules/semver": {
"version": "7.7.1", "version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -4860,9 +5080,9 @@
} }
}, },
"node_modules/tar-fs": { "node_modules/tar-fs": {
"version": "3.0.8", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
"integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+5 -7
View File
@@ -1,8 +1,8 @@
{ {
"name": "x-editable-bootstrap5", "name": "@24unix/x-editable",
"title": "X-editable-bootstrap5", "title": "X-editable",
"description": "A fork of x-editable for Bootstrap 5 support.", "description": "A maintained fork of x-editable for Bootstrap 5 support.",
"version": "1.5.7", "version": "1.5.2",
"homepage": "https://git.24unix.net/tracer/x-editable", "homepage": "https://git.24unix.net/tracer/x-editable",
"author": { "author": {
"name": "Micha Espey", "name": "Micha Espey",
@@ -33,6 +33,7 @@
"module": "dist/bootstrap5-editable/js/bootstrap-editable.esm.js", "module": "dist/bootstrap5-editable/js/bootstrap-editable.esm.js",
"style": "dist/bootstrap5-editable/css/bootstrap-editable.css", "style": "dist/bootstrap5-editable/css/bootstrap-editable.css",
"dependencies": { "dependencies": {
"@anthropic-ai/claude-code": "^1.0.61",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"bootstrap-datepicker": "^1.10.0", "bootstrap-datepicker": "^1.10.0",
"bootstrap-icons": "^1.11.3", "bootstrap-icons": "^1.11.3",
@@ -41,9 +42,6 @@
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"webpack": "^5.98.0" "webpack": "^5.98.0"
}, },
"peerDependencies": {
"select2": ">=4.0.0 || 4.1.0-rc.0"
},
"devDependencies": { "devDependencies": {
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
+32
View File
@@ -0,0 +1,32 @@
/*
X-Editable Bootstrap 5 - Complete Bundle
Uses npm bootstrap-datepicker instead of bundled version
Order matches Gruntfile.js for compatibility
*/
import $ from "jquery";
// Import bootstrap-datepicker from npm
import "bootstrap-datepicker";
// Core editable functionality - EXACT ORDER from Gruntfile
import "./editable-form/editable-form.js";
import "./editable-form/editable-form-utils.js";
import "./containers/editable-container.js";
import "./containers/editable-inline.js";
import "./element/editable-element.js";
import "./inputs/abstract.js";
import "./inputs/list.js";
import "./inputs/text.js";
import "./inputs/textarea.js";
import "./inputs/select.js";
// Date input (now uses npm bootstrap-datepicker)
import "./inputs/date/date.js";
import "./inputs/date/datefield.js";
// Bootstrap 5 specific containers and forms
import "./containers/editable-popover5.js";
import "./editable-form/editable-form-bootstrap5.js";
// Export jQuery for compatibility
export default $;
+3 -22
View File
@@ -3,6 +3,7 @@
* --------------------- * ---------------------
* requires bootstrap-popover.js * requires bootstrap-popover.js
*/ */
import { Popover } from "bootstrap";
(function ($) { (function ($) {
"use strict"; "use strict";
@@ -12,7 +13,7 @@
containerName: 'popover', containerName: 'popover',
containerDataName: 'bs.popover', containerDataName: 'bs.popover',
innerCss: '.popover-body', innerCss: '.popover-body',
defaults: bootstrap.Popover.Default, defaults: Popover.Default,
initContainer: function(){ initContainer: function(){
$.extend(this.containerOptions, { $.extend(this.containerOptions, {
@@ -39,27 +40,7 @@
/* show */ /* show */
innerShow: function () { innerShow: function () {
// Preserve scroll position to prevent page jumping
var scrollTop = $(window).scrollTop();
var scrollLeft = $(window).scrollLeft();
this.call('show'); 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 */ /* hide */
@@ -83,7 +64,7 @@
call: function() { call: function() {
if ( ! $(this.$element).data(this.containerDataName)) { if ( ! $(this.$element).data(this.containerDataName)) {
$(this.$element).data(this.containerDataName, $(this.$element).data(this.containerDataName,
bootstrap.Popover.getOrCreateInstance(this.$element, this.containerOptions) Popover.getOrCreateInstance(this.$element, this.containerOptions)
); );
} }
@@ -1,6 +1,7 @@
/* /*
Editableform based on Twitter Bootstrap 5 Editableform based on Twitter Bootstrap 5
*/ */
import $ from "jquery";
(function ($) { (function ($) {
"use strict"; "use strict";
@@ -47,7 +48,7 @@ Editableform based on Twitter Bootstrap 5
'<button type="submit" class="btn btn-primary btn-sm editable-submit">'+ '<button type="submit" class="btn btn-primary btn-sm editable-submit">'+
'<i class="bi bi-check"></i>'+ '<i class="bi bi-check"></i>'+
'</button>'+ '</button>'+
'<button type="button" class="btn btn-outline-secondary btn-sm editable-cancel">'+ '<button type="button" class="btn btn-secondary btn-sm editable-cancel">'+
'<i class="bi bi-x"></i>'+ '<i class="bi bi-x"></i>'+
'</button>'; '</button>';
+14
View File
@@ -151,3 +151,17 @@
.editable-pre-wrapped { .editable-pre-wrapped {
white-space: pre-wrap; white-space: pre-wrap;
} }
/* Position datepicker above input for datepicker-above class */
.datepicker-above .datepicker-inline {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-bottom: 5px;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
+8 -4
View File
@@ -40,6 +40,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return; return;
} }
// Set the editable's type from the input's type
this.type = this.input.type;
//set value from settings or by element's text //set value from settings or by element's text
if (this.options.value === undefined || this.options.value === null) { if (this.options.value === undefined || this.options.value === null) {
this.value = this.input.html2value(this.$element.html().trim()); this.value = this.input.html2value(this.$element.html().trim());
@@ -75,15 +78,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
e.preventDefault(); e.preventDefault();
} }
//stop propagation to prevent interference with other click handlers //stop propagation not required because in document click handler it checks event target
e.stopPropagation(); //e.stopPropagation();
if(this.options.toggle === 'mouseenter') { if(this.options.toggle === 'mouseenter') {
//for hover only show container //for hover only show container
this.show(); this.show();
} else { } else {
//always close other containers when opening a new one //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
this.toggle(true); var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
} }
}, this)); }, this));
} else { } else {
+67 -45
View File
@@ -26,27 +26,29 @@ $(function(){
(function ($) { (function ($) {
"use strict"; "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) { var Date = function (options) {
this.init('date', options, Date.defaults); this.init('date', options, Date.defaults);
this.initPicker(options, Date.defaults); this.initPicker(options, Date.defaults);
// Ensure type is set correctly
this.type = 'date';
}; };
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput); $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
$.extend(Date.prototype, { $.extend(Date.prototype, {
initPicker: function(options, defaults) { prerender: function() {
// Initialize bootstrap-datepicker reference // Call parent prerender
if (!$.fn.bdatepicker) { Date.superclass.prerender.call(this);
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.');
}
}
initPicker: function(options, defaults) {
//'format' is set directly from settings or data-* attributes //'format' is set directly from settings or data-* attributes
//by default viewformat equals to format //by default viewformat equals to format
@@ -66,8 +68,8 @@ $(function(){
//language //language
this.options.datepicker.language = this.options.datepicker.language || 'en'; this.options.datepicker.language = this.options.datepicker.language || 'en';
//store DPglobal //store DPglobal - use datepicker instead of bdatepicker
this.dpg = $.fn.bdatepicker.DPGlobal; this.dpg = $.fn.datepicker.DPGlobal;
//store parsed formats //store parsed formats
this.parsedFormat = this.dpg.parseFormat(this.options.format); this.parsedFormat = this.dpg.parseFormat(this.options.format);
@@ -75,7 +77,22 @@ $(function(){
}, },
render: function () { render: function () {
this.$input.bdatepicker(this.options.datepicker); // Ensure we have an input element
if (!this.$input || !this.$input.length) {
return;
}
// Initialize datepicker immediately
try {
this.$input.datepicker(this.options.datepicker);
// Force set the initial value if we have one
if (this.value) {
this.$input.datepicker('setDate', this.value);
}
} catch (error) {
// Silently handle datepicker initialization errors
}
//"clear" link //"clear" link
if(this.options.clear) { if(this.options.clear) {
@@ -90,20 +107,8 @@ $(function(){
}, },
value2html: function(value, element) { value2html: function(value, element) {
let text = ''; var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
Date.superclass.value2html.call(this, text, element);
if (value) {
if (typeof value === 'string') {
text = value;
} else if (value instanceof Date && typeof value.getUTCDate === 'function') {
text = this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language);
}
}
// direct fallback: set text without using editableutils
if (element) {
element.textContent = text;
}
}, },
html2value: function(html) { html2value: function(html) {
@@ -111,17 +116,7 @@ $(function(){
}, },
value2str: function(value) { value2str: function(value) {
if (!value) { return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
return '';
}
// If value is already a string (like "2025-11-27"), just return it.
if (typeof value === 'string') {
return value;
}
// Otherwise, assume it's a Date object and format it.
return this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language);
}, },
str2value: function(str) { str2value: function(str) {
@@ -133,15 +128,42 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
this.$input.bdatepicker('update', value); // Ensure datepicker is initialized before trying to update
if (!this.$input.data('datepicker')) {
this.$input.datepicker(this.options.datepicker);
}
this.$input.datepicker('update', value);
}, },
input2value: function() { input2value: function() {
const dp = this.$input.data('datepicker'); var datepicker = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
return dp.getFormattedDate(this.options.format || 'yyyy-mm-dd'); if (datepicker) {
if (datepicker.date) {
return datepicker.date;
} }
return this.$input.val();
// Try getting date from dates array
if (datepicker.dates && datepicker.dates.length > 0) {
return datepicker.dates[0];
}
// Try using getDate method
if (typeof datepicker.getDate === 'function') {
var dateFromMethod = datepicker.getDate();
return dateFromMethod;
}
}
// Fallback: try to parse the input value directly
var inputVal = this.$input.val();
if (inputVal) {
var parsedDate = this.parseDate(inputVal, this.parsedViewFormat);
return parsedDate;
}
return null;
}, },
activate: function() { activate: function() {
+45 -9
View File
@@ -14,6 +14,9 @@ Automatically shown in inline mode.
var DateField = function (options) { var DateField = function (options) {
this.init('datefield', options, DateField.defaults); this.init('datefield', options, DateField.defaults);
this.initPicker(options, DateField.defaults); this.initPicker(options, DateField.defaults);
// Ensure type is set correctly
this.type = 'datefield';
}; };
$.fn.editableutils.inherit(DateField, $.fn.editabletypes.date); $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
@@ -24,8 +27,8 @@ Automatically shown in inline mode.
this.setClass(); this.setClass();
this.setAttr('placeholder'); this.setAttr('placeholder');
//bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js) //use datepicker instead of bdatepicker
this.$tpl.bdatepicker(this.options.datepicker); this.$tpl.datepicker(this.options.datepicker);
//need to disable original event handlers //need to disable original event handlers
this.$input.off('focus keydown'); this.$input.off('focus keydown');
@@ -33,17 +36,48 @@ Automatically shown in inline mode.
//update value of datepicker //update value of datepicker
this.$input.keyup($.proxy(function(){ this.$input.keyup($.proxy(function(){
this.$tpl.removeData('date'); this.$tpl.removeData('date');
this.$tpl.bdatepicker('update'); this.$tpl.datepicker('update');
}, this)); }, this));
}, },
value2input: function(value) { value2input: function(value) {
this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : ''); var formattedValue = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
this.$tpl.bdatepicker('update'); this.$input.val(formattedValue);
this.$tpl.datepicker('update');
}, },
input2value: function() { input2value: function() {
// First try the container datepicker (ideal case)
var containerDatepicker = this.$tpl.data('datepicker');
if (containerDatepicker && containerDatepicker.dates && containerDatepicker.dates.length > 0) {
return containerDatepicker.dates[0];
}
// Fallback: try the input datepicker (in case manual init worked)
var inputDatepicker = this.$input.data('datepicker');
if (inputDatepicker && inputDatepicker.dates && inputDatepicker.dates.length > 0) {
return inputDatepicker.dates[0];
}
// Try getDate methods
if (containerDatepicker && typeof containerDatepicker.getDate === 'function') {
var containerDate = containerDatepicker.getDate();
if (containerDate) {
return containerDate;
}
}
if (inputDatepicker && typeof inputDatepicker.getDate === 'function') {
var inputDate = inputDatepicker.getDate();
if (inputDate) {
return inputDate;
}
}
// Final fallback to text parsing
return this.html2value(this.$input.val()); return this.html2value(this.$input.val());
}, },
@@ -60,19 +94,21 @@ Automatically shown in inline mode.
/** /**
@property tpl @property tpl
**/ **/
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>', tpl:'<div class="input-group input-group-sm date datepicker-above" style="width: 200px; border: 1px solid #dee2e6; border-radius: 0.375rem; position: relative;"><input type="text" class="form-control form-control-sm" style="border: none;"/><span class="input-group-text" style="border: none; background: transparent;"><i class="bi bi-calendar"></i></span></div>',
/** /**
@property inputclass @property inputclass
@default 'input-small' @default 'form-control form-control-sm'
**/ **/
inputclass: 'input-small', inputclass: 'form-control form-control-sm',
/* datepicker config */ /* datepicker config */
datepicker: { datepicker: {
weekStart: 0, weekStart: 0,
startView: 0, startView: 0,
minViewMode: 0, minViewMode: 0,
autoclose: true autoclose: true,
orientation: 'top',
container: 'body'
} }
}); });
+87
View File
@@ -0,0 +1,87 @@
.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.

After

Width:  |  Height:  |  Size: 1.8 KiB

+615
View File
@@ -0,0 +1,615 @@
/*
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.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

+89 -139
View File
@@ -1,18 +1,21 @@
/** /**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/select2/select2. Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
Please see [Select2 docs](https://select2.org/) for detailed description and options. Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
You should manually download and include Select2 v4.x distributive: You should manually download and include select2 distributive:
<link href="node_modules/select2/dist/css/select2.css" rel="stylesheet" type="text/css"></link> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="node_modules/select2/dist/js/select2.js"></script> <script src="select2/select2.js"></script>
To make it **bootstrap-styled** you can use css from [select2-bootstrap-5-theme](https://github.com/apalfrey/select2-bootstrap-5-theme): To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
<link href="select2-bootstrap-5-theme.css" rel="stylesheet" type="text/css"></link> <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>
**Note:** This version requires Select2 v4.x. For remote sources, you may need to provide custom
`templateResult` and `templateSelection` functions.
@class select2 @class select2
@extends abstractinput @extends abstractinput
@@ -41,39 +44,35 @@ $(function(){
minimumInputLength: 1 minimumInputLength: 1
} }
}); });
//remote source (advanced) - Select2 v4.x format //remote source (advanced)
$('#country').editable({ $('#country').editable({
select2: { select2: {
placeholder: 'Select Country', placeholder: 'Select Country',
allowClear: true, allowClear: true,
minimumInputLength: 3, minimumInputLength: 3,
id: function (item) {
return item.CountryId;
},
ajax: { ajax: {
url: '/getCountries', url: '/getCountries',
dataType: 'json', dataType: 'json',
delay: 250, data: function (term, page) {
data: function (params) { return { query: term };
return {
query: params.term,
page: params.page
};
}, },
processResults: function (data, params) { results: function (data, page) {
return { return { results: data };
results: data.map(function(item) { }
return {
id: item.CountryId,
text: item.CountryName
};
})
};
}, },
cache: true formatResult: function (item) {
return item.CountryName;
}, },
templateResult: function (item) { formatSelection: function (item) {
return item.text || item.CountryName; return item.CountryName;
}, },
templateSelection: function (item) { initSelection: function (element, callback) {
return item.text || item.CountryName; return $.get('/getCountryById', { query: element.val() }, function (data) {
callback(data);
});
} }
} }
}); });
@@ -105,19 +104,12 @@ $(function(){
if (typeof source === 'string') { if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {}; options.select2.ajax = options.select2.ajax || {};
//default ajax params for Select2 v4.x //some default ajax params
if(!options.select2.ajax.data) { if(!options.select2.ajax.data) {
options.select2.ajax.data = function(params) { options.select2.ajax.data = function(term) {return { query:term };};
return {
query: params.term,
page: params.page
};
};
} }
if(!options.select2.ajax.processResults) { if(!options.select2.ajax.results) {
options.select2.ajax.processResults = function(data) { options.select2.ajax.results = function(data) { return {results:data };};
return {results: data };
};
} }
options.select2.ajax.url = source; options.select2.ajax.url = source;
} else { } else {
@@ -134,8 +126,16 @@ $(function(){
this.isMultiple = this.options.select2.tags || this.options.select2.multiple; this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
this.isRemote = ('ajax' in this.options.select2); 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 //store function that renders text in select2
this.formatSelection = this.options.select2.templateSelection; this.formatSelection = this.options.select2.formatSelection;
if (typeof(this.formatSelection) !== "function") { if (typeof(this.formatSelection) !== "function") {
this.formatSelection = function (e) { return e.text; }; this.formatSelection = function (e) { return e.text; };
} }
@@ -147,6 +147,18 @@ $(function(){
render: function() { render: function() {
this.setClass(); 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 //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@@ -156,33 +168,15 @@ $(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) { value2html: function(value, element) {
var text = '', data, var text = '', data,
that = this; that = this;
// Use stored selected data if available (for visual display after selection) if(this.options.select2.tags) { //in tags mode just assign value
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 = value;
//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
} else if(this.sourceData) { } else if(this.sourceData) {
data = $.fn.editableutils.itemsByValue(value, this.sourceData, function(e) { return e.id; }); data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
} else { } else {
//can not get list of possible values //can not get list of possible values
//(e.g. autotext for select2 with ajax source) //(e.g. autotext for select2 with ajax source)
@@ -201,6 +195,7 @@ $(function(){
text = Array.isArray(text) ? text.join(this.options.viewseparator) : text; text = Array.isArray(text) ? text.join(this.options.viewseparator) : text;
//$(element).text(text);
Constructor.superclass.value2html.call(this, text, element); Constructor.superclass.value2html.call(this, text, element);
}, },
@@ -209,96 +204,45 @@ $(function(){
}, },
value2input: function(value) { value2input: function(value) {
// if value array => join it anyway // if value array => join it anyway
if(Array.isArray(value)) { if(Array.isArray(value)) {
value = value.join(this.getSeparator()); value = value.join(this.getSeparator());
} }
// For remote sources with existing value, create option element before Select2 init //for remote source just set value, text is updated by initSelection
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')) { if(!this.$input.data('select2')) {
this.$input.val(value); this.$input.val(value);
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
// 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 { } else {
// Make sure existing option is selected //second argument needed to separate initial change from user's click (for autosubmit)
$existingOption.prop('selected', true); this.$input.val(value).trigger('change', true);
//Uncaught Error: cannot call val() if initSelection() is not defined
//this.$input.select2('val', value);
} }
// Force Select2 to update its display // if defined remote source AND no multiple mode AND no user's initSelection provided -->
this.$input.trigger('change.select2'); // 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;
// Mark that a selection was just made (for blur handling) if(!customId && !customText) {
this._justSelected = true; var $el = $(this.options.scope);
if (!$el.data('editable').isEmpty) {
var data = {id: value, text: $el.text()};
this.$input.select2('data', data);
}
} }
}, 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() { input2value: function() {
var val = this.$input.val(); return this.$input.select2('val');
// --- Handle Bootstrap Datepicker ---
var dp = this.$input.data('datepicker');
if (dp && typeof dp.getFormattedDate === 'function') {
val = dp.getFormattedDate(this.options.format || 'yyyy-mm-dd');
}
// --- Handle Select2 v4.x ---
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) { str2value: function(str, separator) {
@@ -321,10 +265,16 @@ $(function(){
return val; return val;
}, },
autosubmit: function() {
this.$input.on('change', function(e, isInitial){
if(!isInitial) {
$(this).closest('form').submit();
}
});
},
getSeparator: function() { getSeparator: function() {
// Select2 v4.x uses different separator handling return this.options.select2.separator || $.fn.select2.defaults.separator;
return this.options.select2.separator || ',';
}, },
/* /*
@@ -360,7 +310,7 @@ $(function(){
**/ **/
tpl:'<input type="hidden">', tpl:'<input type="hidden">',
/** /**
Configuration of select2. [Full list of options](https://select2.org/configuration). Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
@property select2 @property select2
@type object @type object
+4 -3
View File
@@ -26,6 +26,7 @@
href="#" href="#"
id="yes-no-switch" id="yes-no-switch"
class="editable editable-click" class="editable editable-click"
data-pk="101"
> >
Yes Yes
</a> </a>
@@ -39,7 +40,7 @@
id="yes-no-switch-json" id="yes-no-switch-json"
class="editable editable-click" class="editable editable-click"
data-type="select" data-type="select"
data-pk="1" data-pk="102"
data-title="Select Yes/No" data-title="Select Yes/No"
> >
Yes Yes
@@ -55,10 +56,10 @@
id="datepicker" id="datepicker"
class="editable editable-click" class="editable editable-click"
data-type="date" data-type="date"
data-pk="1" data-pk="103"
data-title="Select Date" data-title="Select Date"
> >
Yes Click to select date
</a> </a>
</div> </div>
+12 -25
View File
@@ -1,18 +1,15 @@
// debugger
import $ from 'jquery'; import $ from 'jquery';
window.$ = window.jQuery = $; window.$ = window.jQuery = $;
//
console.log("jQuery version:", $.fn?.jquery);
// // import $ from './dist/jquery';
import "bootstrap" import "bootstrap"
import "bootstrap/dist/css/bootstrap.min.css" import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap-icons/font/bootstrap-icons.min.css" import "bootstrap-icons/font/bootstrap-icons.min.css"
// bootstrap-datepicker loaded separately (not bundled in grunt build)
import "bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"; import "bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js";
import "bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css"; import "bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css";
const Editable = require("bootstrap5-editable/js/bootstrap-editable"); // Import the editable functionality (attaches to jQuery.fn) - using Grunt-built version
console.log("Editable:", Editable); require("./dist/bootstrap5-editable/js/bootstrap-editable");
$.fn.editable.defaults.mode = 'inline'; $.fn.editable.defaults.mode = 'inline';
$(function() { $(function() {
@@ -23,18 +20,12 @@ $(function() {
source: 'test.php', // URL to fetch select options source: 'test.php', // URL to fetch select options
value: 1, value: 1,
success: function(response, newValue) { success: function(response, newValue) {
console.log("Saved successfully:", response); // Handle success
}, },
error: function(response) { error: function(response) {
console.error("Save error:", response); // Handle error
} }
}); });
console.log("Internal editable data:", $('#yes-no-switch').data('editable'));
const ed = $('#yes-no-switch').data('editable');
console.log("Internal editable data:", ed);
if (ed) {
console.log("TYPE:", ed.type, "OPTIONS:", ed.options);
}
@@ -48,16 +39,19 @@ $(function() {
], ],
value: 1, value: 1,
success: function(response, newValue) { success: function(response, newValue) {
console.log("Static source saved successfully:", response); // Handle success
}, },
error: function(response) { error: function(response) {
console.error("Static source save error:", response); // Handle error
} }
}); });
const initialDateValue = new Date().toISOString().split('T')[0];
$('#datepicker').editable({ $('#datepicker').editable({
type: 'date', type: 'date',
url: 'test.php', // URL to send the POST request url: 'test.php', // URL to send the POST request
value: initialDateValue, // Set to current date (YYYY-MM-DD)
format: 'yyyy-mm-dd', // Date format format: 'yyyy-mm-dd', // Date format
viewformat: 'dd/mm/yyyy', // How the user sees it viewformat: 'dd/mm/yyyy', // How the user sees it
datepicker: { datepicker: {
@@ -66,18 +60,11 @@ $(function() {
todayHighlight: true todayHighlight: true
}, },
success: (response, newValue)=> { success: (response, newValue)=> {
console.log("Date saved successfully:", response); // Handle success
}, },
error: (response) => { error: (response) => {
console.error("Date save error:", response); // Handle error
} }
}).on('shown', (e, editable)=> {
console.log("Datepicker shown:", editable);
$(editable.input.$input).datepicker({
weekStart: 1,
autoclose: true,
todayHighlight: true
}).datepicker('show')
}); });
}) })
+2 -1
View File
@@ -46,6 +46,7 @@ module.exports = [
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [
{ from: "src/editable-form/editable-form.css", to: path.resolve(__dirname, "dist/bootstrap5-editable/css") }, { from: "src/editable-form/editable-form.css", to: path.resolve(__dirname, "dist/bootstrap5-editable/css") },
{ from: "node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css", to: path.resolve(__dirname, "dist/bootstrap5-editable/css") },
{ from: "node_modules/bootstrap-icons/font/fonts", to: path.resolve(__dirname, "dist/fonts") } { from: "node_modules/bootstrap-icons/font/fonts", to: path.resolve(__dirname, "dist/fonts") }
] ]
}) })
@@ -54,7 +55,7 @@ module.exports = [
// X-Editable Plugin (Bootstrap 5) // X-Editable Plugin (Bootstrap 5)
{ {
entry: "./src/editable-form/editable-form-bootstrap5.js", entry: "./src/bootstrap5-editable.js",
output: { output: {
path: path.resolve(__dirname, "dist/bootstrap5-editable/js"), // X-Editable stays here path: path.resolve(__dirname, "dist/bootstrap5-editable/js"), // X-Editable stays here
filename: "bootstrap-editable.js", filename: "bootstrap-editable.js",