Model from another component.

62 views
Skip to first unread message

Paul Swingewood

unread,
Aug 23, 2022, 5:33:44 AMAug 23
to Joomla! General Development
Hi all,

I am really struggling. I need to get a model from another component.

I have read all kinds of splurge on JmodelLegacy using:

JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_contact/models', 'ContactModel');
$model   = JModelLegacy::getInstance('Contact', 'ContactModel', ['ignore_request' => true]);

but where does JmodelLegacy live?

use use \Joomla\?????\JModelLegacy;

I'm not convinced this is the right way to do it as I see a lot of this stuff is not going to be used in the furure?

So, I have a component. I want to 'include' the model from another component.

What is the correct way to do this in Joomla 4?

The reason behind this:

My component has a view with several forms tabbed with Uitab.
The view displays correctly and shows the forms reading the XML to render the fields as it should. Actually the XML is just a copy of the XML that is used in the 'individual' form views.

All works well until I want to edit. Because the data isn't coming from the correct model (or that's what I think)

Many Thanks - Paul

Viper

unread,
Aug 23, 2022, 5:52:00 AMAug 23
to Joomla! General Development

MarkRS

unread,
Aug 23, 2022, 6:20:50 AMAug 23
to Joomla! General Development
And. as an addition to @Viper's (excellent) suggestion, any time you see "J" in front of a class name in J4 it's almost certainly not what should be used.

Paul Swingewood

unread,
Aug 23, 2022, 6:43:03 AMAug 23
to Joomla! General Development
Thank you very much @Viper that seems to have got the model correctly.

 /**
     * Method to check out an item for editing and redirect to the edit form.
     *
     * @return  void
     *
     * @since   1.0.0
     *
     * @throws  Exception
     */
    public function edit()
    {
        $app = Factory::getApplication('com_bcc_maccs_trader_application');

        // Get the previous edit id (if any) and the current edit id.
        $previousId = (int) $app->getUserState('com_bcc_maccs_trader_application.edit.personaldetail.id');
        $editId     = $app->input->getInt('id', 0);

        Factory::getApplication()->enqueueMessage('maccsaccountcasecontroller Previous editId - '.$previousId, 'notice');
        Factory::getApplication()->enqueueMessage('maccsaccountcasecontroller  editId - '.$editId, 'notice');

        // Set the user id for the user to edit in the session.
        $app->setUserState('com_bcc_maccs_trader_application.edit.personaldetail.id', $editId);

        // Get the model.
        // $model = $this->getModel('Maccsaccountcase', 'Site'); - Paul this is the old one

        $model = Factory::getApplication()->bootComponent('com_bcc_maccs_trader_application')->getMVCFactory()->createModel('Personaldetail', 'Site');
 
        Factory::getApplication()->enqueueMessage('Model - Personaldetail', 'notice');


        // Check out the item
        if ($editId)
        {
            $model->checkout($editId);
        }

        // Check in the previous user.
        if ($previousId && $previousId !== $editId)
        {
            $model->checkin($previousId);
        }


        // Redirect to the edit screen.
        $this->setRedirect(Route::_('index.php?option=com_bcc_maccs_account_case&view=maccsaccountcaseform&layout=edit', false));
    }

