Force a field to be mandatory on condition
Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.
- learning:
- Force a field to be filled on condition
- level:
- Advanced
- domains:
- PHP, Constrain
- methods:
- AddAttributeFlags, AddInitialAttributeFlags, ForceAttributeFlags, ForceInitialAttributeFlags, IsValidAttcode, DoCheckToWrite
- min version:
- 2.3.0
This use case is just one way of forcing a field to be provided.
Check before Submission
But it does not work for DataSynchro and REST/JSON API.
As a result in the below example, a change of status, will not immediately force the location to be mandatory.
Only Server in production
In this use case, we want to ensure that
-
At creation, no location is proposed
-
On modification of a Server in production, the location becomes mandatory.
- itop_design / classes
-
<class id="PhysicalDevice"> <fields> <field id="location_id"> <!-- force location_id flags to be recomputed if status is changed --> <dependencies _delta="redefine"> <attribute id="org_id"/> <attribute id="status"/> </dependencies> </field> </fields> </class> <class id="Server" _delta="must_exist"> <event_listeners> <event_listener id="evtSetInitialFlags" _delta="define"> <!-- Id of the event does not have to be the same as the function, but why not --> <event>EVENT_DB_SET_INITIAL_ATTRIBUTES_FLAGS</event> <rank>10</rank> <!-- The callback must be the name of an existing class method. The name is free --> <callback>evtSetInitialFlags</callback> </event_listener> <event_listener id="evtSetAttributeFlags" _delta="define"> <event>EVENT_DB_SET_ATTRIBUTES_FLAGS</event> <rank>10</rank> <callback>evtSetAttributeFlags</callback> </event_listener> </event_listeners> <methods> <method id="evtSetInitialFlags" _delta="define"> <static>false</static> <access>public </access> <comment>This method is called once, before opening the Server creation form</comment> <code><![CDATA[
public function evtSetInitialAttributeFlags(Combodo\iTop\Service\Events\EventData $oEventData) { // Force the flags, ignoring existing ones $this->ForceInitialAttributeFlags('location_id', OPT_ATT_HIDDEN); // $this->AddInitialAttributeFlags('location_id', OPT_ATT_HIDDEN); might not do the work if some other code forces it to be mandatory as well }
]]></ code> </method> <method id="evtSetAttributeFlags" _delta="define"> <static>false</static> <access>public </access> <comment>This method is called once, before opening a Server modification form</comment> <code><![CDATA[
public function evtSetAttributeFlags(Combodo\iTop\Service\Events\EventData $oEventData) { if ((MetaModel::IsValidAttCode(get_class($this), 'status')) && ($this->Get('status') == 'production')) { // Adding the mandatory flag $this->AddAttributeFlags('location_id', OPT_ATT_MANDATORY); // You can decide that you want to set this flag and ignore what other flags already set for this attribute // In which case instead of AddAttri... use ForceAttri... $this->ForceAttributeFlags('location_id', OPT_ATT_MANDATORY); } }
]]></ code> </method> </methods> </class>
Only Server and Network Device in production
Declare the above events and methods twice, one on each class Server and NetworkDevice
Only Datacenter Devices in production
Declare the above events and methods on class DatacenterDevice instead of Server
Check on Submission
We can also check on submission that a location was provided, if not, we prevent the creation/modification and display an error message so the user can fill the Location and submit again.
We will subscribe to event EVENT_DB_CHECK_TO_WRITE, and defined
a callback method evtCheckToWrite()
of the object
class:
-
This event is generated just before writing to database - See details of call stack.
-
The callback method should provide error message(s) if it encounters data incoherence.
-
Errors messages are generated using $this->AddCheckIssues('Some Error Message'),
-
Warnings messages use $this->AddCheckWarnings('Some Waring Message'),
-
-
When returning from this method, if there is at least one error the object is not written to database (creation or update)
-
Error and warning messages are
-
displayed to the user in interactive mode only: Console, Portal, CSV import
-
logged in itop/log/error.log depending on level of tracking for DataSynchro, REST/JSON, CLI
-
Migration: No visible effect on setup, but
objects not compliant can no more be modified, until they are made
compliant. So it could prevent a datasynchro or a REST/JSON script
to update other fields for eg.
To identify faulty objects, create an audit rule to retrieve
objects not compliant to this new constrain and fix them one by one
in the UI or by CSV import.
AddCheckIssue()
method in the callback of an EVENT_DB_CHECK_TO_WRITE prevents
creation/modification in all cases: on the Console, in the Portal,
in CSV import, in DataSynchro and in REST/JSON APISet
values on current object in the
EVENT_DB_CHECK_TO_WRITE callback method, it has no
effectBefore iTop 3.1.0 you could use the Extensibility API and put
that same code into
iApplicationObjectExtension::OnCheckToWrite()
In this use case we want to prevent a Server to be put in 'production' status without a Location to be provided.
- itop_design / classes
-
<class id="Server" _delta="must_exist"> <event_listeners> <event_listener id="evtCheckToWrite" _delta="define"> <!-- Id of the event does not have to be the same as the function, but why not --> <event>EVENT_DB_CHECK_TO_WRITE</event> <rank>10</rank> <!-- The callback must be the name of an existing class method. The name is free --> <callback>evtCheckToWrite</callback> </event_listener> </event_listeners>
- class:Server
-
public function evtCheckToWrite(Combodo\iTop\Service\Events\EventData $oEventData) { // Defensive programming, ensuring that 'status' is an existing field on the current class // then checking the condition: an enum value returns code, not label, so we test the code, if (MetaModel::IsValidAttCode(get_class($this), 'status') && ($this->Get('status') == 'production')) { // AttributeExternalKey are never NULL, O is the value used when empty if (MetaModel::IsValidAttCode(get_class($this), 'location_id') && ($this->Get('location_id') == 0)) { // 'Server:Error:LocationMandatoryInProduction' must be declared as a dictionary entry $this->AddCheckIssues(Dict::Format('Server:Error:LocationMandatoryInProduction')); } } }
// You may also provide a simple error message in plain text $this->AddCheckIssues('Location is mandatory for all Servers in production');
Here the way to define a dictionary entry in XML:
- itop_design / dictionaries / dictionary@EN US / entries
-
<entry id="Server:Error:LocationMandatoryInProduction" _delta="define"> <![CDATA['Location is mandatory for all Servers in production']]> </entry>