N°2847 - Edit/Create objects

This commit is contained in:
Eric
2020-09-29 17:35:02 +02:00
parent 078f81e853
commit b8d71b2bfb
12 changed files with 205 additions and 160 deletions

View File

@@ -18,11 +18,17 @@
*/
use Combodo\iTop\Application\UI\Component\Alert\AlertFactory;
use Combodo\iTop\Application\UI\Component\Button\Button;
use Combodo\iTop\Application\UI\Component\Button\ButtonFactory;
use Combodo\iTop\Application\UI\Component\Field\Field;
use Combodo\iTop\Application\UI\Component\FieldSet\FieldSet;
use Combodo\iTop\Application\UI\Component\Form\Form;
use Combodo\iTop\Application\UI\Component\Input\InputFactory;
use Combodo\iTop\Application\UI\Component\Title\TitleFactory;
use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar;
use Combodo\iTop\Application\UI\Layout\Column\Column;
use Combodo\iTop\Application\UI\Layout\MultiColumn\MultiColumn;
use Combodo\iTop\Application\UI\Layout\UIContentBlock;
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
@@ -2511,8 +2517,14 @@ JS
}
}
if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container'])
{
$oContentBlock = new UIContentBlock();
$oContentBlock->SetCSSClasses("object-details")
->AddDataAttributes('object-class', $sClass)
->AddDataAttributes('object-id', $iKey)
->AddDataAttributes('object-mode', $sMode);
$oPage->AddUiBlock($oContentBlock);
if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) {
$sClassLabel = MetaModel::GetName($sClass);
$sHeaderTitle = Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel,
$this->GetName());
@@ -2520,97 +2532,87 @@ JS
$oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $this->GetRawName(),
$sClassLabel)); // Set title will take care of the encoding
$oPage->add(<<<HTML
<!-- Beginning of object-details -->
<div class="object-details" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode">
<div class="page_header">
<h1>{$this->GetIcon()} $sHeaderTitle</h1>
</div>
<!-- Beginning of wizContainer -->
<div class="wizContainer">
HTML
);
$oContentBlock->AddSubBlock(TitleFactory::MakeForObjectDetails('', $sHeaderTitle, $this->GetIcon()));
// $oPage->add(<<<HTML
//<!-- Beginning of object-details -->
//<div class="object-details" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode">
// <div class="page_header">
// <h1>{$this->GetIcon()} $sHeaderTitle</h1>
// </div>
// <!-- Beginning of wizContainer -->
// <div class="wizContainer">
//HTML
// );
}
self::$iGlobalFormId++;
$this->aFieldsMap = array();
$sPrefix = '';
if (isset($aExtraParams['formPrefix']))
{
if (isset($aExtraParams['formPrefix'])) {
$sPrefix = $aExtraParams['formPrefix'];
}
$this->m_iFormId = $sPrefix.self::$iGlobalFormId;
$oAppContext = new ApplicationContext();
if (!isset($aExtraParams['action']))
{
if (!isset($aExtraParams['action'])) {
$sFormAction = utils::GetAbsoluteUrlAppRoot().'pages/'.$this->GetUIPage(); // No parameter in the URL, the only parameter will be the ones passed through the form
}
else
{
} else {
$sFormAction = $aExtraParams['action'];
}
// Custom label for the apply button ?
if (isset($aExtraParams['custom_button']))
{
if (isset($aExtraParams['custom_button'])) {
$sApplyButton = $aExtraParams['custom_button'];
}
else
{
if ($sMode === static::ENUM_OBJECT_MODE_EDIT)
{
} else {
if ($sMode === static::ENUM_OBJECT_MODE_EDIT) {
$sApplyButton = Dict::S('UI:Button:Apply');
}
else
{
} else {
$sApplyButton = Dict::S('UI:Button:Create');
}
}
// Custom operation for the form ?
if (isset($aExtraParams['custom_operation']))
{
if (isset($aExtraParams['custom_operation'])) {
$sOperation = $aExtraParams['custom_operation'];
}
else
{
if ($sMode === static::ENUM_OBJECT_MODE_EDIT)
{
} else {
if ($sMode === static::ENUM_OBJECT_MODE_EDIT) {
$sOperation = 'apply_modify';
}
else
{
} else {
$sOperation = 'apply_new';
}
}
if ($sMode === static::ENUM_OBJECT_MODE_EDIT)
{
$oForm = new Form("form_{$this->m_iFormId}");
$oForm->SetAction($sFormAction)
->SetOnSubmitJsCode("return OnSubmit('form_{$this->m_iFormId}');");
$oContentBlock->AddSubBlock($oForm);
$oToolbarTop = new Toolbar();
if ($sMode === static::ENUM_OBJECT_MODE_EDIT) {
// The object already exists in the database, it's a modification
$sButtons = "<input id=\"{$sPrefix}_id\" type=\"hidden\" name=\"id\" value=\"$iKey\">\n";
$sButtons .= "<input type=\"hidden\" name=\"operation\" value=\"{$sOperation}\">\n";
$sButtons .= "<button type=\"button\" class=\"action cancel\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n";
$sButtons .= "<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n";
}
else
{
// The object does not exist in the database it's a creation
$sButtons = "<input type=\"hidden\" name=\"operation\" value=\"$sOperation\">\n";
$sButtons .= "<button type=\"button\" class=\"action cancel\">".Dict::S('UI:Button:Cancel')."</button>&nbsp;&nbsp;&nbsp;&nbsp;\n";
$sButtons .= "<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n";
$oForm->AddSubBlock(InputFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id"));
}
$oForm->AddSubBlock(InputFactory::MakeForHidden('operation', $sOperation));
$oCancelButton = ButtonFactory::MakeForSecondaryAction(Dict::S('UI:Button:Cancel'));
$oCancelButton->AddCSSClasses('action cancel');
$oToolbarTop->AddSubBlock($oCancelButton);
$oApplyButton = ButtonFactory::MakeForValidationAction($sApplyButton, null, null, true);
$oApplyButton->AddCSSClasses('action');
$oToolbarTop->AddSubBlock($oApplyButton);
$aTransitions = $this->EnumTransitions();
if (!isset($aExtraParams['custom_operation']) && count($aTransitions))
{
if (!isset($aExtraParams['custom_operation']) && count($aTransitions)) {
// transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
$oSetToCheckRights = DBObjectSet::FromObject($this);
$aStimuli = Metamodel::EnumStimuli($sClass);
foreach($aTransitions as $sStimulusCode => $aTransitionDef)
{
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
$sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
switch ($iActionAllowed)
{
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
$sButtons .= "<button type=\"submit\" name=\"next_action\" value=\"{$sStimulusCode}\" class=\"action\"><span>".$aStimuli[$sStimulusCode]->GetLabel()."</span></button>\n";
$oButton = ButtonFactory::MakeForValidationAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClasses('action');
$oButton->SetColor(Button::ENUM_COLOR_NEUTRAL);
$oToolbarTop->AddSubBlock($oButton);
break;
default:
@@ -2622,7 +2624,6 @@ HTML
$sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position');
$iTransactionId = isset($aExtraParams['transaction_id']) ? $aExtraParams['transaction_id'] : utils::GetNewTransactionId();
$oPage->SetTransactionId($iTransactionId);
$oPage->add("<form action=\"$sFormAction\" id=\"form_{$this->m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return OnSubmit('form_{$this->m_iFormId}');\">\n");
$sStatesSelection = '';
if (!isset($aExtraParams['custom_operation']) && $this->IsNew())
{
@@ -2672,70 +2673,54 @@ JAVASCRIPT
EOF
);
if ($sButtonsPosition != 'bottom')
{
if ($sButtonsPosition != 'bottom') {
// top or both, display the buttons here
$oPage->p($sStatesSelection);
$oPage->add($sButtons);
$oForm->AddSubBlock($oToolbarTop);
}
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, $sPrefix);
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, $sPrefix, $oForm);
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTab('UI:PropertiesTab');
$aFieldsMap = $this->DisplayBareProperties($oPage, true, $sPrefix, $aExtraParams);
if (!is_array($aFieldsMap))
{
if (!is_array($aFieldsMap)) {
$aFieldsMap = array();
}
if ($sMode === static::ENUM_OBJECT_MODE_EDIT)
{
if ($sMode === static::ENUM_OBJECT_MODE_EDIT) {
$aFieldsMap['id'] = $sPrefix.'_id';
}
// Now display the relations, one tab per relation
if (!isset($aExtraParams['noRelations']))
{
if (!isset($aExtraParams['noRelations'])) {
$this->DisplayBareRelations($oPage, true); // Edit mode, will fill $this->aFieldsMap
$aFieldsMap = array_merge($aFieldsMap, $this->aFieldsMap);
}
$oPage->SetCurrentTab('');
$oPage->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n");
$oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"$iTransactionId\">\n");
foreach($aExtraParams as $sName => $value)
{
if (is_scalar($value))
{
$oPage->add("<input type=\"hidden\" name=\"$sName\" value=\"$value\">\n");
$oForm->AddSubBlock(InputFactory::MakeForHidden('class', $sClass));
$oForm->AddSubBlock(InputFactory::MakeForHidden('transaction_id', $iTransactionId));
foreach ($aExtraParams as $sName => $value) {
if (is_scalar($value)) {
$oForm->AddSubBlock(InputFactory::MakeForHidden($sName, $value));
}
}
if ($sOwnershipToken !== null)
{
$oPage->add("<input type=\"hidden\" name=\"ownership_token\" value=\"".htmlentities($sOwnershipToken,
ENT_QUOTES, 'UTF-8')."\">\n");
if ($sOwnershipToken !== null) {
$oForm->AddSubBlock(InputFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
}
$oPage->add($oAppContext->GetForForm());
if ($sButtonsPosition != 'top')
{
if ($sButtonsPosition != 'top') {
// bottom or both: display the buttons here
$oPage->p($sStatesSelection);
$oPage->add($sButtons);
$oToolbarBottom = new Toolbar();
foreach ($oToolbarTop->GetSubBlocks() as $oButton) {
$oToolbarBottom->AddSubBlock($oButton);
}
$oForm->AddSubBlock($oToolbarBottom);
}
// Hook the cancel button via jQuery so that it can be unhooked easily as well if needed
$sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=cancel&'.$oAppContext->GetForLink();
$oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').click( function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)} );");
$oPage->add("</form>\n");
if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container'])
{
// Close wizContainer and object-details
$oPage->add(<<<HTML
</div><!-- End of wizContainer -->
</div><!-- End of object-details -->
HTML
);
}
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);

View File

@@ -461,7 +461,7 @@ try
$oP->add_saas('env-'.utils::GetCurrentEnvironment().'/combodo-db-tools/default.scss');
$oP->add(
<<<EOF
<<<EOF
<div class="page_header">
<h1>$sPageTitle</h1>
</div>

View File

@@ -1906,45 +1906,38 @@ EOF
$sDescription = MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName();
$oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription);
if ($sRelation == 'depends on')
{
$sRelation = 'impacts';
$sDirection = 'up';
}
if ($sDirection == 'up')
{
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
}
else
{
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth);
}
if ($sRelation == 'depends on') {
$sRelation = 'impacts';
$sDirection = 'up';
}
if ($sDirection == 'up') {
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
} else {
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth);
}
$aResults = $oRelGraph->GetObjectsByClass();
$oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
$aResults = $oRelGraph->GetObjectsByClass();
$oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
$oP->AddTabContainer('Navigator');
$oP->SetCurrentTabContainer('Navigator');
$oP->AddTabContainer('Navigator');
$oP->SetCurrentTabContainer('Navigator');
$sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab');
$sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection";
$sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab');
$sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection";
// Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject
$sClassForAttachment = null;
$iIdForAttachment = null;
if (class_exists('Attachment'))
{
$aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
foreach($aAllowedClasses as $sAllowedClass)
{
if ($oObj instanceof $sAllowedClass)
{
$iIdForAttachment = $id;
$sClassForAttachment = $sClass;
// Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject
$sClassForAttachment = null;
$iIdForAttachment = null;
if (class_exists('Attachment')) {
$aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
foreach ($aAllowedClasses as $sAllowedClass) {
if ($oObj instanceof $sAllowedClass) {
$iIdForAttachment = $id;
$sClassForAttachment = $sClass;
}
}
}
}
// Display the tabs
if ($sFirstTab == 'list')

View File

@@ -1278,17 +1278,14 @@ EOF
function Welcome(iTopWebPage $oPage)
{
$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
if (!empty($sSynchroScope))
{
if (!empty($sSynchroScope)) {
$oSearch = DBObjectSearch::FromOQL($sSynchroScope);
$sClassName = $oSearch->GetClass();
$oSet = new DBObjectSet($oSearch);
$iCount = $oSet->Count();
DisplaySynchroBanner($oPage, $sClassName, $iCount);
$aSynchroUpdate = utils::ReadParam('synchro_update', array());
}
else
{
} else {
$aSynchroUpdate = null;
}

View File

@@ -851,20 +851,15 @@ function DisplayClassDetails($oPage, $sClass, $sContext)
$aDetails = array();
$aOrigins = array();
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalKey())
{
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
if ($oAttDef->IsExternalKey()) {
$sValue = Dict::Format('UI:Schema:ExternalKey_To', MakeClassHLink($oAttDef->GetTargetClass(), $sContext));
if (array_key_exists($sAttCode, $aForwardChangeTracking))
{
if (array_key_exists($sAttCode, $aForwardChangeTracking)) {
$oLinkSet = $aForwardChangeTracking[$sAttCode];
$sRemoteClass = $oLinkSet->GetHostClass();
$sValue = $sValue."<span title=\"Forward changes to $sRemoteClass\">*</span>";
}
}
elseif ($oAttDef->IsLinkSet())
{
} elseif ($oAttDef->IsLinkSet()) {
$sValue = MakeClassHLink($oAttDef->GetLinkedClass(), $sContext);
}
else

View File

@@ -22,16 +22,20 @@ class Form extends UIContentBlock
/** @var string */
protected $sOnSubmitJsCode;
/** @var string */
protected $sAction;
public function __construct(string $sName = null)
{
parent::__construct($sName);
$this->sOnSubmitJsCode = null;
$this->sAction = null;
}
public function SetOnSubmitJsCode(string $sJsCode): void
public function SetOnSubmitJsCode(string $sJsCode): Form
{
$this->sOnSubmitJsCode = $sJsCode;
return $this;
}
/**
@@ -42,5 +46,23 @@ class Form extends UIContentBlock
return $this->sOnSubmitJsCode;
}
/**
* @return string
*/
public function GetAction(): string
{
return $this->sAction;
}
/**
* @param string $sAction
*
* @return Form
*/
public function SetAction(string $sAction): Form
{
$this->sAction = $sAction;
return $this;
}
}

View File

@@ -22,6 +22,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock
protected $aCSSClasses;
/** @var array */
protected $aSubBlocks;
/** @var array */
protected $aDataAttributes;
/**
* UIContentBlock constructor.
@@ -34,6 +36,7 @@ class UIContentBlock extends UIBlock implements iUIContentBlock
parent::__construct($sName);
$this->aSubBlocks = [];
$this->aDataAttributes = [];
$this->SetCSSClasses($sContainerClass);
}
@@ -150,4 +153,36 @@ class UIContentBlock extends UIBlock implements iUIContentBlock
return $this;
}
/**
* @return array
*/
public function GetDataAttributes(): array
{
return $this->aDataAttributes;
}
/**
* @param array $aDataAttributes
*
* @return UIContentBlock
*/
public function SetDataAttributes(array $aDataAttributes): UIContentBlock
{
$this->aDataAttributes = $aDataAttributes;
return $this;
}
/**
* @param string $sName
* @param string $sValue
*
* @return UIContentBlock
*/
public function AddDataAttributes(string $sName, string $sValue): UIContentBlock
{
$this->aDataAttributes[$sName] = $sValue;
return $this;
}
}

View File

@@ -6,6 +6,7 @@
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\Layout\iUIContentBlock;
use Combodo\iTop\Renderer\BlockRenderer;
class AjaxPage extends WebPage implements iTabbedPage
@@ -46,7 +47,7 @@ class AjaxPage extends WebPage implements iTabbedPage
* @inheritDoc
* @throws \Exception
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
public function AddTabContainer($sTabContainer, $sPrefix = '', iUIContentBlock $oParentBlock = null)
{
$this->AddUiBlock($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
}

View File

@@ -1,19 +1,22 @@
<?php
use Combodo\iTop\Application\UI\Layout\iUIContentBlock;
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
interface iTabbedPage
{
/**
* @param string $sTabContainer
* @param string $sPrefix
*
* @param \Combodo\iTop\Application\UI\Layout\iUIContentBlock|null $oParentBlock
*
* @return mixed
*/
public function AddTabContainer($sTabContainer, $sPrefix = '');
public function AddTabContainer($sTabContainer, $sPrefix = '', iUIContentBlock $oParentBlock = null);
/**
* @param string $sTabContainer

View File

@@ -21,6 +21,7 @@
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\Component\Panel\Panel;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\Layout\iUIContentBlock;
use Combodo\iTop\Application\UI\Layout\NavigationMenu\NavigationMenuFactory;
use Combodo\iTop\Application\UI\Layout\PageContent\PageContent;
use Combodo\iTop\Application\UI\Layout\PageContent\PageContentFactory;
@@ -1228,13 +1229,17 @@ EOF;
* @inheritDoc
* @throws \Exception
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
public function AddTabContainer($sTabContainer, $sPrefix = '', iUIContentBlock $oParentBlock = null)
{
$oPanel = new Panel('');
// TODO 2.8.0 Change color according to object
$oPanel->SetColor(Panel::ENUM_COLOR_BLUE);
$oPanel->AddSubBlock($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
$this->AddUiBlock($oPanel);
if (!is_null($oParentBlock)) {
$oParentBlock->AddSubBlock($oPanel);
} else {
$this->AddUiBlock($oPanel);
}
}
/**

View File

@@ -1,4 +1,6 @@
<form method="post"{% if oUIBlock.GetOnSubmitJsCode() %} {{ oUIBlock.GetOnSubmitJsCode() }}{% endif %}>
<form method="post" enctype="multipart/form-data" id="{{ oUIBlock.GetId() }}"
{% if oUIBlock.GetOnSubmitJsCode() %}onSubmit="{{ oUIBlock.GetOnSubmitJsCode() }}"{% endif %}
{% if oUIBlock.GetAction() %}action="{{ oUIBlock.GetAction() }}"{% endif %}>
{% apply spaceless %}
{% block iboContentBlockContainer %}
{% for oSubBlock in oUIBlock.GetSubBlocks() %}

View File

@@ -1,8 +1,15 @@
{% apply spaceless %}
{% block iboContentBlockContainer %}
{% if oUIBlock.GetCSSClasses() %}
<div id="{{ oUIBlock.GetId() }}" class="{{ oUIBlock.GetCSSClasses() }}">
{% if oUIBlock.GetCSSClasses() or oUIBlock.GetDataAttributes() %}
<div id="{{ oUIBlock.GetId() }}"
{% if oUIBlock.GetCSSClasses() %}class="{{ oUIBlock.GetCSSClasses() }}"{% endif %}
{% if oUIBlock.GetDataAttributes() %}
{% for sName, sValue in oUIBlock.GetDataAttributes() %}
data-{{ sName }}="{{ sValue }}"
{% endfor %}
{% endif %}
>
{% endif %}
{% for oSubBlock in oUIBlock.GetSubBlocks() %}