v25.0.7: Fix distribution file dependencies and production click handler issues

## Major Fix - Production Distribution Dependencies
- Fix missing click handlers in production by ensuring proper dependency management
- Add select2 to Gruntfile.js common inputs to include it in built distribution
- Resolve 'this.$input.select2 is not a function' error in production builds
- Update demo to include select2 and bootstrap-datepicker as separate dependencies when using built files

## Dependencies & Usage
- Built distribution files now include select2 input type in bootstrap-editable.min.js
- Users must include select2 and bootstrap-datepicker separately when using built files:
  - import 'select2' and 'select2/dist/css/select2.min.css'
  - import 'bootstrap-datepicker' and 'bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css'
- This matches standard JavaScript library dependency patterns

## Technical Details
- Root cause: JavaScript cascade failure when select2 dependency was missing
- Missing select2 prevented entire x-editable initialization, breaking click handlers
- Now both webpack source build and Grunt distribution include same input types
- Maintains backward compatibility while fixing production deployment issues

## Fixes Production Issues
-  Click handlers now properly attach in production
-  Form submission logic works correctly
-  All input types functional (text, select, select2, date, etc.)
-  Proper event binding and library initialization
-  No more 'Unknown type: select2' errors
This commit is contained in:
Micha
2025-07-29 16:11:16 +02:00
parent 18b0e40de9
commit 09358eaaf6
8 changed files with 375 additions and 8 deletions

View File

@@ -34,7 +34,8 @@ function getFiles() {
inputs+'list.js', inputs+'list.js',
inputs+'text.js', inputs+'text.js',
inputs+'textarea.js', inputs+'textarea.js',
inputs+'select.js', inputs+'select.js',
inputs+'select2/select2.js',
inputs+'checklist.js', inputs+'checklist.js',
inputs+'html5types.js' inputs+'html5types.js'
]; ];

View File

@@ -12,8 +12,13 @@ import "bootstrap-datepicker";
import "bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css"; import "bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css";
// Import the editable functionality (attaches to jQuery.fn) - using webpack source build // Import the editable functionality (attaches to jQuery.fn) - using built distribution
require("../src/bootstrap5-editable.js"); // When using built files, you need to include select2 separately for select2 inputs
import "select2";
import "select2/dist/css/select2.min.css";
import '../dist/bootstrap5-editable/js/bootstrap-editable.min.js'
import '../dist/bootstrap5-editable/css/bootstrap-editable.css'
$.fn.editable.defaults.mode = 'inline'; $.fn.editable.defaults.mode = 'inline';
$(function() { $(function() {

BIN
dist/240c0c6464de0b4bf116.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

BIN
dist/56d4c7ce2d3591a02107.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,4 +1,4 @@
/*! X-editable Bootstrap 5 - v25.0.6 /*! X-editable Bootstrap 5 - v25.0.7
* A maintained 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 */

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
"name": "x-editable-bootstrap5", "name": "x-editable-bootstrap5",
"title": "X-editable Bootstrap 5", "title": "X-editable Bootstrap 5",
"description": "A maintained fork of x-editable for Bootstrap 5 support.", "description": "A maintained fork of x-editable for Bootstrap 5 support.",
"version": "25.0.6", "version": "25.0.7",
"homepage": "https://git.24unix.net/tracer/x-editable", "homepage": "https://git.24unix.net/tracer/x-editable",
"author": { "author": {
"name": "Micha Espey", "name": "Micha Espey",