N°803 - Allow display & edition of attributes on n:n relations on Portal

This commit is contained in:
Benjamin Dalsass
2023-06-07 15:30:08 +02:00
parent d778203f99
commit 11c147574a
8 changed files with 184 additions and 80 deletions

View File

@@ -2416,23 +2416,25 @@ class AttributeLinkedSet extends AttributeDefinition
foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) {
$oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
$aAttributesToDisplay[$sAttCodeToDisplay] = [
'label' => $oAttDefToDisplay->GetLabel(),
'mandatory' => !$oAttDefToDisplay->IsNullAllowed(),
'att_code' => $sAttCodeToDisplay,
'label' => $oAttDefToDisplay->GetLabel(),
];
}
$oFormField->SetAttributesToDisplay($aAttributesToDisplay);
// Append lnk attributes (filtered from zlist)
$aLnkAttDefToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($this->m_sHostClass, $this->m_sCode);
$aLnkAttributesToDisplay = array();
foreach ($aLnkAttDefToDisplay as $oLnkAttDefToDisplay) {
$aLnkAttributesToDisplay[$oLnkAttDefToDisplay->GetCode()] = [
'sortable' => false,
'label' => $oLnkAttDefToDisplay->GetLabel(),
'mandatory' => !$oLnkAttDefToDisplay->IsNullAllowed(),
];
if ($this->IsIndirect()) {
$aLnkAttDefToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($this->m_sHostClass, $this->m_sCode);
$aLnkAttributesToDisplay = array();
foreach ($aLnkAttDefToDisplay as $oLnkAttDefToDisplay) {
$aLnkAttributesToDisplay[$oLnkAttDefToDisplay->GetCode()] = [
'att_code' => $oLnkAttDefToDisplay->GetCode(),
'label' => $oLnkAttDefToDisplay->GetLabel(),
'mandatory' => !$oLnkAttDefToDisplay->IsNullAllowed(),
];
}
$oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay);
}
$oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay);
parent::MakeFormField($oObject, $oFormField);
@@ -3136,7 +3138,7 @@ class AttributeDecimal extends AttributeDBField
$iPrecision = $this->Get('decimals');
$iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below
return "^[-+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$";
return "^[\-\+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$";
}
public function GetBasicFilterOperators()

File diff suppressed because one or more lines are too long

View File

