Form Prefill
Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.
- learning:
- Prefill search criterion, prefill an object at creation or on transition
- level:
- Advanced
- domains:
- PHP, Automation, Constrain
- min version:
- 2.5.0
Search preset
When you edit a Team and start to add Members, then the search
of those Persons is automatically filtered on the Organization of
the Team. This was a particular example, which apply on all classes
with an organization. When you edit any object A with an
Organization field and start to add related objects B, the search
of those objects B is automatically filtered with the Organization
of the Object A.
Creation preset
If you create a new Caller person while editing a User Request, that Person Organization will be prefilled with the User Request Organization.
This magic is valid in 95% of the cases, but there are always corner cases, which can now be addressed by completing this automatic behavior with some specific one depending on the context. This can be done thanks to…
New API methods
Function | Description |
PrefillSearchForm(&$aContextParam) | to preset fields in the search frame of related objects |
PrefillCreationForm(&$aContextParam) | to preset fields in an object creation form |
PrefillTransitionForm(&$aContextParam) | to preset fields in a transition form |
You may enter those functions in different situations
-
From a menu / action
-
While adding objects on an Indirect LinkedSet attribute (n:n relationship)
-
From the + button near an ExternalKey attribute while editing another object (1:n relationship)
-
From the edit in line mode when editing a LinkedSet attribute (n:1 relationship)
-
…
Prefill Search Form
This is defined on the Source object class to preset fields in each search frame of related objects, so within the same method, depending on the search object class, we will preset different search criteria.
Context Parameter | Description |
$aContextParam['dest_class'] | provide the Searched class |
$aContextParam['filter'] | provide the filter (DBObjectSearch) object |
$aContextParam['user'] | provides the login string of the connected user |
$aContextParam['origin'] | can be either console or
portal |
Example
- class:Contract
-
/** Example of how on the Contract class, the Search has been modified to search for: * Services from the Provider only * Contacts and Documents from both the Customer and the Provider **/ public function PrefillSearchForm(&$aContextParam) { // If we want to search for objects of class 'Service' or a sub-class of 'Service' if($aContextParam['dest_class'] == 'Service' || is_subclass_of($aContextParam['dest_class'], 'Service')) { // If 'org_id' is an existing attribute of that searched class // and 'provider_id' of the source Contract is not empty if(MetaModel::IsValidAttCode($aContextParam['dest_class'],'org_id') && !empty($this->Get('provider_id'))) { // We remove any criteria set by the default search $aContextParam['filter']->ResetCondition(); // We set a criteria on the 'org_id' of the searched class with the Contract provider value $aContextParam['filter']->AddCondition('org_id', $this->Get('provider_id')); } } // If we want to search for objects of class or sub-classes of 'Contact' or 'Document' elseif (($aContextParam['dest_class'] == 'Contact' || is_subclass_of($aContextParam['dest_class'], 'Contact')) || ($aContextParam['dest_class'] == 'Document' || is_subclass_of($aContextParam['dest_class'], 'Document'))) { // If 'org_id' is an existing attribute of that searched class // Defensive programming in case the data model of remote classes is changed. if(MetaModel::IsValidAttCode($aContextParam['dest_class'],'org_id')) { $aOrgIds = array(); if(!empty($this->Get('provider_id'))) $aOrgIds[] = $this->Get('provider_id'); if(!empty($this->Get('org_id'))) $aOrgIds[] = $this->Get('org_id'); // if 'provider_id' or 'org_id' of the source Contract are not empty // This test is not needed when org_id and provider_id are mandatory on the Contract class if(count($aOrgIds)>0) { // We set a criteria on 'org_id' with multiple selected values (IN parameter) $aContextParam['filter']->ResetCondition(); $aContextParam['filter']->AddCondition('org_id', $aOrgIds , 'IN'); } } } }
Prefiltering on a sub-class
class UserRequest extends Ticket { /** * Other example, in which we want to limit the CIs search to the Server class * and allow the User to set other Server fields as search criteria */ public function PrefillSearchForm(&$aContextParam) { if($aContextParam['dest_class'] == 'FunctionalCI') { // We change the FunctionalCI search to limit it to Server $aContextParam['filter']->ChangeClass('Server'); } } }
Using →ChangeClass('Server')
is much better than
→AddCondition('finalclass', 'Server')
.
With the proposed option the fields usable as search criteria are
any Server field, while with the second option they are limited to
just those of a FunctionalCI
Prefiltering using a relationship
class UserRequest extends Ticket { /** * Other example, in which we want to limit the CIs search to the PC linked to the caller of the User Request * SELECT PC JOIN lnkContactToFunctionalCI AS L ON L.functionalci_id=PC.id WHERE L.contact_id = :this->caller_id */ public function PrefillSearchForm(&$aContextParam) { if($aContextParam['dest_class'] == 'FunctionalCI') { $aContextParam['filter']->ResetCondition(); // We filter the FunctionalCI search to limit it to PC $aContextParam['filter']->AddCondition('finalclass', 'PC'); // We search for lnkContactToFunctionalCI $oSearchLnk = new DBObjectSearch('lnkContactToFunctionalCI'); // We add a condition on that lnk search to limit it to those related to the caller $oSearchLnk ->AddCondition('contact_id', $this->Get('caller_id')); // We join that lnk search with the FunctionalCI search, providing the key for that join $aContextParam['filter']->AddCondition_ReferencedBy($oSearchLnk, 'functionalci_id'); } } }
Prefill Creation Form
Prefill fields in the Creation form of a class
-
The automatic mechanism has already done its work, so fields can be preset already, if the object is created from another one.
-
$this
reference the current object which is about to be created in Database, if the user submit the form.
Context Parameter | Description |
$aContextParam['user'] | provides the login string of the connected user |
$aContextParam['origin'] | can be either console or
portal |
$aContextParam['source_obj'] | available only when creating an external key object (for example, a new Team object created from a Person object) |
Example 1
In this example, we create a RequestTemplate from a
ServiceSubcategory, and we want to not only preset the
servicesubcategory_id
, which is done automatically,
but also the service_id
which can be guessed from the
servicesubcategory_id
- class:RequestTemplate
-
public function PrefillCreationForm(&$aContextParam) { $id = $this->Get('servicesubcategory_id'); if ($id != 0) { // Get the original object itself $oSubcategoryObject = MetaModel::GetObject('ServiceSubCategory', $id, false); // Prefill other data with original object fields value $this->Set('service_id',$oSubcategoryObject->Get('service_id')); } }
Example 2
In this example, we create a new Contract and we want to preset
the start_date
with current date and the
provider_id
with the user organization.
- class:CustomerContract
-
public function PrefillCreationForm(&$aContextParam) { // Preset the starting date of the contract with the current date // if not already set by Object Copier for eg. if(empty($this->Get('start_date'))) { $this->Set('start_date',time());} // Get the current user as the parameter only provides the login $oUser = UserRights::GetUserObject(); // Preset the Provider of the contract as the organization of the current user // Be cautious ''org_id'' was added to the User class in 2.5.0 only $this->Set('provider_id',$oUser->Get('org_id')); }
Example 3
In this example, we create a new UserRequest and we want to
preset the caller id
with the contact_id of the
connected user.
- class:UserRequest
-
public function PrefillCreationForm(&$aContextParam) { // Get the current user as the parameter only provides the login $oUser = UserRights::GetUserObject(); // Prefill field caller with the contact of user $this->SetIfNull('caller_id', $oUser->Get('contactid')); return true; }
Internals
When the creation form of a class A object is invoked from a class B object with a AttributeLinkedSet pointing to class A, the Context[“default”] parameter provides the id of the class B object, with the code attribute of the class A ExternalKey pointing to class B: <itop>/pages/UI.php?operation=new&class=RequestTemplate&c[menu]=ServiceSubcategory&default[servicesubcategory_id]=16
Prefill Transition Form
To preset values in a transition form:
-
At this stage the transition is not yet performed.
-
$this
reference the current object on which the transition will be applied.
Context Parameter | Description |
$aContextParam['expected_attributes']['attribute_code'] | provides display flags on attributes in the form |
$aContextParam['origin'] | can be either console or
portal |
$aContextParam['stimulus'] | provide the applied stimulus |
This example implements a “pre-assign to me” logic:
-
If I assign or re-assign a ticket, which is not assigned to me and I am part of the team to which this ticket is dispatched, then when I enter the transition form, my name is preset as the 'agent'. I can change it.
- class:UserRequest
-
public function PrefillTransitionForm(&$aContextParam) { $oPerson = UserRights::GetContactObject(); // If we have a Person associated with the current User // and we are in an assignment transition if ($oPerson && ($aContextParam['stimulus'] == 'ev_assign' || $aContextParam['stimulus'] == 'ev_reassign')) { $iTicketAgentId = $this->Get('agent_id'); $iPersonId = $oPerson->GetKey(); // If the agent is not already the current user if ( $iTicketAgentId != $iPersonId ) { // Check if the current Contact can be assigned to this ticket // = There is a team and Contact is part of that team $iTicketTeamId = $this->Get('team_id'); $oTeamLinkSet = $oPerson->Get('team_list'); $bIsInTeam = false; while($oTeamLink = $oTeamLinkSet->Fetch()) { // We don't use GetKey as it would return the key of the link object $iTeam = $oTeamLink->Get('team_id'); if ($iTicketTeamId == $iTeam) { $bIsInTeam = true; } } if($bIsInTeam) { $this->Set('agent_id',$iPersonId); //As we modified a potential MUST CHANGE value, we'll take care of it by removing this flag if(array_key_exists('agent_id', $aContextParam['expected_attributes'])) { $aContextParam['expected_attributes']['agent_id'] = ~OPT_ATT_MUSTCHANGE & $aContextParam['expected_attributes']['agent_id']; } } } } }
Known limitations:
-
When the agent is preset with the user name, it is also done in the details of the ticket below as if the change was done already which is not true (confusing display), if the transition is cancelled, the previous agent is back, as expected.
-
It is possible for the user to bypass the “must-change” flag in case of preset, by setting back the previous agent. It won't be seen by the UI and “must-change” is not tested on the back-end.
-
If you prefill a field that's only “mandatory” in the target state, it may not show as iTop only shows these fields if they are null and it won't be updated as form prefill only prefill the object in memory prior to display in the form. You can work around this issue by adding “must_prompt” flag to your field in this transition.
Integrate a function within XML
Example of how to declare the above function within XML datamodel definition
<class id="RequestTemplate" _delta="must_exist"> <methods> <method id="PrefillCreationForm" _delta="define"> <static>false</static> <access>public</access> <type>Overload-DBObject</type> <arguments> <argument id="1"> <type>reference</type> <mandatory>true</mandatory> </argument> </arguments> <code><![CDATA[public function PrefillCreationForm(&$aContextParam) { // code of the function }]]> </ code> </method> </methods> </class>