Thank you for the information about JCook updateXref method. It took me a while to figure out all the changes needed in order to use JCook methods.
I started using it but I have some problems of possible bugs.
Let me explain my scenario:
We are combining two back-end view in one.
1- View called Contracts: To create a new contract. Where it would be selected the Client and the Vendor, plus other details less relevant for the explanation.
2- View Contract Assets: Designed with JCook platform to add Assets to a Contract. Due to JCook platform limitation is was designed to add one Asset per Contract at a time. However we used JCook to generated a second Contract view called "Contractassets" in order to generate the second Contract form called "contract assets_form.php"
For that reason we are combining the two view in one in order to be Able to create a Contract and at the same time add the respective Assets at one time and for other purposes and benefits to the end user.
check the view images in the attachments with their respective names.
In order to create the combined view I did the following.
1- Forked Contract.xml
1- Created a second fieldset.
<fieldset name="contractassets.form" (using the JCook code generated for the second view in Contracts)
.... original fields used in the 1st view Contract from .....
.... plus new fields used in the second view CotractAssets form.
<field name="asset_id"
alias="_asset_id_serial_number"
label="MYCOMPONENT_FIELD_ASSET_SERIAL_NUMBER"
filter="ARRAY"
required="false"
class="element-filter"
listKey="id"
labelKey="serial_number"
type="ckxref"
size="1"
height="28" (NOTE: DUE TO THE CKXREF TYPE IT IS NOT FOLLOWING THE DEFINED HEIGHT)
valueKey="asset_id"
nullLabel="MYCOMPONENT_JSEARCH_SELECT_ASSET_SERIAL_NUMBER"/>
NOTE: Double checking. In here, SHOULD IT BE MY VALUEKEY ="asset_id" or "vendor_id" ?
2- Forked /Models/Contract.php
- Function populateObjects
- public function populateObjects(&$item) {
// Load XRef Assets /* To get all Related Contract Assets
$this -> loadXref($item, 'assets', 'contractassets', 'contract_id', 'id', null, 'contract.contractassets');
}
NOTE: This generates and an array of objects with the Assets related to my contract like doing it in the original JCook ContractAssets back-end view created. However is does not give me ALL the assets available for the Contract by Vendor and Client since the original JCook Contract Asset view is 1:1 and let you pick and choose ANY ASSET available regardless of the Contract even though the Contract is related to an exclusive Vendor and Client.
- Function prepareQuery:
- I created the case 'contract.contractassets':
- Is it the same as original case 'contract.contract' since my other field comes from the Xref data and I am creating the table when I click on the add button with JavaScript and JQuery when
- Function save($data) {
public function save($data) {
//Convert from a non-SQL formatted date (creation_date)
$data['creation_date'] = MycomponentHelperDates::getSqlDate($data['creation_date'], array('Y-d-m'), true);
//Convert from a non-SQL formatted date (checked_out_time)
$data['checked_out_time'] = MycomponentHelperDates::getSqlDate($data['checked_out_time'], array('Y-m-d H:i'), true);
//Convert from a non-SQL formatted date (modification_date)
$data['modification_date'] = MycomponentHelperDates::getSqlDate($data['modification_date'], array('Y-d-m'), true);
//Convert from a non-SQL formatted date (start_date)
$data['start_date'] = MycomponentHelperDates::getSqlDate($data['start_date'], array('Y-d-m'), true);
//Convert from a non-SQL formatted date (expiration_date)
$data['expiration_date'] = MycomponentHelperDates::getSqlDate($data['expiration_date'], array('Y-d-m'), true);
//Some security checks
$acl = MycomponentHelper::getActions();
//Secure the author key if not allowed to change
if (isset($data['created_by']) && !$acl->get('core.edit'))
unset($data['created_by']);
$result = parent::save($data);
if ($result && isset($data['asset_id'])) {
// Reload the item after save
$item = $this -> getItem();
// Update Xref links : Assets FOLLOWING JCOOK N:M GUILINES
$model = CkJModel::getInstance('ContractAssets', 'MycomponentModel');
$model -> updateXref('asset_id', $data['asset_id'], 'contract_id', $item -> id);
}
return $result;
}
NOTES: I noticed that the data from $data generated for my field "asset_id" did not have the same structure as the one generated by the ContractAsset model whenever you go directly to that view and save. After tracing multiple dumps that data passed to "updateXref" (
$model -> updateXref('asset_id', $data['asset_id'], 'contract_id', $item -> id);
) was different that what it was required. Therefore even tried forcing the required data structure by re-building my $data with in the save function before calling updateXref.
Due to this difference I used the Xref to get $data) that let me knows the current Assets already selected for the current contract available by vendor and client. I also generated a $lists that does not get pass to the $data since it is not in the original method for "function loadFormData()". Therefore I used the my $_Post to get the new values inserted as Related Assets for the current contract that are passed to my $_Post in the input fields from the table generated in the view form.
3- Forked Models/ContractAssets.php
- Function preparequery: (Note: this is pretty much the same query used when using the filters search in the back-end with couple more columns);
The main purpose it to gather the Assets related to a Contract by Vendor Id and Client Id since those are the premises in the Contract form.
case 'contract.contractassets':
//BASE FIELDS
$this->addSelect( 'a.asset_id,'
. 'a.checked_out_time,'
. 'a.contract_id,'
. 'a.cost,'
. 'a.support_level_id,'
. 'a.notes');
//SELECT
$this->addSelect('_contract_id_.vendor_id AS `_contract_id_vendor_id`');
$this->addSelect('_contract_id_.client_id AS `_contract_id_client_id`');
$this->addSelect('_contract_id_vendor_id_.vendor_name AS `_contract_id_vendor_id_vendor_name`');
$this->addSelect('_contract_id_.contract_number AS `_contract_id_contract_number`');
$this->addSelect('_contract_id_client_id_.client_name AS `_contract_id_client_id_client_name`');
$this->addSelect('_asset_id_.site_id AS `_asset_id_site_id`');
$this->addSelect('_asset_id_.model_id AS `_asset_id_model_id`');
$this->addSelect('_asset_id_site_id_.site_name AS `_asset_id_site_id_site_name`');
$this->addSelect('_asset_id_.serial_number AS `_asset_id_serial_number`');
$this->addSelect('_asset_id_model_id_.model AS `_asset_id_model_id_model`');
$this->addSelect('_asset_id_model_id_.product_type_id AS `_asset_id_model_id_product_type_id`');
$this->addSelect('_asset_id_model_id_product_type_id_.product_type AS `_asset_id_model_id_product_type_id_product_type`');
$this->addSelect('_support_level_id_.support_level AS `_support_level_id_support_level`');
$this->addSelect('_support_level_id_.id AS `_support_level_id_support_level_id`');
$this->addSelect('_checked_out_.name AS `_checked_out_name`');
//JOIN
$this->addJoin('`#__mycomponent_contracts` AS _contract_id_ ON _contract_id_.id = a.contract_id', 'LEFT');
$this->addJoin('`#__mycomponent_vendors` AS _contract_id_vendor_id_ ON _contract_id_vendor_id_.id = _contract_id_.vendor_id', 'LEFT');
$this->addJoin('`#__mycomponent_clients` AS _contract_id_client_id_ ON _contract_id_client_id_.id = _contract_id_.client_id', 'LEFT');
$this->addJoin('`#__mycomponent_assets` AS _asset_id_ ON _asset_id_.id = a.asset_id', 'LEFT');
$this->addJoin('`#__mycomponent_sites` AS _asset_id_site_id_ ON _asset_id_site_id_.id = _asset_id_.site_id', 'LEFT');
$this->addJoin('`#__mycomponent_productmods` AS _asset_id_model_id_ ON _asset_id_model_id_.id = _asset_id_.model_id', 'LEFT');
$this->addJoin('`#__mycomponent_producttypes` AS _asset_id_model_id_product_type_id_ ON _asset_id_model_id_product_type_id_.id = _asset_id_model_id_.product_type_id', 'LEFT');
$this->addJoin('`#__mycomponent_supportlevels` AS _support_level_id_ ON _support_level_id_.id = a.support_level_id', 'LEFT');
$this->addJoin('`#__users` AS _checked_out_ ON _checked_out_.id = a.checked_out', 'LEFT');
// Order by Serial number
$this->setState('list.ordering', '_asset_id_serial_number');
//$this->addOrder('_asset_id_serial_number');
// Disable the pagination
$this->setState('list.limit', null);
$this->setState('list.start', null);
break;
4- Forked /View/Contract/view.html.php:
- Created the new custom view called
- NOTE: PLEASE LET ME KNOW IF YOU HAVE A BETTER WAY TO CONVER TO A JSON than the way I found to do it.
EX:
echo '<script>var asset_id= '.new JResponseJson($lists['fk']['asset_id']).'; </script>';
- function displayContractassets:
protected function displayContractassets($tpl = null)
{
// Initialise variables.
$this->model = $model = $this->getModel();
$this->state = $state = $this->get('State');
$this->params = $state->get('params');
$state->set('context', 'contract.contractassets');
// BEGIN FORK
// To get my Related Assets following JCook methods
$model->setState('xref.assets', true);
// END FORK
$this->item = $item = $this->get('Item');
$this->form = $form = $this->get('Form');
$this->canDo = $canDo = MycomponentHelper::getActions($model->getId());
$lists = array();
$this->lists = &$lists;
// Define the default title
$this->params->def('title', JText::_('Mycomponent_LAYOUT_CONTRACT_WITH_ASSETS'));
$this->_prepareDocument();
// Deprecated var : use $this->params->get('page_heading')
$this->title = $this->params->get('page_heading');
$user = JFactory::getUser();
$isNew = ($model->getId() == 0);
//Check out the item.
if (!$isNew)
$model->checkout($model->getId());
//Check ACL before opening the form (prevent from direct access)
if (!$model->canEdit($item, true))
$model->setError(JText::_('JERROR_ALERTNOAUTHOR'));
// Check for errors.
if (count($errors = $model->getErrors()))
{
JError::raiseError(500, implode(BR, array_unique($errors)));
return false;
}
$jinput = JFactory::getApplication()->input;
//Hide the component menu in item layout
$jinput->set('hidemainmenu', true);
//Toolbar initialization
JToolBarHelper::title(JText::_('Mycomponent_LAYOUT_CONTRACT_WITH_ASSETS'), 'mycomponent_contracts');
// Save
if (($isNew && $model->canCreate()) || (!$isNew && $item->params->get('access-edit')))
CkJToolBarHelper::apply('contract.apply', "Mycomponent_JTOOLBAR_SAVE");
// Save & Close
if (($isNew && $model->canCreate()) || (!$isNew && $item->params->get('access-edit')))
CkJToolBarHelper::save('contract.save', "Mycomponent_JTOOLBAR_SAVE_CLOSE");
// Save & New
if (($isNew && $model->canCreate()) || (!$isNew && $item->params->get('access-edit')))
CkJToolBarHelper::save2new('contract.save2new', "Mycomponent_JTOOLBAR_SAVE_NEW");
// Cancel
CkJToolBarHelper::cancel('contract.cancel', "Mycomponent_JTOOLBAR_CANCEL");
$model_client_id = CkJModel::getInstance('Clients', 'MycomponentModel');
$model_client_id->addGroupOrder("a.client_name");
$lists['fk']['client_id'] = $model_client_id->getItems();
$model_vendor_id = CkJModel::getInstance('Vendors', 'MycomponentModel');
$model_vendor_id->addGroupOrder("a.vendor_name");
$lists['fk']['vendor_id'] = $model_vendor_id->getItems();
// BEGIN FORK
// $this->item->assets
// Related Assets gathered following JCook methods
// To be used in JavaScript to disable Assets already selected
// It is been pass as a JSON to the JavaScript
echo '<script>var contract_assets_id= '.new JResponseJson($this->item->assets).'; </script>';
// $model_assets_id
// Related Assets gathered following JCook methods
// To generate data used in form fields as Related Contract Assets available according to the current Contract.
// It will get ALL related Assets available by Client ID and Vendor ID according to the current Contract
// It is been pass as a JSON to the JavaScript
$model_asset_id = CkJModel::getInstance('Assets', 'MycomponentModel');
$model_asset_id->addSelect( 'a.checked_out_time,'
. 'a.client_id,'
. 'a.model_id,'
. 'a.serial_number,'
. 'a.site_id');
$model_asset_id->addSelect('_client_id_.client_name AS `_client_id_client_name`');
$model_asset_id->addSelect('_site_id_.site_name AS `_site_id_site_name`');
$model_asset_id->addSelect('_model_id_.model AS `_model_id_model`');
$model_asset_id->addSelect('_model_id_.vendor_id AS `_model_id_vendor_id`');
$model_asset_id->addSelect('_model_id_.product_type_id AS `_model_id_product_type_id`');
$model_asset_id->addSelect('_model_id_product_type_id_.product_type AS `_model_id_product_type_id_product_type`');
$model_asset_id->addSelect('_model_id_vendor_id_.vendor_name AS `_model_id_vendor_id_vendor_name`');
$model_asset_id->addSelect('_checked_out_.name AS `_checked_out_name`');
// $model_asset_id JOIN
$model_asset_id->addJoin('`#__mycomponent_clients` AS _client_id_ ON _client_id_.id = a.client_id', 'LEFT');
$model_asset_id->addJoin('`#__mycomponent_sites` AS _site_id_ ON _site_id_.id = a.site_id', 'LEFT');
$model_asset_id->addJoin('`#__mycomponent_productmods` AS _model_id_ ON _model_id_.id = a.model_id', 'LEFT');
$model_asset_id->addJoin('`#__mycomponent_producttypes` AS _model_id_product_type_id_ ON _model_id_product_type_id_.id = _model_id_.product_type_id', 'LEFT');
$model_asset_id->addJoin('`#__mycomponent_vendors` AS _model_id_vendor_id_ ON _model_id_vendor_id_.id = _model_id_.vendor_id', 'LEFT');
$model_asset_id->addJoin('`#__users` AS _checked_out_ ON _checked_out_.id = a.checked_out', 'LEFT');
// $model_asset_id WHERE
$model_asset_id->addWhere('a.client_id = '.$this->item->client_id);
$model_asset_id->addWhere('_model_id_.vendor_id = '.$this->item->vendor_id);
// $model_asset_id Disable the pagination
$model_asset_id->setState('list.limit', null);
$model_asset_id->setState('list.start', null);
$lists['fk']['asset_id'] = $model_asset_id->getItems();
echo '<script>var asset_id= '.new JResponseJson($lists['fk']['asset_id']).'; </script>';
// $model_support_level_id
// Related Support Levels gathered following JCook methods
// To generate data used in form fields as Support Level available according to the Vendor in the current Contract.
// It will get ALL related Support Levels available by Vendor ID according to the current Contract
// It is been pass as a JSON to the JavaScript
$model_support_level_id = CkJModel::getInstance('Supportlevels', 'MycomponentModel');
$model_support_level_id->addWhere('a.vendor_id = '.$this->item->vendor_id);
$model_support_level_id->addGroupOrder("a.vendor_id");
$lists['fk']['support_level_id'] = $model_support_level_id->getItems();
echo '<script>var support_level_id= '.new JResponseJson($lists['fk']['support_level_id']).'; </script>';
// END FORK
}
NOTE: The "$lists" is the actual field been displayed instead of the one generated by the Xref model and passed to my $data) due to the inconvenient that we need a 1:M relation Contract(1) with Multiple Assests(M).
5- Forked /view/contract/tmpl/contractassets_form.php
- Previous fields are done in the condense mode with JCOOK
- Concerned fields has html created as following:
<?php
// Asset_id FROM XREF MODEL
$field = $fieldSet['jform_asset_id'];
$field->jdomOptions = array(
'list' => $this->lists['fk']['asset_id']
);
?>
<div class="control-group <?php echo 'field-' . $field->id . $field->responsive; ?>">
<div class="control-label">
<?php echo $field->label; ?>
</div>
<div class="controls">
<select id="jform_asset_id" name="jform[asset_id][]" class="element-filter" >
<option value="" ">- Select Support Level -</option>
<?php foreach ($this->lists['fk']['asset_id'] as $option) : ?>
<option value="<?php echo $option->id; ?>" > <?php echo $option->serial_number; ?></option>
<?php endforeach; ?>
</select>
<button class="addvalue" type="button">Add value</button>
<button class="addall" type="button">Add ALL</button>
</div>
</div>
<div id="table-assets">
<table class="asset_id"><caption>Contract Assets list</caption>
<thead><tr>
<th>Serial #</th>
<th>Type</th>
<th>Model</th>
<th>Support Level</th>
<th>Cost</th>
<th>Notes</th>
<th>To do</th>
</tr></thead>
<tbody>
<?php foreach ($this->item->assets as $asset) : ?>
<tr id="<?php echo $asset->asset_id; ?>">
<td><input type="hidden" name="jform[contract_asset_id][]" value="<?php echo $asset->asset_id; ?>" readonly>
<input type="text" name="jform[asset_serial][]" value="<?php echo $asset->_asset_id_serial_number; ?>" readonly>
</td>
<td><input type="text" name="jform[asset_type][]" value="<?php echo $asset->_asset_id_model_id_product_type_id_product_type; ?>" readonly></td>
<td><input type="text" name="jform[asset_model][]" value="<?php echo $asset->_asset_id_model_id_model; ?>" readonly></td>
<td><select id="jform_support_level_id" name="jform[support_level][]">
<option value="" ">- Select Support Level -</option>
<?php foreach ($this->lists['fk']['support_level_id'] as $option) : ?>
<option value="<?php echo $option->id; ?>" <?php echo ((($option->id == $asset->_support_level_id_support_level_id))? 'selected':''); ?>> <?php echo $option->support_level; ?></option>
<?php endforeach; ?>
</select></td>
<td><input type="text" name="jform[cost][]" value="<?php echo $asset->cost; ?>"></td>
<td><input type="text" name="jform[notes][]" value="<?php echo $asset->notes; ?>"</td><button class="remmovevalue" type="button">Remove value</button></tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
Summary:
1- Following the N:M guidelines that you have does not work since my $data does not recognize the new input fields generated. Therefore when it is been passed to the updateXref it does not save the correct info(just the asset id in the ContractAssets table).
How can I solve this?
2- By using the original JCook back-end "Contract Asset" view to create a 1:1 relation for a contract with one Asset the Asset Id been save in the ContractAssets table in not the correct one. The number been save is an auto-increment number. Please check the respect images for visualisation of the error.
- Image name "Jcook_Original_Contract_Asset_view" There you would see the Contract Asset to be save with the details in the notes field showing the "asset_id" that should show up in the database after been save.
- Image name "Jcook_Original_Contract_Asset_view_after_saving" Here you would see that the Contract Assets was saved but the Asset Serial number is NA instead of the one that suppose to be saved. This is due to the asset_id number discrepancy or ERROR.
- image name: "Jcook_Original_database_table_Contract_Assets" This show the data that is was save in the database for the info related in the previous images. As you would see the "asset_id" is not "18" like it supposed to be. It saved "asset_id=154".
Please fix the BUG to save the correct "asset_id" in the JCook Original Contract Asset View.
3- What would it be the best procedure to avoid having two variables in the function save($data) method?
Variable 1: $lists that passes to $data as $data since it is on me original fields.
Variable 2: $_POST The input from the table generated in the view form. This the actual data that needs to be saved in order to save the Contract:ContractAssets relation (1:M).
I also noticed some spelling issues:
Initialiase ->Initialise (in function preparequery)
formated -> formatted (in /models/ function save)