SuiteCRM 8.7: Custom SugarWidgetSubPanelEditButton Ignored in Subpanel

Hello everyone,

I’m customizing the Products subpanel in the Clienti module so that, when the Edit button is clicked, it redirects to the cstm_ClienteProdotto module (which acts as a bridge between the two) instead of going to the AOS_Products module. This approach is explained in the following video:
https://www.youtube.com/watch?v=KYVhQx21ugk&t=374s

Host module cstm_Clienti
suitecrm\public\legacy\custom\Extension\modules\cstm_Clienti\Ext\Layoutdefs\cstm_clienti_aos_products_1_cstm_Clienti.php :

$layout_defs["cstm_Clienti"]["subpanel_setup"]['cstm_clienti_aos_products_1'] = array (
  'order' => 100,
  'module' => 'AOS_Products',
  'subpanel_name' => 'customSubPanel',
  'sort_order' => 'asc',
  'sort_by' => 'id',
  'title_key' => 'LBL_CSTM_CLIENTI_AOS_PRODUCTS_1_FROM_AOS_PRODUCTS_TITLE',
  'get_subpanel_data' => 'cstm_clienti_aos_products_1',
  'top_buttons' => 
  array (
    /*
    0 => 
    array (
      'widget_class' => 'SubPanelTopButtonQuickCreate',
    ),
    */
    0 => 
    array (
      'widget_class' => 'SubPanelTopSelectButton',
      'mode' => 'MultiSelect',
    ),
  ),
);

Sub Module AOS_Products

suitecrm\public\legacy\custom\modules\AOS_Products\metadata\subpanels\customSubPanel.php :

if (!defined('sugarEntry') || !sugarEntry) {
    die('Not A Valid Entry Point');
}


$module_name='AOS_Products';
$subpanel_layout = array(
    'top_buttons' => array(
        array('widget_class' => 'SubPanelTopCreateButton'),
        array('widget_class' => 'SubPanelTopSelectButton', 'popup_module' => $module_name),
    ),

    'where' => '',

    'list_fields' => array(
        'name'=>array(
            'vname' => 'LBL_NAME',
            'widget_class' => 'SubPanelDetailViewLink',
            'width' => '45%',
        ),
        'date_modified'=>array(
            'vname' => 'LBL_DATE_MODIFIED',
            'width' => '45%',
        ),
        'edit_button'=>array(
            'widget_class' => 'SubPanelEditClienteProdottoButton',
            'module' => 'cstm_ClienteProdotto',
            'width' => '4%',
        ),
        /*
        'remove_button'=>array(
            'widget_class' => 'SubPanelRemoveButton',
            'module' => $module_name,
            'width' => '5%',
        ),
        */
    ),
);

I can confirm that customSubPanel is being used, because the remove_button does not appear. However, when I click the Edit button, it still loads the normal AOS_Products edit view instead of calling my custom SubPanelEditClienteProdottoButton class.

My custom class is located here:

suitecrm\public\legacy\custom\include\generic\SugarWidgets\SugarWidgetSubPanelEditClienteProdottoButton.php

class SugarWidgetSubPanelEditClienteProdottoButton extends SugarWidgetField
{
    protected static $defs = array();
    protected static $edit_icon_html;


    public function displayHeaderCell($layout_def)
    {
        return '';
    }