@@ -1808,16 +1808,31 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
/**********************************************************/
/* Hide attributes label in link set edition, will be fixed during attributes refactoring */
.form_linkedset_wrapper label {
.form_linkedset_wrapper .form_field_label {
display: none;
}
.form_linkedset_wrapper .form_field_control {
width: 100%!important;
}
/* Add mandatory field column label */
.form_linkedset_wrapper .dataTables_scrollHead th.mandatory:after {
content: "*";
position: relative;
left: 3px;
color: #EA7D1E;
color: $combodo-orange;
font-size: 0.9em;
}
/* Add style for invalid input */
.form_linkedset_wrapper input:invalid {
border-color: $state-danger-border;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.form_linkedset_wrapper input:invalid:focus {
border-color: darken($state-danger-border, 10);
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px lighten($state-danger-border, 10);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px lighten($state-danger-border, 10);
}

View File

@@ -103,6 +103,12 @@ $(function()
oEvent.preventDefault();
var me = this;
// Prevent form submit if input in invalid state
// @see Bug N°803 - Allow display & edition of attributes on n:n relations on Portal
if($('input:invalid', this.element).length > 0){
return;
}
// EasterEgg : Vibrate on submit
if(window.navigator.vibrate)
{

View File

@@ -1365,7 +1365,7 @@ class ObjectController extends BrickController
$oFieldOutput = $oFieldRenderer->Render();
$sValue = $oFieldOutput->GetHtml();
}
$aObjectData['attributes'][$sAttCode] = [
$aObjectData['attributes']['lnk__'.$sAttCode] = [
'att_code' => $sAttCode,
'value' => $sValue,
'css_inline' => $oFieldOutput->GetCss(),

View File

@@ -861,8 +861,8 @@ class ObjectFormManager extends FormManager
{
$oAttDefToDisplay = MetaModel::GetAttributeDef($oField->GetTargetClass(), $sAttCodeToDisplay);
$aAttributesToDisplay[$sAttCodeToDisplay] = [
'label' => $oAttDefToDisplay->GetLabel(),
'mandatory' => !$oAttDefToDisplay->IsNullAllowed(),
'att_code' => $sAttCodeToDisplay,
'label' => $oAttDefToDisplay->GetLabel(),
];
}
$oField->SetAttributesToDisplay($aAttributesToDisplay);

View File

@@ -53,16 +53,17 @@ class BsLinkedSetFieldRenderer extends BsFieldRenderer
$sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : '';
$sFieldDescriptionForHTMLTag = ($this->oField->HasDescription()) ? 'data-tooltip-content="'.utils::HtmlEntities($this->oField->GetDescription()).'"' : '';
// Merge lnk and remote class attributes to display
$aAttributesToDisplay = array_merge($this->oField->GetLnkAttributesToDisplay(), $this->oField->GetAttributesToDisplay());
// Retrieve link and remote attributes
$aAttributesToDisplay = $this->oField->GetAttributesToDisplay();
$aLnkAttributesToDisplay = $this->oField->GetLnkAttributesToDisplay();
$iLinkAttributesToDisplayCount = count($this->oField->GetLnkAttributesToDisplay()) + 1;
// Vars to build the table
$sAttributesToDisplayAsJson = json_encode($aAttributesToDisplay);
$sLnkAttributesToDisplayAsJson = json_encode($aLnkAttributesToDisplay);
$sAttCodesToDisplayAsJson = json_encode($this->oField->GetAttributesToDisplay(true));
$sLnkAttCodesToDisplayAsJson = json_encode($this->oField->GetLnkAttributesToDisplay(true));
$aItems = array();
$aItemIds = array();
$aAddedItemIds = array();
@@ -161,6 +162,7 @@ EOF
$('#{$sTableId} > tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
// Prepares data for datatables
var oLnkColumnProperties_{$this->oField->GetGlobalId()} = {$sLnkAttributesToDisplayAsJson};
var oColumnProperties_{$this->oField->GetGlobalId()} = {$sAttributesToDisplayAsJson};
var oRawDatas_{$this->oField->GetGlobalId()} = {$sItemsAsJson};
var oTable_{$this->oField->GetGlobalId()};
@@ -196,33 +198,68 @@ EOF
});
}
for(sKey in oColumnProperties_{$this->oField->GetGlobalId()})
for(sKey in oLnkColumnProperties_{$this->oField->GetGlobalId()})
{
aColumnProperties = oColumnProperties_{$this->oField->GetGlobalId()}[sKey];
aColumnProperties = oLnkColumnProperties_{$this->oField->GetGlobalId()}[sKey];
// Level main column
aColumnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": !aColumnProperties.sortable,
"sortable": false,
"title": aColumnProperties.label,
"defaultContent": "",
"type": "html",
"data": "attributes."+sKey+".att_code",
"data": "attributes.lnk__" + sKey,
"className": aColumnProperties.mandatory ? 'mandatory' : '',
"render": function(data, type, row){
var cellElem;
// Preparing the cell data
if(row.attributes[data].url !== undefined)
if(data.url !== undefined)
{
cellElem = $('<a></a>');
cellElem.attr('href', row.attributes[data].url);
cellElem.attr('href', data.url);
}
else
{
cellElem = $('<span></span>');
}
cellElem.html('<span>' + row.attributes[data].value + '</span>');
cellElem.html('<span>' + data.value + '</span>');
return cellElem.prop('outerHTML');
},
});
}
for(sKey in oColumnProperties_{$this->oField->GetGlobalId()})
{
aColumnProperties = oColumnProperties_{$this->oField->GetGlobalId()}[sKey];
// Level main column
aColumnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": true,
"title": aColumnProperties.label,
"defaultContent": "",
"type": "html",
"data": "attributes." + sKey,
"className": aColumnProperties.mandatory ? 'mandatory' : '',
"render": function(data, type, row){
var cellElem;
// Preparing the cell data
if(data.url !== undefined)
{
cellElem = $('<a></a>');
cellElem.attr('href', data.url);
}
else
{
cellElem = $('<span></span>');
}
cellElem.html('<span>' + data.value + '</span>');
return cellElem.prop('outerHTML');
},
@@ -445,9 +482,12 @@ JS
oTable_{$this->oField->GetGlobalId()}.draw();
// Execute inline js for each attributes renderers
for(key in oData.items[i].attributes){
eval(oData.items[i].attributes[key].js_inline)
}
for(let i in oData.items)
{
for(let key in oData.items[i].attributes){
eval(oData.items[i].attributes[key].js_inline)
}
}
// Updating input
updateInputValue_{$this->oField->GetGlobalId()}();
@@ -663,10 +703,10 @@ JS
);
// Link attributes to display
$this->PrepareItem($oItem, $this->oField->GetLinkedClass(), $this->oField->GetLnkAttributesToDisplay(true), true, $aItemProperties, $oOutput);
$this->PrepareItem($oItem, $this->oField->GetLinkedClass(), $this->oField->GetLnkAttributesToDisplay(true), true, $aItemProperties, 'lnk__');
// Remote attributes to display
$this->PrepareItem($oRemoteItem, $this->oField->GetTargetClass(), $this->oField->GetAttributesToDisplay(true), false, $aItemProperties, $oOutput);
$this->PrepareItem($oRemoteItem, $this->oField->GetTargetClass(), $this->oField->GetAttributesToDisplay(true), false, $aItemProperties);
// Remap objects to avoid added item to be considered as current item when form validation isn't valid
// and form reconstruct
@@ -725,7 +765,7 @@ JS
* @throws \ArchivedObjectException
* @throws \CoreException
*/
protected function PrepareItem(DBObject $oItem, string $sClass, array $aAttributesCodesToDisplay, bool $bIsEditable, array &$aItemProperties, $oOutput)
protected function PrepareItem(DBObject $oItem, string $sClass, array $aAttributesCodesToDisplay, bool $bIsEditable, array &$aItemProperties, string $sAttribueKeyPrefix = '')
{
// Iterate throw attributes...
foreach ($aAttributesCodesToDisplay as $sAttCode) {
@@ -780,7 +820,8 @@ JS
}
}
$aItemProperties['attributes'][$sAttCode] = $aAttProperties;
$aItemProperties['attributes'][$sAttribueKeyPrefix.$sAttCode] = $aAttProperties;
}
}
}