But still not populating the form :(
I assume I have to get the table as well?

This opens a whole new can of worms.
How do I get the table?

I take it in the form model ?
There is this method.

    /**
     * Method to get the table
     *
     * @param   string $type   Name of the Table class
     * @param   string $prefix Optional prefix for the table class name
     * @param   array  $config Optional configuration array for Table object
     *
     * @return  Table|boolean Table if found, boolean false on failure
     */
    public function getTable($type = 'Personaldetail', $prefix = 'Administrator', $config = array())
    {
        return parent::getTable($type, $prefix, $config);
    }

However, trying to get Personaldetail table results in :

Compile Error: Cannot declare class Birminghamcitycouncil\Component\Bcc_maccs_trader_application\Administrator\Table\AccountPersonaldetailTable, because the name is already in use.

Thanks - Paul

Viper

unread,
Aug 23, 2022, 7:44:28 AMAug 23
to Joomla! General Development
It's depending on that model you trying to extend. 
Look at the basic simple model https://github.com/Globulopolis/Jcomments-4/blob/4.1/component/administrator/src/Model/BlacklistModel.php
As you can see, model have getForm and loadFormData, and validate() to override the default methods. Optional you can override a getTable method in you model(to ajust model name or add some extra data) but I would not recommend to do this.

Paul Swingewood

unread,
Aug 23, 2022, 9:49:53 AMAug 23
to Joomla! General Development
So carrying on with this quest:

I realise that the form isn't loading in the form model.

public function getForm($data = array(), $loadData = true)
     {
         // Get the form.
         $form = $this->loadForm('com_bcc_maccs_trader_application.personaldetail', 'personaldetailform', array(
                         'control'   => 'jform',
                         'load_data' => $loadData
                 )
         );

         if (empty($form))
         {
                 return false;
         }

         return $form;
     }

The code above generates an error :

Form::loadForm could not load file

I realise that snippet may not be useful but in the grand scheme of the other posts I am hoping that someone can see the problem.

What Can't I get the form data :(

Paul.

Viper

unread,
Aug 23, 2022, 10:22:33 AMAug 23
to Joomla! General Development
Place a proper form xml file under com_componentname/forms

Paul Swingewood

unread,
Aug 23, 2022, 10:49:29 AMAug 23
to Joomla! General Development
That was one of the first things I coded. :)

The forms display ok just not with any data when you edit.

Capture77.PNG

Paul Swingewood

unread,
Aug 23, 2022, 11:42:04 AMAug 23
to Joomla! General Development
I think I may have cracked it! - Well the first bit anyway:

// Get a level row instance.
            // $table = $this->getTable();
            $table = Factory::getApplication()->bootComponent('com_bcc_maccs_trader_application')->getMVCFactory()->createTable('Personaldetail', 'Site');

However, there are multiple tabbed forms each with their own tables. So, how can I get Multiple tables, is this possible?
Is there a way that the tabs (hmm client side) can trigger different controllers?

You'll see from the screen shots the 2nd form is blank (wrong data)

Capture88.PNGCapture99.PNG



Viper

unread,
Aug 23, 2022, 1:46:56 PMAug 23
to Joomla! General Development
Make one xml form, load data from all tables into one form object(e.g. in loadFormData()). On save()/update() make changes in other tables. Or make changes without using Tables class, just make save() method in model and call it in controller.

Paul Swingewood

unread,
Aug 23, 2022, 2:16:21 PMAug 23
to Joomla! General Development
@viper
'load data from all tables into one form object(e.g. in loadFormData())' - How do I do this?

At present the data is blank causing it to load from getitem which is kind of the problem.

 protected function loadFormData()
     {
         $data = Factory::getApplication()->getUserState('com_bcc_maccs_trader_application.edit.personaldetail.data', array());
print_r($data);
exit;
         if (empty($data))
         {
             $data = $this->getItem();
         }

         if ($data)
         {
             return $data;
         }

         return array();
     }

What syntax do I use to load data from all tables (Personal Details, Application Details, Supporting documents)

Can you give an example please?

Thank you - Paul

Paul Swingewood

unread,
Aug 23, 2022, 2:20:36 PMAug 23
to Joomla! General Development
The XML file
maccsaccountcaseform.zip

Viper

unread,
Aug 23, 2022, 4:07:04 PMAug 23
to Joomla! General Development
Make own fieldset for each tab(look at the https://github.com/joomla/joomla-cms/blob/4.2-dev/administrator/components/com_content/forms/article.xml as example). When render each fieldset via $this->form->renderFieldset($fieldset); in template.
In model getItem() load data from tables into one object.

Paul Swingewood

unread,
Aug 24, 2022, 3:27:44 PMAug 24
to Joomla! General Development
Hi again @Viper - Thanks very much for the help so far.

I have the data. I can have a block of data for each form or a complete set of data as one object as you suggested.

          //PAUL Merge the arrays
         //$alldata = array_merge($properties, $ADresult, $SDresult);
          //PAUL create an object of all data.
         //$data = ArrayHelper::toObject($alldata, CMSObject::class);

         //PAUL create an object for each dataset
         $data = ArrayHelper::toObject($properties, CMSObject::class);
         $ADdata = ArrayHelper::toObject($ADresult, CMSObject::class);
         $SDdata = ArrayHelper::toObject($SDresult, CMSObject::class);

The symptoms are:
1) using the all data method
When I click the link to the form, form 1 is populated but forms 2 and 3 have no data.
2) usng the individual data sets
Same result as above.

