Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


## Release 1.10
- FIX : Warning Constant INC_FROM_DOLIBARR already defined - *18/08/2025* - 1.10.5
- FIX : DA026565 Missing chiffrage element in ajax select - *2025-05-26* - 1.10.4
- FIX : DA026567 Empty ajax select - *2025-05-22* - 1.10.3
- FIX DA026311 : removed references to the deprecated 'asset' and 'ordre_fabrication' elements across the codebase. This includes adjustments to class mappings, known elements, and checks for deprecated modules - *18/04/2025* - 1.10.2
Expand Down
101 changes: 7 additions & 94 deletions class/actions_related.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -420,108 +420,21 @@ function blockRelated($parameters, &$object, &$action, $hookmanager, $moreStyle=

</form>
</div>
<?php
$jsContext = [
'relatedBaseURL' => dol_buildpath('/related',1),
];
?>
<script type="text/javascript" src="<?= dol_buildpath('/related/js/related.js', 1) ?>"></script>
<script type="text/javascript">

$(document).ready(function() {

$('.blockrelated_content').each(function() {
$(this).closest('div.tabsAction').after($(this));
});

$('#add_related_object').autocomplete({
source: function( request, response ) {
$.ajax({
url: "<?php echo dol_buildpath('/related/script/interface.php',1) ?>",
dataType: "json",
data: {
key: request.term
,get:'search'
}
,success: function( data ) {
var c = [];
$.each(data, function (i, cat) {

var first = true;
$.each(cat, function(j, label) {

if(first) {
c.push({value:i, label:i, object:'title'});
first = false;
}

c.push({ value: j, label:' '+label, object:i});

});


});

response(c);



}
});
},
minLength: 1,
select: function( event, ui ) {

if(ui.item.object == 'title') return false;
else {
$('#id_related_object').val(ui.item.value);
$('#add_related_object').val(ui.item.label.trim());
$('#type_related_object').val(ui.item.object);

$('#bt_add_related_object').css('display','inline');

return false;
}

},
open: function( event, ui ) {
$( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" );
},
close: function() {
$( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
}
});

$( "#add_related_object" ).autocomplete().data("uiAutocomplete")._renderItem = function( ul, item ) {

$li = $( "<li />" )
.attr( "data-value", item.value )
.append( item.label )
.appendTo( ul );

if(item.object=="title") $li.css("font-weight","bold");

return $li;
};


var blockrelated = $('div.tabsAction .blockrelated_content');
if (blockrelated.length == 1)
{
if ($('.blockrelated_content').length > 1)
{
blockrelated.remove();
}
else
{
blockrelated.appendTo($('div.tabsAction'));
}
}

});

ATM_MODULE_RELATED.main(<?= json_encode($jsContext) ?>);
</script>

<?php


if (! $error)
{

return 0; // or return 1 to replace standard code
}
else
Expand Down
6 changes: 4 additions & 2 deletions core/modules/modRelated.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function __construct($db)
// Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module)
$this->description = "Links elements together";
// Possible values for version are: 'development', 'experimental', 'dolibarr' or version
$this->version = '1.10.4';
$this->version = '1.10.5';
// Url to the file with your last numberversion of this module
require_once __DIR__ . '/../../class/techatm.class.php';
$this->url_last_version = \related\TechATM::getLastModuleVersionUrl($this);
Expand Down Expand Up @@ -201,7 +201,9 @@ function init($options='')
{
$sql = array();

define('INC_FROM_DOLIBARR',true);
if (!defined('INC_FROM_DOLIBARR')) {
define('INC_FROM_DOLIBARR', true);
}

dol_include_once('/related/config.php');
dol_include_once('/related/script/create-maj-base.php');
Expand Down
160 changes: 160 additions & 0 deletions js/related.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/* jshint -W098: suppress the `ATM_MODULE_RELATED is declared but never used` warning */
/* jshint -W117: suppress the `'$' is not defined` warning */
/* jshint -W116: allow if (……) return; (without brackets) */
(function () {
"use strict";
window.ATM_MODULE_RELATED = {
/**
*
* @param {{relatedBaseURL: string }} dolibarrContext Contexte passé depuis PHP.
*/
main(dolibarrContext) {
window.addEventListener('DOMContentLoaded', () => {
this.moveRelatedBlocksAfterTabsAction();
this.cleanupDuplicateBlocks();

const ajaxURL = `${dolibarrContext.relatedBaseURL}/script/interface.php`;
this.initializeAutocomplete(ajaxURL);
});
},

/**
* Moves all .blockrelated_content elements to appear after their parent .tabsAction
*/
moveRelatedBlocksAfterTabsAction() {
document.querySelectorAll('.blockrelated_content').forEach(element => {
const tabsAction = element.closest('div.tabsAction');
if (tabsAction) {
tabsAction.insertAdjacentElement('afterend', element);
}
});
},

/**
* Initializes the jQuery UI autocomplete for adding related objects
*/
initializeAutocomplete(ajaxURL) {
const $input = $('#add_related_object');
if (!$input.length) return;

$input.autocomplete({
source: (request, response) => this.fetchAutocompleteData(request, response, ajaxURL),
minLength: 1,
select: (event, ui) => this.handleAutocompleteSelect(ui),
open: function() {
this.classList.remove('ui-corner-all');
this.classList.add('ui-corner-top');
},
close: function() {
this.classList.remove('ui-corner-top');
this.classList.add('ui-corner-all');
}
});

// Custom renderer for categorized items
$input.autocomplete().data("uiAutocomplete")._renderItem = this.renderAutocompleteItem;
},

/**
* Fetches autocomplete suggestions from the server
*/
fetchAutocompleteData(request, response, ajaxURL) {
$.ajax({
url: ajaxURL,
dataType: "json",
data: {
key: request.term,
get: 'search'
},
success: (data) => {
const items = this.transformAutocompleteData(data);
response(items);
}
});
},

/**
* Transforms server response into autocomplete items with category headers
*/
transformAutocompleteData(data) {
const items = [];

Object.entries(data).forEach(([category, categoryData]) => {
// Add category header
items.push({
value: category,
label: category,
object: 'title'
});

// Add category items
Object.entries(categoryData).forEach(([id, label]) => {
items.push({
value: id,
label: ' ' + label,
object: category
});
});
});

return items;
},

/**
* Handles selection of an autocomplete item
*/
handleAutocompleteSelect(ui) {
// Prevent selection of category headers
if (ui.item.object === 'title') {
return false;
}

// Populate hidden fields
document.getElementById('id_related_object').value = ui.item.value;
document.getElementById('add_related_object').value = ui.item.label.trim();
document.getElementById('type_related_object').value = ui.item.object;

// Show the add button
const addButton = document.getElementById('bt_add_related_object');
if (addButton) {
addButton.style.display = 'inline';
}

return false;
},

/**
* Custom renderer for autocomplete items (bold category headers)
*/
renderAutocompleteItem(ul, item) {
const li = document.createElement('li');
li.dataset.value = item.value;
li.textContent = item.label;

if (item.object === "title") {
li.style.fontWeight = "bold";
}

ul[0].appendChild(li);
return $(li);
},

/**
* Removes duplicate .blockrelated_content blocks within .tabsAction
*/
cleanupDuplicateBlocks() {
const tabsAction = document.querySelector('div.tabsAction');
if (!tabsAction) return;

const blockrelated = tabsAction.querySelector('.blockrelated_content');
if (!blockrelated) return;

const allBlockrelated = document.querySelectorAll('.blockrelated_content');
if (allBlockrelated.length > 1) {
blockrelated.remove();
} else {
tabsAction.appendChild(blockrelated);
}
}
};
}());
Loading