Using Contacts ListView in custom List View

I am on SuiteCRM 7.12.5 on Debian 10 linux

I have been trying unsuccessfully to follow the flow of generating a ListView that uses the Contacts ListView and have been unable to get what I want.

I want a view that has the same setup as the Contacts ListView, with the title at the top, the “header” bar (may NOT be using the right terminology here) that shows the columns with their Names and then lists the data that I have generated for teh Contacts.

I do NOT want to override the default Contacts ListView; I want to add a new one.

I have searched the Forum but cannot find the answer I need.

I have created a new ListView that extends the Contacts ViewList
I have used the Smarty template to show content on that page
I have added a menu which points to an action that brings up the custom ListView I created

And the content I want to see is there, but I do NOT have the full Contacts ListView layout. I am missing the “header” bar that shows the columns with their names.

How do I access/populate the custom view in a way that includes the “header” bar with the column names shown and formats the output to fit into the column setup?

Hi @Ramblin, could you please share your custom view file?
Have you created it in this path:
custom/modules/<Module_Name>/views/view.list.php
?

@LionSolution

Yes, I have created a custom listview file but I did not name it
custom/modules/Contacts/views/view.list.php
since that would then override the standard Contacts ListView and I do not want to do that.

I want to keep the current Contacts list as is and add another custom view.

So, here’s what I did.

The view file is just sample code to show something; the actual beans logic and variables displayed will change.

Create new listview file at
custom/modules/Contacts/views/view.contact_product.php
with the content:

<?php

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

require_once('include/MVC/View/views/view.list.php');

class CustomContactsViewContact_Product extends ViewList {

        public function display() {

                require_once("data/BeanFactory.php");

                $contactBean = BeanFactory::getBean( 'Contacts' );

                // The get_full_list method returns an array of all objects/beans of that module
                // Each top-level array element/row is one record contained as an object
                // To access each record, use normal array access syntax: $contactArray[number]
                // To access the variables of each record, use normal object/bean syntax: $contactArray[number]->variable_name
                $contactArray = $contactBean->get_full_list(
                        $order_by = "last_name",
                        // The where conditions require you to include the table name
                        //$where = "contacts.last_name = 'Walden'",
                        $check_dates = true,
                        $show_deleted = 0,
                );

                if ( isset($contactArray)  ) {
                        $number_rows = count( $contactArray , 0 );
                } else {
                        $number_rows = 0;
                        $contactArray = array();
                }

                $Contact_Product_Smarty = new Sugar_Smarty();

                $Contact_Product_Smarty->assign( 'level_one_header' , "This will be the first level heading" );
                $Contact_Product_Smarty->assign( 'level_two_header' , "And this is the second level heading" );
                $Contact_Product_Smarty->assign( 'number_rows' , $number_rows );
                $Contact_Product_Smarty->assign( 'contact_array' , $contactArray );

                $Contact_Product_Smarty->display('custom/modules/Contacts/templates/contact_product.tpl');

        }
}

created the Smarty tpl file at
custom/modules/Contacts/templates/contact_product.tpl
with the content (again, simplified just to show something)

<h1>L1 Label: {$level_one_header}</h1>
<h2>L2 Label: {$level_two_header}</h2>
<p>The number of rows retrieved was {$number_rows}</p>
<ul>
	{foreach name=contactArrayTpl from=$contact_array key=recordNumber item=contactRecord}
		<li>Record {$recordNumber} for {$contactRecord->first_name} {$contactRecord->last_name} was last modified on {$contactRecord->date_modified}</li>
	{/foreach}
</ul>

Registered the entryPoint in
custom/Extension/application/Ext/EntryPointRegistry/contactProductEntry.php
with the content:

<?php

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

$entry_point_registry['cpListView'] = array(
  'file' => 'custom/modules/Contacts/views/view.contact_product.php',
  'auth' => true,
);

Added a new controller entry at
custom/modules/Contacts/controller.php
with the content:

<?php

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

require_once('modules/Contacts/controller.php');

class CustomContactsController extends ContactsController
{
    public function action_cpListView()
    {
        $this->view = 'contact_product';
    }
}

I successfully see the results of the (mocked up) content when I use
https://{siteName}/index.php?module=Contacts&action=cpListView

But the view does NOT include the header bar (not sure of correct term to use here) with the column labels like you see in a normal listview, followed by the variables formatted to fit the columns.

I think you rewrite the engine too deeply.

In a view.list.php you can already obtain a lot of things.

I show you an example regarding a custom/modules/Accounts/views/view.list.php:

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