I believe this is because form 2 and 3 aren't 'bound' to the data (however if all data is there then why not??)

I am a bit lost tonight. How do I tell form 2 (tab2) to use ADdata and form 3 (tab3) to use SDdata?

Viper

unread,
Aug 25, 2022, 3:28:40 AMAug 25
to Joomla! General Development
You must have object like:
$data = (object) array(
     'id' => 123,
     'title' => 'test',
);
to fill the form inputs.

But in xml you have fieldsets to easy render the part of the form. See an attachment.
user.xml

Paul Swingewood

unread,
Aug 31, 2022, 6:59:39 AMAug 31
to Joomla! General Development
@Viper,

Having had a bank holiday and come back to this I still can't get it to work :(

I have attached the form tmpl, the model, and the form.xml

Can you have a look and tell me what I am doing wrong please?
Paul.zip

MarkRS

unread,
Aug 31, 2022, 8:06:49 AMAug 31
to Joomla! General Development
I'm not @Viper, but why are you dealing with other tables in your model?
Surely having models for each set of data (ie table) and accessing the data through that would be more sensible.
You might claim, and that would be for you to decide, that the subsidiary tables are so dependant on the main table that they don't warrant their own models.  That might be valid.

Having said that, your model doesn't appear to actually extract any data from those subsidiary models.  Which makes it unsurprising that you can't see any of it.

Paul Swingewood

unread,
Aug 31, 2022, 8:18:13 AMAug 31
to Joomla! General Development
Hello MarkRS,

So what's missing?

All the data is stored in $data but I agree I don't see how each form binds to what data?
I had assumed that if all data was there when you called render it just picked the data up.

The problem is, the one view has many forms via tabs.
I was following what Viper said as it seemed to make sense, but no matter what only the first form populates with data.

Paul.

MarkRS

unread,
Aug 31, 2022, 8:46:58 AMAug 31
to Joomla! General Development
I think that's because you're only picking up data from the first table.  Are you saying you've loaded all the data you want?  It doesn't look like it from the bits you've posted.