View File

@@ -23,8 +23,13 @@ namespace Combodo\iTop\Renderer\Bootstrap\FieldRenderer;
use AttributeDate;
use AttributeDateTime;
use AttributeText;
use Combodo\iTop\Form\Field\DateField;
use Combodo\iTop\Form\Field\DateTimeField;
use Combodo\iTop\Form\Field\Field;
use Combodo\iTop\Form\Field\MultipleChoicesField;
use Combodo\iTop\Form\Field\TextAreaField;
use Combodo\iTop\Form\Validator\MandatoryValidator;
use Combodo\iTop\Form\Validator\Validator;
use Combodo\iTop\Renderer\RenderingOutput;
use Dict;
use InlineImage;
@@ -63,57 +68,62 @@ class BsSimpleFieldRenderer extends BsFieldRenderer
case 'Combodo\\iTop\\Form\\Field\\SelectField':
case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
// Opening container
$oOutput->AddHtml('<div class="form-group form_group_small ' . $sFieldMandatoryClass . '">');
$oOutput->AddHtml('<div class="form-group form_group_small '.$sFieldMandatoryClass.'">');
// Label
$oOutput->AddHtml('<div class="form_field_label">');
if ($this->oField->GetLabel() !== '') {
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label" '.$sFieldDescriptionForHTMLTag.'>')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
}
$oOutput->AddHtml('</div>');
// Label
$oOutput->AddHtml('<div class="form_field_label">');
if ($this->oField->GetLabel() !== '') {
$oOutput->AddHtml('<label for="'.$this->oField->GetGlobalId().'" class="control-label" '.$sFieldDescriptionForHTMLTag.'>')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
}
$oOutput->AddHtml('</div>');
// Value
$oOutput->AddHtml('<div class="form_field_control">');
// - Help block
$oOutput->AddHtml('<div class="help-block"></div>');
// - Value regarding the field type
switch($sFieldClass) {
case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
$oOutput->AddHtml('<div class="input-group date" id="datepicker_' . $this->oField->GetGlobalId() . '">');
$oOutput->AddHtml('<input type="text" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('" class="form-control" maxlength="255" />');
$oOutput->AddHtml('<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>');
$oOutput->AddHtml('</div>');
$sJSFormat = json_encode($this->oField->GetJSDateTimeFormat());
$sLocale = Dict::S('Portal:Calendar-FirstDayOfWeek');
$oOutput->AddJs(
<<<EOF
// Value
$oOutput->AddHtml('<div class="form_field_control">');
// - Help block
$oOutput->AddHtml('<div class="help-block"></div>');
// Prepare input validations tags
$sInputTags = $this->ComputeInputValidationTags($this->oField);
// - Value regarding the field type
switch ($sFieldClass) {
case 'Combodo\\iTop\\Form\\Field\\DateTimeField':
$oOutput->AddHtml('<div class="input-group date" id="datepicker_'.$this->oField->GetGlobalId().'">');
$oOutput->AddHtml('<input type="text" id="'.$this->oField->GetGlobalId().'" name="'.$this->oField->GetId().'" value="')->AddHtml($this->oField->GetDisplayValue(), true)->AddHtml('" class="form-control" maxlength="255" '.$sInputTags.'/>');
$oOutput->AddHtml('<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>');
$oOutput->AddHtml('</div>');
$sJSFormat = json_encode($this->oField->GetJSDateTimeFormat());
$sLocale = Dict::S('Portal:Calendar-FirstDayOfWeek');
$oOutput->AddJs(
<<<EOF
$('#datepicker_{$this->oField->GetGlobalId()}').datetimepicker({format: $sJSFormat, locale: '$sLocale'});
EOF
);
break;
);
break;
case 'Combodo\\iTop\\Form\\Field\\PasswordField':
$oOutput->AddHtml('<input type="password" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" maxlength="255" autocomplete="off" />');
break;
case 'Combodo\\iTop\\Form\\Field\\PasswordField':
$oOutput->AddHtml('<input type="password" id="'.$this->oField->GetGlobalId().'" name="'.$this->oField->GetId().'" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" maxlength="255" autocomplete="off" />');
break;
case 'Combodo\\iTop\\Form\\Field\\StringField':
case 'Combodo\\iTop\\Form\\Field\\UrlField':
case 'Combodo\\iTop\\Form\\Field\\EmailField':
case 'Combodo\\iTop\\Form\\Field\\PhoneField':
$oOutput->AddHtml('<input type="text" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')->AddHtml($this->oField->GetCurrentValue(), true)->AddHtml('" class="form-control" maxlength="255" />');
break;
case 'Combodo\\iTop\\Form\\Field\\StringField':
case 'Combodo\\iTop\\Form\\Field\\UrlField':
case 'Combodo\\iTop\\Form\\Field\\EmailField':
case 'Combodo\\iTop\\Form\\Field\\PhoneField':
$oOutput->AddHtml('<input type="text" id="'.$this->oField->GetGlobalId().'" name="'.$this->oField->GetId().'" value="')->AddHtml($this->oField->GetCurrentValue(),
true)->AddHtml('" class="form-control" maxlength="255" '.$sInputTags.'/>');
break;
case 'Combodo\\iTop\\Form\\Field\\SelectField':
case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
$oOutput->AddHtml('<select id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" ' . ( ($this->oField->GetMultipleValuesEnabled()) ? 'multiple' : '' ) . ' class="form-control">');
foreach ($this->oField->GetChoices() as $sChoice => $sLabel) {
// Note : The test is a double equal on purpose as the type of the value received from the XHR is not always the same as the type of the allowed values. (eg : string vs int)
$sSelectedAtt = ($this->oField->GetCurrentValue() == $sChoice) ? 'selected' : '';
$oOutput->AddHtml('<option value="' . $sChoice . '" ' . $sSelectedAtt . ' >')->AddHtml($sLabel)->AddHtml('</option>');
}
$oOutput->AddHtml('</select>');
break;
}
case 'Combodo\\iTop\\Form\\Field\\SelectField':
case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
$oOutput->AddHtml('<select id="'.$this->oField->GetGlobalId().'" name="'.$this->oField->GetId().'" '.(($this->oField->GetMultipleValuesEnabled()) ? 'multiple' : '').' class="form-control">');
foreach ($this->oField->GetChoices() as $sChoice => $sLabel) {
// Note : The test is a double equal on purpose as the type of the value received from the XHR is not always the same as the type of the allowed values. (eg : string vs int)
$sSelectedAtt = ($this->oField->GetCurrentValue() == $sChoice) ? 'selected' : '';
$oOutput->AddHtml('<option value="'.$sChoice.'" '.$sSelectedAtt.' >')->AddHtml($sLabel)->AddHtml('</option>');
}
$oOutput->AddHtml('</select>');
break;
}
$oOutput->AddHtml('</div>');
// Closing container
@@ -725,4 +735,34 @@ JS
}
}
/**
* @param \Combodo\iTop\Form\Field\Field $oField
*
* @return string
*/
private function ComputeInputValidationTags(Field $oField): string
{
// Result tags
$sTags = '';
// Iterate throw validators...
foreach ($oField->GetValidators() as $oValidator) {
// Validator
if (get_class($oValidator) === Validator::class) {
if (!($oField instanceof DateField || $oField instanceof DateTimeField)) { // unrecognized regular expression
$sTags .= ' pattern="'.$oValidator->GetRegExp().'" ';
}
}
// Mandatory validator
if ($oValidator instanceof MandatoryValidator) {
$sTags .= ' required ';
}
}
return $sTags;
}
}