    public function displayList(&$layout_def)
    {
        global $app_strings;
        global $subpanel_item_count;

        // Genera un ID unico per il bottone
        $unique_id = $layout_def['subpanel_id'] . "_edit_" . $subpanel_item_count;

        // Recupera gli ID: il cliente dall'url e il prodotto dal layout_def
        $cliente_id  = $_REQUEST['record'];
        $prodotto_id = $layout_def['fields']['id'];

        // Sanifica gli input per la query
        $cliente_id  = $GLOBALS['db']->quote($cliente_id);
        $prodotto_id = $GLOBALS['db']->quote($prodotto_id);

        // Costruisci la query corretta
        $sql = "SELECT A.id 
                FROM cstm_clienteprodotto AS A 
                JOIN cstm_clienteprodotto_cstm AS B ON A.id = B.id_c 
                WHERE B.cliente_id_c = {$cliente_id} 
                  AND B.prodotto_id_c = {$prodotto_id} 
                  AND A.deleted = 0";

        $result = $GLOBALS['db']->query($sql);
        $row = $GLOBALS['db']->fetchByAssoc($result);
       

        $href = 'index.php?module=cstm_ClienteProdotto'
              . '&action=EditView'
              . '&record=' . $row['id']
              . '&return_module=' . $_REQUEST['module']
              . '&return_action=DetailView'
              . '&return_id=' . $_REQUEST['record'];
        
        $handler = '';

        if ($layout_def['ListView']) {
            return '<a href="' . $href . '" onmouseover="' . $handler . '" onfocus="' . $handler .
                '" class="listViewTdToolsS1" id="' . $unique_id . '">' . $app_strings['LNK_EDIT'] . '</a>';
        }

        return '';
    }
}

What I Have Already Tried:

  • Performed a Quick Repair & Rebuild multiple times.
  • Deleted the cache in suitecrm/cache/.
  • Checked the logs (suitecrm.log, suitecrm/logs/prod/prod.log and PHP error logs), but there are no references to this issue.

Has anyone encountered a similar issue, or can anyone point me in the right direction on why my custom edit button logic isn’t being applied?

Thank you in advance!

What exactly is the web request being made when you press the button?

Complete URL + POST data (if there is any)

Check your browser’s dev tools, network tab, to get it

When I press the Custom Edit button, the following request appears in the browser’s
Network tab:

http://localhost/legacy/index.php?action_module=products&return_action=DetailView&return_module=cstm_Clienti&return_id=2509b475-de57-7350-7bcf-60ee00e0c528&module=AOS_Products&action=EditView&record=188d4651-ad54-0d0d-c7bd-67921ba57685

same request as an object

{
	"GET": {
		"scheme": "http",
		"host": "localhost",
		"filename": "/legacy/index.php",
		"query": {
			"action_module": "products",
			"return_action": "DetailView",
			"return_module": "cstm_Clienti",
			"return_id": "2509b475-de57-7350-7bcf-60ee00e0c528",
			"module": "AOS_Products",
			"action": "EditView",
			"record": "188d4651-ad54-0d0d-c7bd-67921ba57685"
		},
		"remote": {
			"Address": "127.0.0.1:80"
		}
	}
}

I think the problem is here

class SugarWidgetSubPanelEditClienteProdottoButton extends SugarWidgetField

I am not sure of the mechanism here, but typically you would extend the class you are overriding, not the base class.

So maybe something like

class SugarWidgetSubPanelEditClienteProdottoButton extends SugarWidgetSubPanelEditXYZButton

replacing XYZ with whatever is found in the core class you are trying to extend.

I changed

class SugarWidgetSubPanelEditClienteProdottoButton extends SugarWidgetField

with

class SugarWidgetSubPanelEditClienteProdottoButton extends SugarWidgetSubPanelEditButton

(In suitecrm 7.1 it also worked by extending SugarWidgetField.)

but the result remains the same.
I also noticed the following error in the browser console:

ERROR TypeError: can’t convert undefined to object
initSubpanels http://localhost/dist/dist_core_fesm2022_core_mjs-_39da1.236f1c41e5a37e9f.js:59
RxJS 8
node_modules_angular_core_fesm2022_core_mjs.3e1a513b7cb4915e.js:1:69196
Angular 2
RxJS 6
Angular 2
invoke http://localhost/dist/polyfills.06f5de644d6730a0.js:1
run http://localhost/dist/polyfills.06f5de644d6730a0.js:1
Angular 2
handleError http://localhost/dist/polyfills.06f5de644d6730a0.js:1
runTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
invokeTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
invoke http://localhost/dist/polyfills.06f5de644d6730a0.js:1
0 http://localhost/dist/polyfills.06f5de644d6730a0.js:1
(Async: setTimeout handler)
E http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
onScheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleMacroTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
Se http://localhost/dist/polyfills.06f5de644d6730a0.js:1
l http://localhost/dist/polyfills.06f5de644d6730a0.js:1
t http://localhost/dist/polyfills.06f5de644d6730a0.js:1
RxJS 16
updateState http://localhost/dist/dist_core_fesm2022_core_mjs-_39da1.236f1c41e5a37e9f.js:32
bt http://localhost/dist/dist_core_fesm2022_core_mjs-_39da1.236f1c41e5a37e9f.js:32
RxJS 14
At Angular
invoke http://localhost/dist/polyfills.06f5de644d6730a0.js:1
onInvoke Angular
invoke http://localhost/dist/polyfills.06f5de644d6730a0.js:1
run http://localhost/dist/polyfills.06f5de644d6730a0.js:1
Ce http://localhost/dist/polyfills.06f5de644d6730a0.js:1
invokeTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
onInvokeTask Angular
invokeTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
runTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
j http://localhost/dist/polyfills.06f5de644d6730a0.js:1
invokeTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
N http://localhost/dist/polyfills.06f5de644d6730a0.js:1
B http://localhost/dist/polyfills.06f5de644d6730a0.js:1
H http://localhost/dist/polyfills.06f5de644d6730a0.js:1
(Async: EventListener.handleEvent)
n http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
onScheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
scheduleEventTask http://localhost/dist/polyfills.06f5de644d6730a0.js:1
p http://localhost/dist/polyfills.06f5de644d6730a0.js:1
se Angular
RxJS 22

Just to clarify, I have already implemented this customization in SuiteCRM 7.1, and it worked perfectly.

For this reason, I just want to make sure I’m not missing anything specific in SuiteCRM 8.7.

Ok, sorry, maybe revert that change I recommended earlier

And have a look here, try to step through this logic to see what is happening

→ https://github.com/salesagility/SuiteCRM/blob/hotfix/include/generic/LayoutManager.php#L269-L302

Looking at the HTML code in the browser, I see that the edit buttons are being rendered by Angular and are not simple <a href=""> element like suitecrm 7.1. However, it’s not clear to me where and how to extend the button definition for the specific subpanel cstmClienti->AOS_Products in angular. Furthermore, I’m not sure how I should define the button in customSubPanel.php so that it is rendered as a custom Angular button.

¨These are separate issues, I guess; you can try producing a different URL in the front-ent, but you could also try (as in our previous discussions) to ensure that the URL that is already produced will be implemented with your custom class in the back-end.

I don’t see any reason why not; although evidently it doesn’t seem to be working.

If you decide on trying the front-end changes, have you looked at the developer insights videos regarding adding buttons in the front-end?

In SuiteCRM 8.7, the edit buttons are now done using Angular, which is a change from version 7.1 that just used simple HTML links. If you want to add a custom button to the cstmClienti->AOS_Products subpanel, you’ll need to create a new Angular component and register it with the module. In the customSubPanel.php file, you’ll set up the component selector that will match up with your new Angular button.

Hello, thank you for your response. Could you outline the basic steps to follow or direct me to documentation for creating the component and registering it? The structure of SuiteCRM with Angular is still like a black box to me.

@Lawliet I am afraid that advice given by @ethelandino is not correct. That might be generic Angular advice, but it is not how you extend the UI in SuiteCRM v8.

Your best education on those extensions is the set of SuiteCRM Developer Insights videos, and the developer section for v8 on Suite Docs.

@pgr, I do agree with you. Developer Insights, SuiteCRM documentation are great way to find out the interface

SuiteCRM 8.7 in SuiteCRM uses Angular for the most parts of its UI (including Edit buttons of subpanels) however, it should be noted. I think the most common reason for this is cache or subpanel configuration error.