require_once('modules/Accounts/views/view.list.php');

class CustomAccountsViewList extends AccountsViewList {

    function __construct() {
        parent::__construct();
    }

    function display() {
        global $mod_strings;

		...
		
        parent::display();

        if ($type == 'Supplier') {
            $Search_title = <<<EOQ
            <script language = "javascript">
			
			...
			
            </script>
EOQ;
            echo $Search_title;
        } else {
            $Search_title = <<<EOQ
            <script language = "javascript">
                        
						...
						
            </script>
EOQ;
            echo $Search_title;
        } //else
    } //function display

} //class CustomAccountsViewList

@Ramblin which IDE are you using? Do you have XDEBUG configured?

@LionSolution

I edited for the COntacts module and tried what you showed up until the javascript part, which I did not know what you were trying to tell me there.

What I ended up with was a list of the Contacts - using the current Contacts variables setup - then my “placeholder” content showing after that in its free-form format.

I am assuming the javascript was supposed to replace the normal header “title” and the normal conact list (and ???) … but that is the specific question I have.

How do I set the “title” (or whatever the correct terminology is for the text appearing above the “header” bar) and how do I replace the contents beings shown:

  • as the column headers in the header bar
  • as the variable values show in each row below the header bar

Any help would be appreciated

@pgr
Sure, make me admit how much of an amateur I am … :slight_smile:

I actually stopped using an IDE

I used to use NetBeans and I did have XDEBUG configured, but when I tried to step through the SugarCRM/SuiteCRM code, it took so many steps that I was getting bored and inevitably stepped right through the spot where I was supposed to be paying attention.

So now, I just use Notepad++ on my PC - it actually does have some decent syntax assists for php - and then use Putty to put the code onto the server.

Not likely what you wanted to hear but …

Eheh it’s actually when I start believing in people’s capabilities as developers that I bring up my IDE and XDEBUG pitch :smiley:

It takes some learning to do effective debugging, understanding how to navigate the stack traces, correctly use all the step in / over / out options, but IMHO it’s absolutely necessary to grow as a developer.

And doing this with a huge and sometimes hugely complex code-base like SuiteCRM, adds some new problems, but it also makes it more necessary!

When working on your own code, like in this case, you don’t get lost so easily, and you get so many benefits from being able to inspect variables etc.

I encourage you try to get back together with XDEBUG. Take it out to dinner, talk, work out your problems… it will be worth it.

I agree with @pgr : using Eclipse, PHP Storm or NetBeans will definetely help you.

@pgr @lionsolution

Alright, already … 2 pro developers ganging up on 1 poor amateur coder … :slight_smile:

I went back to NetBeans / XDebug and gave it a try.

Unfortunately, after about 200 clicks on the step through, I gave up. I was nowhere close to the point I needed to be.

I tried using a breakpoint in the code I wanted to figure out, but it does not seem to want to work and, in looking at the forums, there does seem to be an issue with XDebug honouring the breakpoints set in NetBeans.

When I am creating my own scripts, I htink I will continue to use Netbeans/XDebug; I had forgotten about all the coding assists that provides.

So, back to the old hunt-and-peck approach for me with SuiteCRM.

If you have any advice on how I can inject my variables into the code that presents the Contacts Listview, I’d appreciate it, but if not, thank you for the help you have provided.

Sorry for the delayed response here. The organization for which I am doing this work is my Rotary club and we have one of our biggest fund-raisers this week, so have been scrambling to find time for anything but that for the last couple of weeks. And yes, in case you were wondering, I am customizing SuiteCRM for my club on a pro-bono basis. I guess they get what they are paying for … :frowning:

Hi Ramblin, if you created a custom controller with this action:
action_cpListView
it’s right that you see your results here:
https://{siteName}/index.php?module=Contacts&action=cpListView

I don’t think you need the .tpl file
Anyway you could check this:
Best way to extend .tpl file for a custom ListView?

@pgr @LionSolution

Good news and bad

Good news:
I got Netbeans working with XDebug and the breakpints are functioning.
Turns out Debian 10 installs XDebug 2.7, which is deprecated and does not have the same setup as v3.x, which apparently Netbeans 13 prefers …
So I uninstalled the version of XDebug installed with apt and used pecl to instal v3.14 of XDebug and then followed their configuration instructions plus a few tweaks and it worked fine. See document attached for full explanation of the setup. (I like to document things I learned so I don’t have to go through the frustration again … :wink: )
Installing and Configuring Netbeans with XDebug.zip (27.8 KB)

