Our team uses iTop, and we have a need to create a new tab for a certain class (it is called RefreshAndReplacement).
We have been looking at Molkobain's datacenter view extension as an example. On racks and enclosures, this extension provides a new tab that allows you to see a visual representation of servers in racks and such. We figured this would be a good way to learn how to do this.
We start by creating a class that implements iApplicationUIExtension, and OnDisplayRelations we add an AjaxTab. This ajaxtab points to a ajax.render.php which handles setting the page output.
We are not sure about this pattern, might need to change it, but we are just trying to copy what Molkobain has in order to make a minimum viable product before refactoring.
This new tab shows up successfully, but upon clicking on it, we get a weird response from the server. It is not an error message - just a regular message in the view. It says :
"Unknown attribute nb_u from class RefreshAndReplacement"
The thing is, nowhere in our code do we reference nb_u. I know it has something to do with Molkobain's code, but I'm not too sure where to find where this message is coming from.
Here's some of the code:
in applicationuiextension.class.php, OnDisplayRelations:
<?phpuseContainerCrashers\iTop\Extension\DeploymentCreator\DeploymentCreatorFactory;/** @noinspection UsingInclusionOnceReturnValueInspection */@include_once'../approot.inc.php';@include_once'../../approot.inc.php';@include_once'../../../approot.inc.php';try{require_onceAPPROOT.'application/application.inc.php';require_onceAPPROOT.'/application/startup.inc.php';$oPage=newAjaxPage('');require_onceAPPROOT.'/application/user.preferences.class.inc.php';LoginWebPage::DoLoginEx('backoffice',false);$oPage->no_cache();$sOperation=utils::ReadParam('operation','');$sClass=utils::ReadParam('class','',false,'class');$iId=(int)utils::ReadParam('id',0);$oObject=MetaModel::GetObject($sClass,$iId);$oDeploymentCreatorView=DeploymentCreatorFactory::BuildFromObject($oObject);// Render tab$oPage->SetContentType('text/html');$oOutput=$oDeploymentCreatorView->Render();// HTML$oPage->add("We're inside the try.");$oPage->add($oOutput->GetHtml());// JS inlineif(!empty($oOutput->GetJs())){$oPage->add_ready_script($oOutput->GetJs());}// CSS inlineif(!empty($oOutput->GetCss())){$oPage->add_style($oOutput->GetCss());}$oPage->output();}catch(Exception$e){// Note: Transform to cope with XSS attacksechohtmlentities($e->GetMessage(),ENT_QUOTES,'utf-8');IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());}
Deploymentcreatorfactory.class.php:
<?phpnamespaceContainerCrashers\iTop\Extension\DeploymentCreator;//ContainerCrashers\iTop\Extension\DeploymentCreator\DeploymentCreatorFactory'useException;useDBObject;/***ClassDeploymentCreatorFactory**@packageContainerCrashers\iTop\Extension\DeploymentCreator*@since1.1.0*/classDeploymentCreatorFactory{constDEFAULT_DEPLOYMENT_CREATOR_VIEW_CLASS='ContainerCrashers\\iTop\\Extension\\DeploymentCreator\\DeploymentCreatorView';/**@varstring$sDeploymentCreatorClass*/protectedstatic$sDeploymentCreatorClass;/**@vararray$aCachedDeploymentCreatorViews*/protectedstatic$aCachedDeploymentCreatorViews=array();/***ReturnsaDeploymentCreatorViewof$oObjectbasedonthecurrentregisteredclass(static::$sDeploymentCreatorView)**Note:DeploymentCreatorViewobjectsarecachedinstatic::$aCachedDeploymentCreatorViews**@param\DBObject$oObject**@return\ContainerCrashers\iTop\Extension\DeploymentCreator\DeploymentCreatorView**@throws\Exception*/publicstaticfunctionBuildFromObject(DBObject$oObject){//Setdefaultclassifnoneif(empty(static::$sDeploymentCreatorClass)){static::$sDeploymentCreatorClass=static::DEFAULT_DEPLOYMENT_CREATOR_VIEW_CLASS;}//Checkifclassexistsif(!class_exists(static::$sDeploymentCreatorClass)){thrownewException('Could not make DeploymentCreatorView as "'.static::$sDeploymentCreatorClass.'" class does not exists.');}//Cacheview$sCacheKey=get_class($oObject).'::'.$oObject->GetKey();if(!array_key_exists($sCacheKey,static::$aCachedDeploymentCreatorViews)){static::$aCachedDeploymentCreatorViews[$sCacheKey]=newstatic::$sDeploymentCreatorClass($oObject);}returnstatic::$aCachedDeploymentCreatorViews[$sCacheKey];}/***RegistersthePHPclasstobuild.Mustbeaninstanceofstatic::DEFAULT_DEPLOYMENT_CREATOR_VIEW_CLASS.**@paramstring$sClassTheFQCNoftheclass**@throws\Exception*/publicstaticfunctionRegisterClass($sClass){//CheckifclassinstanceofDeploymentCreatorViewif(false===is_a($sClass,static::DEFAULT_DEPLOYMENT_CREATOR_VIEW_CLASS,true)){thrownewException('Could not register "'.$sClass.'" as DeploymentCreatorView class as it does not extends it.');}static::$sDeploymentCreatorClass=$sClass;}}
deploymentview.class.php
<?phpnamespaceContainerCrashers\iTop\Extension\DeploymentCreator;useDBObject;useutils;useCombodo\iTop\Renderer\RenderingOutput;useMolkobain\iTop\Extension\DatacenterView\Common\Helper\ConfigHelper;/** * Class DeploymentCreatorView * * @package ContainerCrashers\iTop\Extension\DeploymentCreator */classDeploymentCreatorView{constENUM_ENDPOINT_OPERATION_RENDERTAB='render_tab';constDEFAULT_OBJECT_IN_EDIT_MODE=false;/** @var string $sStaticConfigHelperClass */protected$sStaticConfigHelperClass;/** @var \DBObject $oObject */protected$oObject;/** @var string $sType */protected$sType;/** @var bool $bObjectInEditMode Is object in edition mode */protected$bObjectInEditMode;/** @var array $aOptions Current value of the options */protected$aOptions;publicfunction__construct(DBObject$oObject){$this->sStaticConfigHelperClass=static::GetStaticConfigHelperClass();$this->oObject=$oObject;$this->sType='refreshandreplacement';$this->bObjectInEditMode=static::DEFAULT_OBJECT_IN_EDIT_MODE;// Note: There is note static default value as array is not allowed before PHP 5.6$this->aOptions=array();}//--------------------// Getters / Setters//--------------------/** * @return \DBObject */publicfunctionGetObject(){return$this->oObject;}/** * Returns the object's type (see ENUM_ELEMENT_TYPE_XXX constants) * * @return string */publicfunctionGetType(){return$this->sType;}/** * Returns true if the object is in edition mode * * @return bool */publicfunctionIsObjectInEditMode(){return(bool)$this->bObjectInEditMode;}/** * Sets if the object is in edition mode * * @param bool $bObjectInEditMode If not passed, set to true automatically * * @return $this */publicfunctionSetObjectInEditMode($bObjectInEditMode=true){$this->bObjectInEditMode=(bool)$bObjectInEditMode;return$this;}//----------// Helpers//----------/** * Returns the endpoint url with optional $aParams. * Note: that object's class & id are always passed as parameters. * * @param array $aParams Array of key => value to pass in the endpoint as parameters * * @return string */publicfunctionGetEndpoint($aParams=array()){$aQueryStringParams=array('class='.$this->GetObjectClass(),'id='.$this->GetObjectId(),);foreach($aParamsas$sKey=>$sValue){$aQueryStringParams[]=$sKey.'='.$sValue;}returnutils::GetAbsoluteUrlModulesRoot().ConfigHelper::GetModuleCode().'/console/ajax.render.php?'.implode('&',$aQueryStringParams);}/** * @return string */publicfunctionGetObjectClass(){returnget_class($this->oObject);}/** * @return int */publicfunctionGetObjectId(){return$this->oObject->GetKey();}/** * Returns the whole view (legend, elements, ...) as fragments (HTML, JS files, JS inline, CSS files, CSS inline) * * @return \Combodo\iTop\Renderer\RenderingOutput * @throws \DictExceptionMissingString */publicfunctionRender(){$oOutput=newRenderingOutput();$oOutput->AddHtml(<<<HTML<div>HElloworld!</div>HTML);return$oOutput;}/** * Returns the FQCN of the ConfigHelper used by the current (static) class * * @return string */publicstaticfunctionGetStaticConfigHelperClass(){return'\\Molkobain\\iTop\\Extension\\DatacenterView\\Common\\Helper\\ConfigHelper';}}
If someone more experienced than me has any idea as to why this is happening, I would be very grateful if you could point me in the right direction :)
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
It depends on what you want to display in that tab. If it's something relatively simple, you can do something far more simple that this. The example you took had a lot of code and factorization, but it might be "too much" for displaying simple things.
Let us know what you want to display and we'll help you go through it.
Edit: I pasted you below an example of the minimum you need to make a ajax tab. Note that if you want to make a non-ajax tab, it's even simpler. But you should consider the computing time of the tab to decide if it should be ajax or not (meaning, does it have a lot to display?)
Guillaume
Last edit: Molkobain 2024-03-08
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
First of all, thanks so much for the quick reply. Your example provided makes a lot of sense and we are going to test it today.
Secondly, I had not considered the performance improvements of using a different type of tab. We are just trying to display a form on this page, so I am curious about what other types of tab we could use?
Edit: So after some trial and error today we managed to create a regular tab with the following code in our applicationuiextension:
This worked well. I do have one more question - but not sure if I should create a new thread for it or not. It has to do with rendering a field from the class FieldUIBlockFactory. If I create a new field with this class, it returns a UIBlock. But, AddToTab wants us to pass in an HTML string.
I was wondering, do we have to somehow convert the UIBlock to HTML, or do we have to use a different method to pass in a UIBlock instead of HTML?
Thanks a lot,
Jacques
Last edit: Jacques Dancause 2024-03-12
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
in applicationuiextension.class.php, OnDisplayRelations:
public function OnDisplayRelations($oObject, WebPage $oPage, $bEditMode = false) { if (!($oPage instanceof iTopWebPage)) {return; } // Remove this if you want the tab to display in the object creation form if ($oObject->IsNew()) { return; } // Add content in an async tab $sPreviousTab = $oPage->GetCurrentTab(); $oPage->AddAjaxTab( "Hello world", //Dict::S('Molkobain:DatacenterView:Tabs:View:Title'), utils::GetAbsoluteModulePath(<YOUR_MODULE_CODE_HERE>)."ajax.render.php?class=" . get_class($oObject) . "&id="&$oObject->GetKey()"; ); // Put tab cursor back to previous to make sure nothing breaks our tab (other extension for example) $oPage->SetCurrentTab($sPreviousTab); return; }
<?php/** @noinspection UsingInclusionOnceReturnValueInspection */@include_once'../approot.inc.php';@include_once'../../approot.inc.php';@include_once'../../../approot.inc.php';try{require_onceAPPROOT.'application/application.inc.php';require_onceAPPROOT.'/application/startup.inc.php';$oPage=newAjaxPage('');LoginWebPage::DoLoginEx('backoffice',false);$oPage->no_cache();// Operation is not passed yet in the URL, use it if you need it$sOperation=utils::ReadParam('operation','');$sClass=utils::ReadParam('class','',false,'class');$iId=(int)utils::ReadParam('id',0);$oObject=MetaModel::GetObject($sClass,$iId);// Do your business logic there// And build the HTML to return// Render tab$oPage->SetContentType('text/html');// HTML$oPage->add("ADD HTML HERE");$oPage->add(<<<HTMLYOU CAN ALSO ADD HTMLLIKE THAT ON SEVRAL LINESHTML);// JS inlineif(!empty($oOutput->GetJs())){$oPage->add_ready_script("alert('that is a ready script');");}// CSS inlineif(!empty($oOutput->GetCss())){$oPage->add_style(".some_css_rule {}");}$oPage->output();}catch(Exception$e){// Note: Transform to cope with XSS attacksechohtmlentities($e->GetMessage(),ENT_QUOTES,'utf-8');IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());}
👍
1
❤️
1
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Good day,
Our team uses iTop, and we have a need to create a new tab for a certain class (it is called RefreshAndReplacement).
We have been looking at Molkobain's datacenter view extension as an example. On racks and enclosures, this extension provides a new tab that allows you to see a visual representation of servers in racks and such. We figured this would be a good way to learn how to do this.
We start by creating a class that implements iApplicationUIExtension, and OnDisplayRelations we add an AjaxTab. This ajaxtab points to a ajax.render.php which handles setting the page output.
We are not sure about this pattern, might need to change it, but we are just trying to copy what Molkobain has in order to make a minimum viable product before refactoring.
This new tab shows up successfully, but upon clicking on it, we get a weird response from the server. It is not an error message - just a regular message in the view. It says :
"Unknown attribute nb_u from class RefreshAndReplacement"
The thing is, nowhere in our code do we reference nb_u. I know it has something to do with Molkobain's code, but I'm not too sure where to find where this message is coming from.
Here's some of the code:
in applicationuiextension.class.php, OnDisplayRelations:
ajax.render.php:
Deploymentcreatorfactory.class.php:
deploymentview.class.php
If someone more experienced than me has any idea as to why this is happening, I would be very grateful if you could point me in the right direction :)
Hello Jacques,
It depends on what you want to display in that tab. If it's something relatively simple, you can do something far more simple that this. The example you took had a lot of code and factorization, but it might be "too much" for displaying simple things.
Let us know what you want to display and we'll help you go through it.
Edit: I pasted you below an example of the minimum you need to make a ajax tab. Note that if you want to make a non-ajax tab, it's even simpler. But you should consider the computing time of the tab to decide if it should be ajax or not (meaning, does it have a lot to display?)
Guillaume
Last edit: Molkobain 2024-03-08
Hey Guillaume,
First of all, thanks so much for the quick reply. Your example provided makes a lot of sense and we are going to test it today.
Secondly, I had not considered the performance improvements of using a different type of tab. We are just trying to display a form on this page, so I am curious about what other types of tab we could use?
Edit: So after some trial and error today we managed to create a regular tab with the following code in our applicationuiextension:
This worked well. I do have one more question - but not sure if I should create a new thread for it or not. It has to do with rendering a field from the class FieldUIBlockFactory. If I create a new field with this class, it returns a UIBlock. But, AddToTab wants us to pass in an HTML string.
I was wondering, do we have to somehow convert the UIBlock to HTML, or do we have to use a different method to pass in a UIBlock instead of HTML?
Thanks a lot,
Jacques
Last edit: Jacques Dancause 2024-03-12
Here is a simplification
in applicationuiextension.class.php, OnDisplayRelations:
<your_module_code_here>/ajax.render.php</your_module_code_here>