A model pulls data from (by default) a single table.  The "getItem"  method does that.  Presumably (you didn't show it) your view class does something like

$this->data = $MaccsaccountcaseFormModel->getItem();

You've overridden getItem but what you've shown just does stuff with state.  Where (in the structure) are is the data for the other tabs?  From the template file it looks like it's flat, ie each element is directly under $item, is that the case?  Is that sensible?  Wouldn't it make more sense to have each tab be a separate object, all of the tabs objects being elements of the main item (or is it "data"?

Paul Swingewood

unread,
Aug 31, 2022, 9:59:49 AMAug 31
to Joomla! General Development
Hi MarkRS,

All of the data is done here :

protected function loadFormData()
     {
         $data = Factory::getApplication()->getUserState('com_bcc_maccs_trader_application.edit.personaldetail.data', array());

         if (empty($data))
         {
            /* Paul - An explanation of why I do this.
            The personal detail data has an ID which may not match the associated Application and Supporting documents data
            This way you can ensure that the data aligns correcctly.
            */

             $data = $this->getItem();
            $properties = $data->getProperties();


            //PAUL Get the trader ID of the current data.
        $properties = $data->getProperties(); //convert the object to an array
            $trader_id = $properties['traderid'];

      // PAUL Get the Application Details data
            $dbMarket = Factory::getDbo();
            $readADQuery = $dbMarket->getQuery(true)
                ->select('*')
                 ->from($dbMarket->quoteName('#__maccs_trader_application'))
                 ->where($dbMarket->quoteName('traderid') . " = " . $dbMarket->quote($trader_id));
            $dbMarket->setQuery($readADQuery);
            $ADresult = $dbMarket->loadAssoc();

            // PAUL Get the Supporting Documents data
            $dbMarket = Factory::getDbo();
            $readSDQuery = $dbMarket->getQuery(true)
                ->select('*')
                 ->from($dbMarket->quoteName('#__maccs_supporting_documents'))
                 ->where($dbMarket->quoteName('traderid') . " = " . $dbMarket->quote($trader_id));
            $dbMarket->setQuery($readSDQuery);
            $SDresult = $dbMarket->loadAssoc();

            //PAUL create individual objects of each data maybe these could be used on each form?

          $data = ArrayHelper::toObject($properties, CMSObject::class);
          $ADdata = ArrayHelper::toObject($ADresult, CMSObject::class);
          $SDdata = ArrayHelper::toObject($SDresult, CMSObject::class);

     //PAUL Merge the arrays and make one big $data
         $alldata = array_merge($properties, $ADresult, $SDresult);

         $data = ArrayHelper::toObject($alldata, CMSObject::class);

         print_r($data);
         exit;


        }

         if ($data)
         {
             return $data;
         }

         return array();
     }

and produces this via print_r

Joomla\CMS\Object\CMSObject Object ( [_errors:protected] => Array ( ) [typeAlias] => com_bcc_maccs_trader_application.personaldetail [tagsHelper] => Joomla\CMS\Helper\TagsHelper Object ( [tagsChanged:protected] => [replaceTags:protected] => [typeAlias] => com_bcc_maccs_trader_application.personaldetail ) [id] => 3 [state] => 1 [ordering] => 1 [checked_out] => 0 [checked_out_time] => 0000-00-00 00:00:00 [created_by] => 162 [modified_by] => 162 [title] => 1 [name] => Market Trader [dob] => 2022-08-31 00:00:00 [email] => B...@bodmail.bod [hometelephone] => 12345 [mobile] => 12345 [addressline1] => 12345 [addressline2] => 12345 [towncity] => 12345 [county] => 12345 [postcode] => B1 2JP [notes] => [traderid] => 162 [disabilityrequirements] => 12345 [datesubmitted] => 2022-08-31 13:47:37 [status] => 0 [dateapproved] => [tradername] => Market Trader [legalright] => 1 [sellingtype] => Uranium Fuel Rods [marketname] => 1 [tradingdaysrequestedrag] => 10,13 [storagerequired] => 1 [tradingdaysrequestedopen] => 0 [bankutility] => 162-BankUtility.xlsx [ninumber] => 162-NationalInsurance.docx [iddocument] => 162-IDdocument.jpg [pli] => 162-LiabilityInsurance.pdf [pliexpiry] => [catering] => 1 [envirohealth] => 162-EnviroCert.docx [cookingoil] => 162-WasteOil.docx [dutyofcare] => 162-DOCCertificate.pdf )

So surely it must be there?  I can't understand why it doesn't render it on each form?

I did split it, but again couldn't get it to render on each form:

The reason for the SQL is to ensure that each traderID matches and we are returning the correct data. I couldn't fathom how to do this without the queries.

Paul

Paul Swingewood

unread,
Aug 31, 2022, 10:10:02 AMAug 31
to Joomla! General Development
As a further test I rendered ADsellingtype in the Personal Details form (having made the necessary adjustment in the form.xml)

Although it renders the field correctly there is no data. Which points out what you said. Only the first form data is being bound! 

How do I correct it though?

Paul

MarkRS

unread,
Aug 31, 2022, 10:46:12 AMAug 31
to Joomla! General Development
My first thought it you don't have the data the way you think you do.  Rendering the field correctly has nothing to do with the contents, so seeing a field there doesn't indicate much about the presence or otherwise of data.

If you don't have visual debugging available, move your "print_r" into your view or template.

Having said that, I've got a few suggestions.

Why have you got two consecutive calls to $data->getProperties()?  One is redundant.

Really, don't put data extraction code in the view, it belings in a model.  MVC is a really good idea.  Use models to handle data.  Load that data in views.

You're converting arrays to objects, why not just load objects?

MarkRS

unread,
Aug 31, 2022, 10:49:55 AMAug 31
to Joomla! General Development
And another thing(s) :-)

Use multiple templates and load them into the tabs in the main (default.php)  one.  That way you've got smaller, more easily viewed, units.

Create models for the individual tabs then you can use the J! supplied methods to produce your results with less fluff.  Us the J! fluff! 8-)
Reply all
Reply to author
Forward
0 new messages