The bad news
I still cannot get the darn thing to do what I want, even after stepping through the code ( few hundred clicks past the breakpoint - this is one convoluted set of code!) and trying to use the solution from

The problem is not the solution proposed. The problem is me not understanding how to edit that for my situation.

I did NOT create a new module. I linked Products and Contacts via a many-to-many relationship, including an extra “number” field to record the count of “Products” sold, and edited the subpanels to allow entering/editing the “Number” field.

I have a custom action_cpListView() in which I put

<?php
require_once('modules/Contacts/controller.php');
class CustomContactsController extends ContactsController
{
    public function action_cpListView()
    {
        $this->view = 'contact_product';
    }
}

and in my view.contact_product.php I put (with fake variable data just to see if it accepted my replacement data)

<?php

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

require_once('modules/Contacts/Contact.php');
class EditContactsTempArray extends Contact {
    function get_list_view_data(){
        // Get the default output
        $temp_array = parent::get_list_view_data();
        $temp_array['NAME'] = "TestName";
        $temp_array['TITLE'] = "TestTitle";
        return $temp_array;
    }
}

require_once('modules/Contacts/views/view.list.php');
class CustomContactsViewContact_Product extends ContactsViewList {
    /**
     * @see ViewList::preDisplay()
     */
    public function preDisplay()
    {
        require_once('modules/AOS_PDF_Templates/formLetter.php');
        formLetter::LVPopupHtml('Contacts');
        parent::preDisplay();

        $this->lv = new ContactsListViewSmarty();
    }
}

But when I go to the action, it just displays the original list of contacts and their data, without replacing it with my fake data.

So I am not understanding how this is supposed to work.

Either I am not putting my function to replace the data in the right place or I am just overriding that by calling the parent display function or …

Any suggestions?

Hi @Ramblin , could you please try using the display function instead the preDisplay one?

@LionSolution

Sorry about that. I should have included the fact that I first did try with the display function instead of the preDisplay function, but the result was the same.

Again, I am sure it is because I am not implementing the code properly, but I do not know what the proper way to do this would be.

Here is what I tried first (putting this after the “class EditContactsTempArray extends Contact” code):

class CustomContactsViewContact_Product extends ContactsViewList {
	function __construct() {
		parent::__construct();
	}
	public function display() {
		global $mod_strings, $temp_array; 
		parent::display();
               ...
              code to send test data to the screen via custom smarty
              ...
       }
}

and what I had was the same thing. The normal Contacts ListView appeared first with the standard Contacts data and then after that, the custom output I created was shown.

Is there a file I can go to to see the Variable names used byt the existing smarty class for the header (Contacts), and footer to the ListView and the column headers and column data?

We implement features like that all the time for several customers, without big deal.
The implementation strategy relies on the controller.php itself, as the name state “THE controller”.
Inside the method “action_cpListView” you need to specify a custom bean, which knows how to fetch data from the overridden method get_list_view_data, which is called by a given ListView instance somehow.
Next step is to properly customize the custom view. Start by overriding the method getMetaDataFile in order to specify, as the name states, which metadata file should be loaded in order to render the columns. Override the method preDisplay in order to load an instance of a custom ListView class, by default it will load the ListViewSmarty. By overriding it you will have full controll on how to fetch data and process them accordingly. Eventyally you can override the method getSearchFor2 in order to provide more controll on filtering data.

Good luck!

3 Likes

@andopes

Been caught up in another project so this took low priority but am now coming back to this

When you say “you need to specify a custom bean”, do you mean I need to create a custome module and work from within that?

I am hoping to avoid that if possible.

What I want to do (as an example) is to add a Menu item to Contacts so I can generate an aleternative List View when I am looking at the List View or Detail View for Contacts. Not replace the existing List View, but provide a different List view, so that either is available.

I know how to add a new Menu item in the left column.

I know how to add a new Controller file with a new Entry Point for the new Menu.

I do not want to send users to another module but want instead to stay within the Contacts module and offere a Menu item that, if selected, brings up the new List view.

Do you mean I need a new module to get a new List View that uses the current Contacts List View format?

Or can I access the format from a new Controller I create while staying in the Contacts module?

@Ramblin I mean to use the controller.php to decide whether to instantiate the bean from the core class or from a custom one. That said, for ALL default views and behaviors the bean is instantiated from the core class, but for a specific one (action and view) the bean to be instantiated from a custom class which extends the core one. This way you will be able to control how to manage data. Additionally the custom view will be able to instantiate the list object from a custom ListView class so it will be able to fetch list data in a specific way as well as metadata view to be fetched accordingly.