mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-17 06:18:44 +02:00
- Added support for ExternalKey, LinkedSet, linkedSetIndirect, CaseLog to the new portal
- Fixed some bugs on the customfields integration with he portal SVN:trunk[4003]
This commit is contained in:
@@ -1262,7 +1262,56 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
$oRemoteAtt = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToMe());
|
||||
return $oRemoteAtt;
|
||||
}
|
||||
|
||||
|
||||
static public function GetFormFieldClass()
|
||||
{
|
||||
return '\\Combodo\\iTop\\Form\\Field\\LinkedSetField';
|
||||
}
|
||||
|
||||
public function MakeFormField(DBObject $oObject, $oFormField = null)
|
||||
{
|
||||
if ($oFormField === null)
|
||||
{
|
||||
$sFormFieldClass = static::GetFormFieldClass();
|
||||
$oFormField = new $sFormFieldClass($this->GetCode());
|
||||
}
|
||||
|
||||
// Setting target class
|
||||
if (!$this->IsIndirect())
|
||||
{
|
||||
$sTargetClass = $this->GetLinkedClass();
|
||||
}
|
||||
else
|
||||
{
|
||||
$oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
|
||||
$sTargetClass = $oRemoteAttDef->GetTargetClass();
|
||||
|
||||
$oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote());
|
||||
}
|
||||
$oFormField->SetTargetClass($sTargetClass);
|
||||
$oFormField->SetIndirect($this->IsIndirect());
|
||||
// Setting attcodes to display
|
||||
$aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list'));
|
||||
// - Adding friendlyname attribute to the list is not already in it
|
||||
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass);
|
||||
if (!in_array($sTitleAttCode, $aAttCodesToDisplay))
|
||||
{
|
||||
$aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
|
||||
}
|
||||
// - Adding attribute labels
|
||||
$aAttributesToDisplay = array();
|
||||
foreach ($aAttCodesToDisplay as $sAttCodeToDisplay)
|
||||
{
|
||||
$oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
|
||||
$aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel();
|
||||
}
|
||||
$oFormField->SetAttributesToDisplay($aAttributesToDisplay);
|
||||
|
||||
parent::MakeFormField($oObject, $oFormField);
|
||||
|
||||
return $oFormField;
|
||||
}
|
||||
|
||||
public function IsPartOfFingerprint() { return false; }
|
||||
}
|
||||
|
||||
@@ -2919,16 +2968,22 @@ class AttributeCaseLog extends AttributeLongText
|
||||
return $this->GetOptional('format', 'html'); // default format for case logs is now HTML
|
||||
}
|
||||
|
||||
static public function GetFormFieldClass()
|
||||
{
|
||||
return '\\Combodo\\iTop\\Form\\Field\\CaseLogField';
|
||||
}
|
||||
|
||||
public function MakeFormField(DBObject $oObject, $oFormField = null)
|
||||
{
|
||||
// First we call the parent so the field is build
|
||||
$oFormField = parent::MakeFormField($oObject, $oFormField);
|
||||
// Then only we set the value
|
||||
$oFormField->SetCurrentValue($this->GetEditValue($oObject->Get($this->GetCode())));
|
||||
// And we set the entries
|
||||
$oFormField->SetEntries($oObject->Get($this->GetCode())->GetAsArray());
|
||||
|
||||
return $oFormField;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4023,7 +4078,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
|
||||
static public function GetFormFieldClass()
|
||||
{
|
||||
return '\\Combodo\\iTop\\Form\\Field\\SelectField';
|
||||
return '\\Combodo\\iTop\\Form\\Field\\SelectObjectField';
|
||||
}
|
||||
|
||||
public function MakeFormField(DBObject $oObject, $oFormField = null)
|
||||
@@ -4034,7 +4089,11 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
$sFormFieldClass = static::GetFormFieldClass();
|
||||
$oFormField = new $sFormFieldClass($this->GetCode());
|
||||
}
|
||||
|
||||
|
||||
// Setting params
|
||||
$oFormField->SetMaximumComboLength($this->GetMaximumComboLength());
|
||||
$oFormField->SetMinAutoCompleteChars($this->GetMinAutoCompleteChars());
|
||||
$oFormField->SetHierarchical(MetaModel::IsHierarchicalClass($this->GetTargetClass()));
|
||||
// Setting choices regarding the field dependencies
|
||||
$aFieldDependencies = $this->GetPrerequisiteAttributes();
|
||||
if (!empty($aFieldDependencies))
|
||||
@@ -4043,12 +4102,16 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
$oTmpField = $oFormField;
|
||||
$oFormField->SetOnFinalizeCallback(function() use ($oTmpField, $oTmpAttDef, $oObject)
|
||||
{
|
||||
$oTmpField->SetChoices($oTmpAttDef->GetAllowedValues($oObject->ToArgsForQuery()));
|
||||
$oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression());
|
||||
$oSearch->SetInternalParams(array('this' => $oObject));
|
||||
$oTmpField->SetSearch($oSearch);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
$oFormField->SetChoices($this->GetAllowedValues($oObject->ToArgsForQuery()));
|
||||
$oSearch = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression());
|
||||
$oSearch->SetInternalParams(array('this' => $oObject));
|
||||
$oFormField->SetSearch($oSearch);
|
||||
}
|
||||
|
||||
// If ExtKey is mandatory, we add a validator to ensure that the value 0 is not selected
|
||||
|
||||
@@ -13,7 +13,7 @@ $(function()
|
||||
validate_callback: 'validate', // When using an anonymous function, use the 'me' parameter to acces the current widget : function(me){ return me.validate(); },
|
||||
on_validation_callback: function(data){ },
|
||||
get_current_value_callback: 'getCurrentValue',
|
||||
|
||||
set_current_value_callback: function(me, oEvent, oData){ console.log('Form field: set_current_value_callback must be overloaded, this is the default callback.'); }
|
||||
},
|
||||
|
||||
// the constructor
|
||||
@@ -30,7 +30,7 @@ $(function()
|
||||
me.options.validators = oData;
|
||||
});
|
||||
this.element
|
||||
.bind('validate get_current_value', function(oEvent, oData){
|
||||
.bind('validate get_current_value set_current_value', function(oEvent, oData){
|
||||
oEvent.stopPropagation();
|
||||
|
||||
var callback = me.options[oEvent.type+'_callback'];
|
||||
@@ -124,7 +124,12 @@ $(function()
|
||||
{
|
||||
var bMandatory = (this.options.validators.mandatory !== undefined);
|
||||
// Extracting value for the field
|
||||
var oValue = this.getCurrentValue();
|
||||
var oValue = this.element.triggerHandler('get_current_value');
|
||||
if(oValue === null)
|
||||
{
|
||||
console.log('Form field : Warning, there was no value for "'+this.element.attr('data-field-id')+'"');
|
||||
return oResult;
|
||||
}
|
||||
var aValueKeys = Object.keys(oValue);
|
||||
|
||||
// This is just a safety check in case a field doesn't always return an object when no value assigned, so we have to check the mandatory validator here...
|
||||
|
||||
@@ -32,12 +32,14 @@ require_once APPROOT . 'sources/form/field/datefield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/datetimefield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/durationfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/textareafield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/caselogfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/multiplechoicesfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/selectfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/multipleselectfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/selectobjectfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/checkboxfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/radiofield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/field/linkedsetfield.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/validator/validator.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/validator/mandatoryvalidator.class.inc.php';
|
||||
require_once APPROOT . 'sources/form/validator/integervalidator.class.inc.php';
|
||||
@@ -47,4 +49,6 @@ require_once APPROOT . 'sources/renderer/fieldrenderer.class.inc.php';
|
||||
require_once APPROOT . 'sources/renderer/renderingoutput.class.inc.php';
|
||||
require_once APPROOT . 'sources/renderer/bootstrap/bsformrenderer.class.inc.php';
|
||||
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php';
|
||||
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bsselectobjectfieldrenderer.class.inc.php';
|
||||
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bslinkedsetfieldrenderer.class.inc.php';
|
||||
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssubformfieldrenderer.class.inc.php';
|
||||
|
||||
55
sources/form/field/caselogfield.class.inc.php
Normal file
55
sources/form/field/caselogfield.class.inc.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
namespace Combodo\iTop\Form\Field;
|
||||
|
||||
use \Closure;
|
||||
use \DBObject;
|
||||
use \Combodo\iTop\Form\Field\TextAreaField;
|
||||
|
||||
/**
|
||||
* Description of CaseLogField
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
*/
|
||||
class CaseLogField extends TextAreaField
|
||||
{
|
||||
protected $aEntries;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetEntries()
|
||||
{
|
||||
return $this->aEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $aEntries
|
||||
* @return \Combodo\iTop\Form\Field\TextAreaField
|
||||
*/
|
||||
public function SetEntries($aEntries)
|
||||
{
|
||||
$this->aEntries = $aEntries;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -358,6 +358,16 @@ abstract class Field
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the field is editable. Meaning that it is not editable nor hidden.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function IsEditable()
|
||||
{
|
||||
return (!$this->bReadOnly && !$this->bHidden);
|
||||
}
|
||||
|
||||
public function OnCancel()
|
||||
{
|
||||
// Overload when needed
|
||||
|
||||
154
sources/form/field/linkedsetfield.class.inc.php
Normal file
154
sources/form/field/linkedsetfield.class.inc.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
namespace Combodo\iTop\Form\Field;
|
||||
|
||||
use \Combodo\iTop\Form\Field\Field;
|
||||
|
||||
/**
|
||||
* Description of LinkedSetField
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
*/
|
||||
class LinkedSetField extends Field
|
||||
{
|
||||
protected $sTargetClass;
|
||||
protected $sExtKeyToRemote;
|
||||
protected $bIndirect;
|
||||
protected $aAttributesToDisplay;
|
||||
protected $sSearchEndpoint;
|
||||
protected $sInformationEndpoint;
|
||||
|
||||
public function __construct($sId, \Closure $onFinalizeCallback = null)
|
||||
{
|
||||
$this->sTargetClass = null;
|
||||
$this->sExtKeyToRemote = null;
|
||||
$this->bIndirect = false;
|
||||
$this->aAttributesToDisplay = array();
|
||||
$this->sSearchEndpoint = null;
|
||||
$this->sInformationEndpoint = null;
|
||||
|
||||
parent::__construct($sId, $onFinalizeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetTargetClass()
|
||||
{
|
||||
return $this->sTargetClass;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $sTargetClass
|
||||
* @return \Combodo\iTop\Form\Field\LinkedSetField
|
||||
*/
|
||||
public function SetTargetClass($sTargetClass)
|
||||
{
|
||||
$this->sTargetClass = $sTargetClass;
|
||||
return $sTargetClass;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function GetExtKeyToRemote()
|
||||
{
|
||||
return $this->sExtKeyToRemote;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $sExtKeyToRemote
|
||||
* @return \Combodo\iTop\Form\Field\LinkedSetField
|
||||
*/
|
||||
public function SetExtKeyToRemote($sExtKeyToRemote)
|
||||
{
|
||||
$this->sExtKeyToRemote = $sExtKeyToRemote;
|
||||
return $sExtKeyToRemote;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function IsIndirect()
|
||||
{
|
||||
return $this->bIndirect;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param boolean $bIndirect
|
||||
* @return \Combodo\iTop\Form\Field\LinkedSetField
|
||||
*/
|
||||
public function SetIndirect($bIndirect)
|
||||
{
|
||||
$this->bIndirect = $bIndirect;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash array of attributes to be displayed in the linkedset in the form $sAttCode => $sAttLabel
|
||||
*
|
||||
* @param $bAttCodesOnly If set to true, will return only the attcodes
|
||||
* @return array
|
||||
*/
|
||||
public function GetAttributesToDisplay($bAttCodesOnly = false)
|
||||
{
|
||||
return ($bAttCodesOnly) ? array_keys($this->aAttributesToDisplay) : $this->aAttributesToDisplay;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $aAttCodes
|
||||
* @return \Combodo\iTop\Form\Field\LinkedSetField
|
||||
*/
|
||||
public function SetAttributesToDisplay(array $aAttributesToDisplay)
|
||||
{
|
||||
$this->aAttributesToDisplay = $aAttributesToDisplay;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function GetSearchEndpoint()
|
||||
{
|
||||
return $this->sSearchEndpoint;
|
||||
}
|
||||
|
||||
public function SetSearchEndpoint($sSearchEndpoint)
|
||||
{
|
||||
$this->sSearchEndpoint = $sSearchEndpoint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function GetInformationEndpoint()
|
||||
{
|
||||
return $this->sInformationEndpoint;
|
||||
}
|
||||
|
||||
public function SetInformationEndpoint($sInformationEndpoint)
|
||||
{
|
||||
$this->sInformationEndpoint = $sInformationEndpoint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,14 +26,16 @@ use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
|
||||
/**
|
||||
* Description of SelectObjectField
|
||||
*
|
||||
* @author Romain Quetiez <romain.quetiez@combodo.com>
|
||||
*/
|
||||
class SelectObjectField extends Field
|
||||
{
|
||||
protected $oSearch;
|
||||
protected $iMaximumComboLength;
|
||||
protected $iMinAutoCompleteChars;
|
||||
|
||||
protected $bHierarchical;
|
||||
protected $iControlType;
|
||||
protected $sSearchEndpoint;
|
||||
|
||||
const CONTROL_SELECT = 1;
|
||||
const CONTROL_RADIO_VERTICAL = 2;
|
||||
@@ -44,22 +46,33 @@ class SelectObjectField extends Field
|
||||
$this->oSearch = null;
|
||||
$this->iMaximumComboLength = null;
|
||||
$this->iMinAutoCompleteChars = 3;
|
||||
$this->bHierarchical = false;
|
||||
$this->iControlType = self::CONTROL_SELECT;
|
||||
$this->sSearchEndpoint = null;
|
||||
}
|
||||
|
||||
public function SetSearch(DBSearch $oSearch)
|
||||
{
|
||||
$this->oSearch = $oSearch;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function SetMaximumComboLength($iMaximumComboLength)
|
||||
{
|
||||
$this->iMaximumComboLength = $iMaximumComboLength;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function SetMinAutoCompleteChars($iMinAutoCompleteChars)
|
||||
{
|
||||
$this->iMinAutoCompleteChars = $iMinAutoCompleteChars;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function SetHierarchical($bHierarchical)
|
||||
{
|
||||
$this->bHierarchical = $bHierarchical;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function SetControlType($iControlType)
|
||||
@@ -67,6 +80,12 @@ class SelectObjectField extends Field
|
||||
$this->iControlType = $iControlType;
|
||||
}
|
||||
|
||||
public function SetSearchEndpoint($sSearchEndpoint)
|
||||
{
|
||||
$this->sSearchEndpoint = $sSearchEndpoint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the field is mandatory or not.
|
||||
* Setting the value will automatically add/remove a MandatoryValidator to the Field
|
||||
@@ -112,8 +131,18 @@ class SelectObjectField extends Field
|
||||
return $this->iMinAutoCompleteChars;
|
||||
}
|
||||
|
||||
public function GetHierarchical()
|
||||
{
|
||||
return $this->bHierarchical;
|
||||
}
|
||||
|
||||
public function GetControlType()
|
||||
{
|
||||
return $this->iControlType;
|
||||
}
|
||||
|
||||
public function GetSearchEndpoint()
|
||||
{
|
||||
return $this->sSearchEndpoint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,49 @@ class SubFormField extends Field
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mandatory flag on all the fields on the form
|
||||
*
|
||||
* @param boolean $bMandatory
|
||||
*/
|
||||
public function SetMandatory($bMandatory)
|
||||
{
|
||||
foreach ($this->oForm->GetFields() as $oField)
|
||||
{
|
||||
$oField->SetMandatory($bMandatory);
|
||||
}
|
||||
parent::SetMandatory($bMandatory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the read-only flag on all the fields on the form
|
||||
*
|
||||
* @param boolean $bReadOnly
|
||||
*/
|
||||
public function SetReadOnly($bReadOnly)
|
||||
{
|
||||
foreach ($this->oForm->GetFields() as $oField)
|
||||
{
|
||||
$oField->SetReadOnly($bReadOnly);
|
||||
$oField->SetMandatory(false);
|
||||
}
|
||||
parent::SetReadOnly($bReadOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hidden flag on all the fields on the form
|
||||
*
|
||||
* @param boolean $bHidden
|
||||
*/
|
||||
public function SetHidden($bHidden)
|
||||
{
|
||||
foreach ($this->oForm->GetFields() as $oField)
|
||||
{
|
||||
$oField->SetHidden($bHidden);
|
||||
}
|
||||
parent::SetHidden($bHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sFormPath
|
||||
* @return Form|null
|
||||
|
||||
@@ -37,6 +37,7 @@ class Form
|
||||
protected $aDependencies;
|
||||
protected $bValid;
|
||||
protected $aErrorMessages;
|
||||
protected $iEditableFieldCount;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
@@ -51,6 +52,7 @@ class Form
|
||||
$this->aDependencies = array();
|
||||
$this->bValid = true;
|
||||
$this->aErrorMessages = array();
|
||||
$this->iEditableFieldCount = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,6 +373,39 @@ class Form
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of editable fields in this form.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function GetEditableFieldCount($bForce = false)
|
||||
{
|
||||
// Count is usally done by the Finalize function but it can be done there if Finalize hasn't been called yet or if we choose to force it.
|
||||
if (($this->iEditableFieldCount === null) || ($bForce === true))
|
||||
{
|
||||
$this->iEditableFieldCount = 0;
|
||||
foreach ($this->aFields as $oField)
|
||||
{
|
||||
if ($oField->IsEditable())
|
||||
{
|
||||
$this->iEditableFieldCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->iEditableFieldCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the form has at least one editable field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function HasEditableFields()
|
||||
{
|
||||
return ($this->GetEditableFieldCount() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sFormPath
|
||||
* @return Form|null
|
||||
@@ -450,7 +485,11 @@ class Form
|
||||
foreach ($aFieldList as $sId => $oField)
|
||||
{
|
||||
$oField->OnFinalize();
|
||||
}
|
||||
if ($oField->IsEditable())
|
||||
{
|
||||
$this->iEditableFieldCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
namespace Combodo\iTop\Renderer\Bootstrap;
|
||||
|
||||
use \Silex\Application;
|
||||
use \Combodo\iTop\Renderer\FormRenderer;
|
||||
use \Combodo\iTop\Form\Form;
|
||||
|
||||
@@ -43,12 +44,14 @@ class BsFormRenderer extends FormRenderer
|
||||
$this->AddSupportedField('LabelField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('StringField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('TextAreaField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('CaseLogField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('SelectField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('MultipleSelectField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('RadioField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('CheckboxField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('SubFormField', 'BsSubFormFieldRenderer');
|
||||
$this->AddSupportedField('SelectObjectField', 'BsSimpleFieldRenderer');
|
||||
$this->AddSupportedField('SelectObjectField', 'BsSelectObjectFieldRenderer');
|
||||
$this->AddSupportedField('LinkedSetField', 'BsLinkedSetFieldRenderer');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
namespace Combodo\iTop\Renderer\Bootstrap\FieldRenderer;
|
||||
|
||||
use \utils;
|
||||
use \Dict;
|
||||
use \UserRights;
|
||||
use \InlineImage;
|
||||
use \DBObjectSet;
|
||||
use \MetaModel;
|
||||
use \Combodo\iTop\Renderer\FieldRenderer;
|
||||
use \Combodo\iTop\Renderer\RenderingOutput;
|
||||
use \Combodo\iTop\Form\Field\LinkedSetField;
|
||||
|
||||
/**
|
||||
* Description of BsSelectObjectFieldRenderer
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
*/
|
||||
class BsLinkedSetFieldRenderer extends FieldRenderer
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a RenderingOutput for the FieldRenderer's Field
|
||||
*
|
||||
* @return \Combodo\iTop\Renderer\RenderingOutput
|
||||
*/
|
||||
public function Render()
|
||||
{
|
||||
$oOutput = new RenderingOutput();
|
||||
$sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : '';
|
||||
// Vars to build the table
|
||||
$sAttributesToDisplayAsJson = json_encode($this->oField->GetAttributesToDisplay());
|
||||
$sAttCodesToDisplayAsJson = json_encode($this->oField->GetAttributesToDisplay(true));
|
||||
$aItems = array();
|
||||
$aItemIds = array();
|
||||
$this->PrepareItems($aItems, $aItemIds);
|
||||
$sItemsAsJson = json_encode($aItems);
|
||||
$sItemIdsAsJson = htmlentities(json_encode($aItemIds), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
if (!$this->oField->GetHidden())
|
||||
{
|
||||
// Rendering field
|
||||
$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
|
||||
if ($this->oField->GetLabel() !== '')
|
||||
{
|
||||
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
|
||||
}
|
||||
$oOutput->AddHtml('<div class="help-block"></div>');
|
||||
|
||||
// Rendering table
|
||||
// - Vars
|
||||
$sTableId = 'table_' . $this->oField->GetGlobalId();
|
||||
// - Output
|
||||
$oOutput->AddHtml(
|
||||
<<<EOF
|
||||
<div class="form_linkedset_wrapper">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<input type="hidden" id="{$this->oField->GetGlobalId()}" name="{$this->oField->GetId()}" value="{$sItemIdsAsJson}" />
|
||||
<table id="{$sTableId}" data-field-id="{$this->oField->GetId()}" class="table table-striped table-bordered responsive" cellspacing="0" width="100%">
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
EOF
|
||||
);
|
||||
|
||||
// Rendering table widget
|
||||
// - Vars
|
||||
$sEmptyTableLabel = htmlentities(Dict::S('UI:Message:EmptyList:UseAdd'), ENT_QUOTES, 'UTF-8');
|
||||
$sSelectionOptionHtml = ($this->oField->GetReadOnly()) ? 'false' : '{"style": "multi"}';
|
||||
$sSelectionInputHtml = ($this->oField->GetReadOnly()) ? '' : '<span class="row_input"><input type="checkbox" name="' . $this->oField->GetId() . '" /></span>';
|
||||
// - Output
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
var oColumnProperties_{$this->oField->GetGlobalId()} = {$sAttributesToDisplayAsJson};
|
||||
var oRawDatas_{$this->oField->GetGlobalId()} = {$sItemsAsJson};
|
||||
var oTable_{$this->oField->GetGlobalId()};
|
||||
var oSelectedItems_{$this->oField->GetGlobalId()} = {};
|
||||
|
||||
var getColumnsDefinition_{$this->oField->GetGlobalId()} = function()
|
||||
{
|
||||
var aColumnsDefinition = [];
|
||||
var sFirstColumnId = Object.keys(oColumnProperties_{$this->oField->GetGlobalId()})[0];
|
||||
|
||||
for(sKey in oColumnProperties_{$this->oField->GetGlobalId()})
|
||||
{
|
||||
// Level main column
|
||||
aColumnsDefinition.push({
|
||||
"width": "auto",
|
||||
"searchable": true,
|
||||
"sortable": true,
|
||||
"title": oColumnProperties_{$this->oField->GetGlobalId()}[sKey],
|
||||
"defaultContent": "",
|
||||
"type": "html",
|
||||
"data": "attributes."+sKey+".att_code",
|
||||
"render": function(data, type, row){
|
||||
var cellElem;
|
||||
|
||||
// Preparing the cell data
|
||||
if(row.attributes[data].url !== undefined)
|
||||
{
|
||||
cellElem = $('<a></a>');
|
||||
cellElem.attr('target', '_blank').attr('href', row.attributes[data].url);
|
||||
}
|
||||
else
|
||||
{
|
||||
cellElem = $('<span></span>');
|
||||
}
|
||||
cellElem.attr('data-object-id', row.id).html('<span>' + row.attributes[data].value + '</span>');
|
||||
|
||||
if(data === sFirstColumnId)
|
||||
{
|
||||
cellElem.prepend('{$sSelectionInputHtml}');
|
||||
}
|
||||
|
||||
return cellElem.prop('outerHTML');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return aColumnsDefinition;
|
||||
};
|
||||
|
||||
// Note : Those options should be externalized in an library so we can use them on any DataTables for the portal.
|
||||
// We would just have to override / complete the necessary elements
|
||||
oTable_{$this->oField->GetGlobalId()} = $('#{$sTableId}').DataTable({
|
||||
"language": {
|
||||
"emptyTable": "{$sEmptyTableLabel}"
|
||||
},
|
||||
"displayLength": -1,
|
||||
"scrollY": "300px",
|
||||
"scrollCollapse": true,
|
||||
"dom": 't',
|
||||
"columns": getColumnsDefinition_{$this->oField->GetGlobalId()}(),
|
||||
"select": {$sSelectionOptionHtml},
|
||||
"rowId": "id",
|
||||
"data": oRawDatas_{$this->oField->GetGlobalId()},
|
||||
});
|
||||
EOF
|
||||
);
|
||||
|
||||
// Attaching JS widget
|
||||
$sObjectInformationsUrl = $this->oField->GetInformationEndpoint();
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").portal_form_field({
|
||||
'validators': {$this->GetValidatorsAsJson()},
|
||||
'get_current_value_callback': function(me, oEvent, oData){
|
||||
var value = null;
|
||||
|
||||
value = JSON.parse(me.element.find('#{$this->oField->GetGlobalId()}').val());
|
||||
|
||||
return value;
|
||||
},
|
||||
'set_current_value_callback': function(me, oEvent, oData){
|
||||
// When we have data (meaning that we picked objects from search)
|
||||
if(oData !== undefined && Object.keys(oData.values).length > 0)
|
||||
{
|
||||
// Retrieving new rows ids
|
||||
var aObjectIds = Object.keys(oData.values);
|
||||
|
||||
// Retrieving rows informations so we can add them
|
||||
$.post(
|
||||
'{$sObjectInformationsUrl}',
|
||||
{
|
||||
sObjectClass: '{$this->oField->GetTargetClass()}',
|
||||
aObjectIds: aObjectIds,
|
||||
aObjectAttCodes: $sAttCodesToDisplayAsJson
|
||||
},
|
||||
function(oData){
|
||||
// Updating datatables
|
||||
if(oData.items !== undefined)
|
||||
{
|
||||
for(var i in oData.items)
|
||||
{
|
||||
// Adding item to table only if it's not already there
|
||||
if($('#{$sTableId} tr#' + oData.items[i].id + '[role="row"]').length === 0)
|
||||
{
|
||||
// Making id negative in order to recognize it when persisting
|
||||
oData.items[i].id = -1 * parseInt(oData.items[i].id);
|
||||
oTable_{$this->oField->GetGlobalId()}.row.add(oData.items[i]);
|
||||
}
|
||||
}
|
||||
oTable_{$this->oField->GetGlobalId()}.draw();
|
||||
}
|
||||
}
|
||||
)
|
||||
.done(function(oData){
|
||||
// Updating hidden field
|
||||
var aData = oTable_{$this->oField->GetGlobalId()}.rows().data().toArray();
|
||||
var aObjectIds = [];
|
||||
|
||||
for(var i in aData)
|
||||
{
|
||||
aObjectIds.push({id: aData[i].id});
|
||||
}
|
||||
|
||||
$('#{$this->oField->GetGlobalId()}').val(JSON.stringify(aObjectIds));
|
||||
});
|
||||
}
|
||||
// We come from a button
|
||||
else
|
||||
{
|
||||
// Updating hidden field
|
||||
var aData = oTable_{$this->oField->GetGlobalId()}.rows().data().toArray();
|
||||
var aObjectIds = [];
|
||||
|
||||
for(var i in aData)
|
||||
{
|
||||
aObjectIds.push({id: aData[i].id});
|
||||
}
|
||||
|
||||
$('#{$this->oField->GetGlobalId()}').val(JSON.stringify(aObjectIds));
|
||||
}
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
|
||||
// Additional features if in edition mode
|
||||
if (!$this->oField->GetReadOnly())
|
||||
{
|
||||
// Rendering table
|
||||
// - Vars
|
||||
$sButtonAllId = 'btn_all_' . $this->oField->GetGlobalId();
|
||||
$sButtonNoneId = 'btn_none_' . $this->oField->GetGlobalId();
|
||||
$sButtonRemoveId = 'btn_remove_' . $this->oField->GetGlobalId();
|
||||
$sButtonAddId = 'btn_add_' . $this->oField->GetGlobalId();
|
||||
$sLabelAll = Dict::S('Core:BulkExport:CheckAll');
|
||||
$sLabelNone = Dict::S('Core:BulkExport:UncheckAll');
|
||||
$sLabelRemove = Dict::S('UI:Button:Remove');
|
||||
$sLabelAdd = Dict::S('UI:Button:AddObject');
|
||||
// - Output
|
||||
$oOutput->AddHtml(
|
||||
<<<EOF
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<button type="button" class="btn btn-secondary" id="{$sButtonAllId}">{$sLabelAll}</button>
|
||||
<button type="button" class="btn btn-secondary" id="{$sButtonNoneId}">{$sLabelNone}</button>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<button type="button" class="btn btn-danger" id="{$sButtonRemoveId}">{$sLabelRemove}</button>
|
||||
<button type="button" class="btn btn-default" id="{$sButtonAddId}">{$sLabelAdd}</button>
|
||||
</div>
|
||||
</div>
|
||||
EOF
|
||||
);
|
||||
|
||||
// Rendering table widget
|
||||
// - Vars
|
||||
$sAddButtonEndpoint = str_replace('-sMode-', 'from-attribute', $this->oField->GetSearchEndpoint());
|
||||
// - Output
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
// Handles items selection/deselection
|
||||
// - Directly on the table
|
||||
oTable_{$this->oField->GetGlobalId()}.off('select').on('select', function(oEvent, dt, type, indexes){
|
||||
var aData = oTable_{$this->oField->GetGlobalId()}.rows(indexes).data().toArray();
|
||||
|
||||
// Checking input
|
||||
$('#{$sTableId} tr[role="row"].selected td:first-child input').prop('checked', true);
|
||||
// Saving values in temp array
|
||||
for(var i in aData)
|
||||
{
|
||||
var iItemId = aData[i].id;
|
||||
if(!(iItemId in oSelectedItems_{$this->oField->GetGlobalId()}))
|
||||
{
|
||||
oSelectedItems_{$this->oField->GetGlobalId()}[iItemId] = aData[i].name;
|
||||
}
|
||||
}
|
||||
});
|
||||
oTable_{$this->oField->GetGlobalId()}.off('deselect').on('deselect', function(oEvent, dt, type, indexes){
|
||||
var aData = oTable_{$this->oField->GetGlobalId()}.rows(indexes).data().toArray();
|
||||
|
||||
// Checking input
|
||||
$('#{$sTableId} tr[role="row"]:not(.selected) td:first-child input').prop('checked', false);
|
||||
// Saving values in temp array
|
||||
for(var i in aData)
|
||||
{
|
||||
var iItemId = aData[i].id;
|
||||
if(iItemId in oSelectedItems_{$this->oField->GetGlobalId()})
|
||||
{
|
||||
delete oSelectedItems_{$this->oField->GetGlobalId()}[iItemId];
|
||||
}
|
||||
}
|
||||
});
|
||||
// - From the bottom buttons
|
||||
$('#{$sButtonAllId}').off('click').on('click', function(){
|
||||
oTable_{$this->oField->GetGlobalId()}.rows().select();
|
||||
});
|
||||
$('#{$sButtonNoneId}').off('click').on('click', function(){
|
||||
oTable_{$this->oField->GetGlobalId()}.rows().deselect();
|
||||
});
|
||||
|
||||
// Handles items remove/add
|
||||
$('#{$sButtonRemoveId}').off('click').on('click', function(){
|
||||
oTable_{$this->oField->GetGlobalId()}.rows({selected: true}).remove().draw();
|
||||
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").triggerHandler('set_current_value');
|
||||
});
|
||||
$('#{$sButtonAddId}').off('click').on('click', function(){
|
||||
// Creating a new modal
|
||||
var oModalElem;
|
||||
if($('.modal[data-source-element="{$sButtonAddId}"]').length === 0)
|
||||
{
|
||||
oModalElem = $('#modal-for-all').clone();
|
||||
oModalElem.attr('id', '').attr('data-source-element', '{$sButtonAddId}').appendTo('body');
|
||||
}
|
||||
else
|
||||
{
|
||||
oModalElem = $('.modal[data-source-element="{$sButtonAddId}"]').first();
|
||||
}
|
||||
// Resizing to small modal
|
||||
oModalElem.find('.modal-dialog').removeClass('modal-sm').addClass('modal-lg');
|
||||
// Loading content
|
||||
oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
|
||||
oModalElem.find('.modal-content').load(
|
||||
'{$sAddButtonEndpoint}',
|
||||
{
|
||||
sFormPath: '{$this->oField->GetFormPath()}',
|
||||
sFieldId: '{$this->oField->GetId()}'
|
||||
}
|
||||
);
|
||||
oModalElem.modal('show');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
// ... and in hidden mode
|
||||
else
|
||||
{
|
||||
$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="' . $sItemIdsAsJson . '" />');
|
||||
}
|
||||
|
||||
// End of table rendering
|
||||
$oOutput->AddHtml('</div>');
|
||||
$oOutput->AddHtml('</div>');
|
||||
|
||||
return $oOutput;
|
||||
}
|
||||
|
||||
protected function PrepareItems(&$aItems, &$aItemIds)
|
||||
{
|
||||
$oValueSet = $this->oField->GetCurrentValue();
|
||||
$oValueSet->OptimizeColumnLoad(array($this->oField->GetTargetClass() => $this->oField->GetAttributesToDisplay(true)));
|
||||
while ($oItem = $oValueSet->Fetch())
|
||||
{
|
||||
$aItemProperties = array(
|
||||
'id' => $oItem->GetKey(),
|
||||
'name' => $oItem->GetName(),
|
||||
'attributes' => array()
|
||||
);
|
||||
|
||||
// In case of indirect linked set, we must retrieve the remote object
|
||||
if ($this->oField->IsIndirect())
|
||||
{
|
||||
$oRemoteItem = MetaModel::GetObject($this->oField->GetTargetClass(), $oItem->Get($this->oField->GetExtKeyToRemote()));
|
||||
}
|
||||
else
|
||||
{
|
||||
$oRemoteItem = $oItem;
|
||||
}
|
||||
|
||||
foreach ($this->oField->GetAttributesToDisplay(true) as $sAttCode)
|
||||
{
|
||||
if ($sAttCode !== 'id')
|
||||
{
|
||||
$aAttProperties = array(
|
||||
'att_code' => $sAttCode
|
||||
);
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->oField->GetTargetClass(), $sAttCode);
|
||||
if ($oAttDef->IsExternalKey())
|
||||
{
|
||||
$aAttProperties['value'] = $oRemoteItem->Get($sAttCode . '_friendlyname');
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttProperties['value'] = $oAttDef->GetValueLabel($oRemoteItem->Get($sAttCode));
|
||||
}
|
||||
|
||||
$aItemProperties['attributes'][$sAttCode] = $aAttProperties;
|
||||
}
|
||||
}
|
||||
|
||||
$aItems[] = $aItemProperties;
|
||||
$aItemIds[] = array('id' => $oItem->GetKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an regular search button
|
||||
*
|
||||
* @param RenderingOutput $oOutput
|
||||
*/
|
||||
protected function RenderRegularSearch(RenderingOutput &$oOutput)
|
||||
{
|
||||
$sSearchButtonId = 's_rg_' . $this->oField->GetGlobalId();
|
||||
$sEndpoint = str_replace('-sMode-', 'from-attribute', $this->oField->GetSearchEndpoint());
|
||||
|
||||
$oOutput->AddHtml('<div class="col-xs-2 col-lg-1">');
|
||||
$oOutput->AddHtml('<button type="button" class="btn btn-default" id="' . $sSearchButtonId . '">S</button>');
|
||||
$oOutput->AddHtml('</div>');
|
||||
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$('#{$sSearchButtonId}').off('click').on('click', function(){
|
||||
// Creating a new modal
|
||||
var oModalElem;
|
||||
if($('.modal[data-source-element="{$sSearchButtonId}"]').length === 0)
|
||||
{
|
||||
oModalElem = $('#modal-for-all').clone();
|
||||
oModalElem.attr('id', '').attr('data-source-element', '{$sSearchButtonId}').appendTo('body');
|
||||
}
|
||||
else
|
||||
{
|
||||
oModalElem = $('.modal[data-source-element="{$sSearchButtonId}"]').first();
|
||||
}
|
||||
// Resizing to small modal
|
||||
oModalElem.find('.modal-dialog').removeClass('modal-sm').addClass('modal-lg');
|
||||
// Loading content
|
||||
oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
|
||||
oModalElem.find('.modal-content').load(
|
||||
'{$sEndpoint}',
|
||||
{
|
||||
sFormPath: '{$this->oField->GetFormPath()}',
|
||||
sFieldId: '{$this->oField->GetId()}'
|
||||
}
|
||||
);
|
||||
oModalElem.modal('show');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
|
||||
// Copyright (C) 2010-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// iTop is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
namespace Combodo\iTop\Renderer\Bootstrap\FieldRenderer;
|
||||
|
||||
use \utils;
|
||||
use \Dict;
|
||||
use \UserRights;
|
||||
use \InlineImage;
|
||||
use \DBObjectSet;
|
||||
use \MetaModel;
|
||||
use \Combodo\iTop\Renderer\FieldRenderer;
|
||||
use \Combodo\iTop\Renderer\RenderingOutput;
|
||||
use \Combodo\iTop\Form\Field\SelectObjectField;
|
||||
|
||||
/**
|
||||
* Description of BsSelectObjectFieldRenderer
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
*/
|
||||
class BsSelectObjectFieldRenderer extends FieldRenderer
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a RenderingOutput for the FieldRenderer's Field
|
||||
*
|
||||
* @return \Combodo\iTop\Renderer\RenderingOutput
|
||||
*/
|
||||
public function Render()
|
||||
{
|
||||
$oOutput = new RenderingOutput();
|
||||
$sFieldValueClass = $this->oField->GetSearch()->GetClass();
|
||||
$sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : '';
|
||||
$iFieldControlType = $this->oField->GetControlType();
|
||||
|
||||
// TODO : Remove this when hierarchical search supported
|
||||
$this->oField->SetHierarchical(false);
|
||||
|
||||
// Rendering field in edition mode
|
||||
if (!$this->oField->GetReadOnly() && !$this->oField->GetHidden())
|
||||
{
|
||||
// Rendering field
|
||||
$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
|
||||
if ($this->oField->GetLabel() !== '')
|
||||
{
|
||||
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
|
||||
}
|
||||
$oOutput->AddHtml('<div class="help-block"></div>');
|
||||
// - As a select
|
||||
if ($iFieldControlType === SelectObjectField::CONTROL_SELECT)
|
||||
{
|
||||
// Checking if regular select or autocomplete
|
||||
$oSearch = $this->oField->GetSearch()->DeepClone();
|
||||
$oCountSet = new DBObjectSet($oSearch);
|
||||
$iSetCount = $oCountSet->Count();
|
||||
$bRegularSelect = ($iSetCount <= $this->oField->GetMaximumComboLength());
|
||||
unset($oCountSet);
|
||||
|
||||
// - For regular select
|
||||
if ($bRegularSelect)
|
||||
{
|
||||
// HTML for select part
|
||||
// - Opening row
|
||||
$oOutput->AddHtml('<div class="row">');
|
||||
// - Rendering select
|
||||
$oOutput->AddHtml('<div class="col-xs-' . ( $this->oField->GetHierarchical() ? 10 : 12 ) . ' col-sm-' . ( $this->oField->GetHierarchical() ? 9 : 12 ) . ' col-md-' . ( $this->oField->GetHierarchical() ? 10 : 12 ) . '">');
|
||||
$oOutput->AddHtml('<select id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" class="form-control">');
|
||||
$oOutput->AddHtml('<option value="">')->AddHtml(Dict::S('UI:SelectOne'), false)->AddHtml('</option>');
|
||||
// - Retrieving choices
|
||||
$oChoicesSet = new DBObjectSet($oSearch);
|
||||
while ($oChoice = $oChoicesSet->Fetch())
|
||||
{
|
||||
// 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() == $oChoice->GetKey()) ? 'selected' : '';
|
||||
$oOutput->AddHtml('<option value="' . $oChoice->GetKey() . '" ' . $sSelectedAtt . ' >')->AddHtml($oChoice->GetName(), false)->AddHtml('</option>');
|
||||
}
|
||||
unset($oChoicesSet);
|
||||
$oOutput->AddHtml('</select>');
|
||||
$oOutput->AddHtml('</div>');
|
||||
// - Closing col for autocomplete & opening col for hierarchy, rendering hierarchy button, closing col and row
|
||||
$oOutput->AddHtml('<div class="col-xs-' . ( $this->oField->GetHierarchical() ? 2 : 0 ) . ' col-sm-' . ( $this->oField->GetHierarchical() ? 3 : 0 ) . ' col-md-' . ( $this->oField->GetHierarchical() ? 2 : 0 ) . ' text-right">');
|
||||
$this->RenderHierarchicalSearch($oOutput);
|
||||
$oOutput->AddHtml('</div>');
|
||||
// - Closing row
|
||||
$oOutput->AddHtml('</div>');
|
||||
|
||||
// JS FieldChange trigger (:input are not always at the same depth)
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$("#{$this->oField->GetGlobalId()}").off("change keyup").on("change keyup", function(){
|
||||
var me = this;
|
||||
|
||||
$(this).closest(".field_set").trigger("field_change", {
|
||||
id: $(me).attr("id"),
|
||||
name: $(me).closest(".form_field").attr("data-field-id"),
|
||||
value: $(me).val()
|
||||
});
|
||||
});
|
||||
EOF
|
||||
);
|
||||
|
||||
// Attaching JS widget
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").portal_form_field({
|
||||
'validators': {$this->GetValidatorsAsJson()}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
// - For autocomplete
|
||||
else
|
||||
{
|
||||
$sAutocompleteFieldId = 's_ac_' . $this->oField->GetGlobalId();
|
||||
$sEndpoint = str_replace('-sMode-', 'autocomplete', $this->oField->GetSearchEndpoint());
|
||||
$sNoResultText = Dict::S('Portal:Autocomplete:NoResult');
|
||||
|
||||
// Retrieving field value
|
||||
if ($this->oField->GetCurrentValue() !== null && $this->oField->GetCurrentValue() !== 0)
|
||||
{
|
||||
$oFieldValue = MetaModel::GetObject($sFieldValueClass, $this->oField->GetCurrentValue());
|
||||
$sFieldValue = $oFieldValue->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sFieldValue = '';
|
||||
}
|
||||
|
||||
// HTML for autocomplete part
|
||||
// - Opening row
|
||||
$oOutput->AddHtml('<div class="row">');
|
||||
// - Rendering autocomplete search
|
||||
$oOutput->AddHtml('<div class="col-xs-' . ( $this->oField->GetHierarchical() ? 9 : 10 ) . ' col-sm-' . ( $this->oField->GetHierarchical() ? 8 : 9 ) . ' col-md-' . ( $this->oField->GetHierarchical() ? 8 : 10 ) . ' col-lg-' . ( $this->oField->GetHierarchical() ? 9 : 10 ) . '">');
|
||||
$oOutput->AddHtml('<input type="text" id="' . $sAutocompleteFieldId . '" name="' . $sAutocompleteFieldId . '" value="')->AddHtml($sFieldValue, true)->AddHtml('" data-target-id="' . $this->oField->GetGlobalId() . ' "class="form-control" />');
|
||||
$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="' . $this->oField->GetCurrentValue() . '" />');
|
||||
$oOutput->AddHtml('</div>');
|
||||
// - Rendering buttons
|
||||
$oOutput->AddHtml('<div class="col-xs-' . ( $this->oField->GetHierarchical() ? 3 : 2 ) . ' col-sm-' . ( $this->oField->GetHierarchical() ? 4 : 3 ) . ' col-md-' . ( $this->oField->GetHierarchical() ? 4 : 2 ) . ' col-lg-' . ( $this->oField->GetHierarchical() ? 3 : 2 ) . ' text-right">');
|
||||
$oOutput->AddHtml('<div class="btn-group" role="group">');
|
||||
// - Rendering hierarchy button
|
||||
$this->RenderHierarchicalSearch($oOutput);
|
||||
// - Rendering regular search
|
||||
$this->RenderRegularSearch($oOutput);
|
||||
$oOutput->AddHtml('</div>');
|
||||
$oOutput->AddHtml('</div>');
|
||||
// - Closing row
|
||||
$oOutput->AddHtml('</div>');
|
||||
|
||||
// JS FieldChange trigger (:input are not always at the same depth)
|
||||
// Note : Not used for that field type
|
||||
// Attaching JS widget
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").portal_form_field({
|
||||
'validators': {$this->GetValidatorsAsJson()},
|
||||
'get_current_value_callback': function(me, oEvent, oData){
|
||||
var value = null;
|
||||
|
||||
value = me.element.find('#{$this->oField->GetGlobalId()}').val();
|
||||
|
||||
return value;
|
||||
},
|
||||
'set_current_value_callback': function(me, oEvent, oData){
|
||||
var sItemId = Object.keys(oData.value)[0];
|
||||
var sItemName = oData.value[sItemId];
|
||||
|
||||
// Updating autocomplete field
|
||||
me.element.find('#{$this->oField->GetGlobalId()}').val(sItemId);
|
||||
me.element.find('#{$sAutocompleteFieldId}').val(sItemName);
|
||||
oAutocompleteSource_{$this->oField->GetId()}.index.datums[sItemId] = {id: sItemId, name: sItemName};
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
|
||||
// Preparing JS part for autocomplete
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
var oAutocompleteSource_{$this->oField->GetId()} = new Bloodhound({
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
datumTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
remote: {
|
||||
url : '{$sEndpoint}',
|
||||
prepare: function(query, settings){
|
||||
settings.type = "POST";
|
||||
settings.contentType = "application/json; charset=UTF-8";
|
||||
settings.data = {
|
||||
sQuery: query
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
filter: function(response){
|
||||
var oItems = response.results.items;
|
||||
// Manualy adding data from remote to the index.datums so we can check data later
|
||||
for(var sItemKey in oItems)
|
||||
{
|
||||
oAutocompleteSource_{$this->oField->GetId()}.index.datums[oItems[sItemKey].id] = oItems[sItemKey];
|
||||
}
|
||||
return oItems;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#$sAutocompleteFieldId').typeahead({
|
||||
hint: true,
|
||||
hightlight: true,
|
||||
minLength: {$this->oField->GetMinAutoCompleteChars()}
|
||||
},{
|
||||
name: '{$this->oField->GetId()}',
|
||||
source: oAutocompleteSource_{$this->oField->GetId()},
|
||||
limit: 20,
|
||||
display: 'name',
|
||||
templates: {
|
||||
suggestion: Handlebars.compile('<div>{{name}}</div>'),
|
||||
pending: $("#page_overlay .content_loader").prop('outerHTML'),
|
||||
notFound: '<div class="no_result">{$sNoResultText}</div>'
|
||||
}
|
||||
})
|
||||
.off('typeahead:select').on('typeahead:select', function(oEvent, oSuggestion){
|
||||
$('#{$this->oField->GetGlobalId()}').val(oSuggestion.id);
|
||||
})
|
||||
.off('typeahead:change').on('typeahead:change', function(oEvent, oSuggestion){
|
||||
// Checking if the value is a correct value. This is necessary because the user could empty the field / remove some chars and typeahead would not update the hidden input
|
||||
var oDatums = oAutocompleteSource_{$this->oField->GetId()}.index.datums;
|
||||
var bFound = false;
|
||||
for(var i in oDatums)
|
||||
{
|
||||
if(oDatums[i].name == oSuggestion)
|
||||
{
|
||||
bFound = true;
|
||||
$('#{$this->oField->GetGlobalId()}').val(oDatums[i].id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Emptying the fields if value is incorrect
|
||||
if(!bFound)
|
||||
{
|
||||
$('#{$this->oField->GetGlobalId()}').val(0);
|
||||
$('#{$sAutocompleteFieldId}').val('');
|
||||
}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
$oOutput->AddHtml('</div>');
|
||||
}
|
||||
// ... and in read-only mode (or hidden)
|
||||
else
|
||||
{
|
||||
// Retrieving field value
|
||||
if ($this->oField->GetCurrentValue() !== null && $this->oField->GetCurrentValue() !== 0 && $this->oField->GetCurrentValue() !== '')
|
||||
{
|
||||
$oFieldValue = MetaModel::GetObject($sFieldValueClass, $this->oField->GetCurrentValue());
|
||||
$sFieldValue = $oFieldValue->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sFieldValue = Dict::S('UI:UndefinedObject');
|
||||
}
|
||||
|
||||
$oOutput->AddHtml('<div class="form-group">');
|
||||
// Showing label / value only if read-only but not hidden
|
||||
if (!$this->oField->GetHidden())
|
||||
{
|
||||
if ($this->oField->GetLabel() !== '')
|
||||
{
|
||||
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
|
||||
}
|
||||
$oOutput->AddHtml('<div class="form-control-static">' . $sFieldValue . '</div>');
|
||||
}
|
||||
$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="' . $this->oField->GetCurrentValue() . '" class="form-control" />');
|
||||
$oOutput->AddHtml('</div>');
|
||||
|
||||
|
||||
// JS FieldChange trigger (:input are not always at the same depth)
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$("#{$this->oField->GetGlobalId()}").off("change keyup").on("change keyup", function(){
|
||||
var me = this;
|
||||
|
||||
$(this).closest(".field_set").trigger("field_change", {
|
||||
id: $(me).attr("id"),
|
||||
name: $(me).closest(".form_field").attr("data-field-id"),
|
||||
value: $(me).val()
|
||||
});
|
||||
});
|
||||
EOF
|
||||
);
|
||||
|
||||
// Attaching JS widget
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").portal_form_field({
|
||||
'validators': {$this->GetValidatorsAsJson()}
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
return $oOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an hierarchical search button
|
||||
*
|
||||
* @param RenderingOutput $oOutput
|
||||
*/
|
||||
protected function RenderHierarchicalSearch(RenderingOutput &$oOutput)
|
||||
{
|
||||
if ($this->oField->GetHierarchical())
|
||||
{
|
||||
$sHierarchicalButtonId = 's_hi_' . $this->oField->GetGlobalId();
|
||||
$sEndpoint = str_replace('-sMode-', 'hierarchy', $this->oField->GetSearchEndpoint());
|
||||
|
||||
$oOutput->AddHtml('<button type="button" class="btn btn-default" id="' . $sHierarchicalButtonId . '"><span class="glyphicon glyphicon-ext-hierarchy"></span></button>');
|
||||
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$('#{$sHierarchicalButtonId}').off('click').on('click', function(){
|
||||
// Creating a new modal
|
||||
// Note : This could be better if we check for an existing modal first instead of always creating a new one
|
||||
var oModalElem = $('#modal-for-all').clone();
|
||||
oModalElem.attr('id', '').attr('data-source-element', '{$sHierarchicalButtonId}').appendTo('body');
|
||||
// Resizing to small modal
|
||||
oModalElem.find('.modal-dialog').removeClass('modal-lg').addClass('modal-sm');
|
||||
// Loading content
|
||||
oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
|
||||
oModalElem.find('.modal-content').load(
|
||||
'{$sEndpoint}',
|
||||
{
|
||||
sFormPath: '{$this->oField->GetFormPath()}',
|
||||
sFieldId: '{$this->oField->GetId()}'
|
||||
}
|
||||
);
|
||||
oModalElem.modal('show');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an regular search button
|
||||
*
|
||||
* @param RenderingOutput $oOutput
|
||||
*/
|
||||
protected function RenderRegularSearch(RenderingOutput &$oOutput)
|
||||
{
|
||||
$sSearchButtonId = 's_rg_' . $this->oField->GetGlobalId();
|
||||
$sEndpoint = str_replace('-sMode-', 'from-attribute', $this->oField->GetSearchEndpoint());
|
||||
|
||||
$oOutput->AddHtml('<button type="button" class="btn btn-default" id="' . $sSearchButtonId . '"><span class="glyphicon glyphicon-search" aria-hidden="true"></span></button>');
|
||||
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$('#{$sSearchButtonId}').off('click').on('click', function(){
|
||||
// Creating a new modal
|
||||
var oModalElem;
|
||||
if($('.modal[data-source-element="{$sSearchButtonId}"]').length === 0)
|
||||
{
|
||||
oModalElem = $('#modal-for-all').clone();
|
||||
oModalElem.attr('id', '').attr('data-source-element', '{$sSearchButtonId}').appendTo('body');
|
||||
}
|
||||
else
|
||||
{
|
||||
oModalElem = $('.modal[data-source-element="{$sSearchButtonId}"]').first();
|
||||
}
|
||||
// Resizing to small modal
|
||||
oModalElem.find('.modal-dialog').removeClass('modal-sm').addClass('modal-lg');
|
||||
// Loading content
|
||||
oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
|
||||
oModalElem.find('.modal-content').load(
|
||||
'{$sEndpoint}',
|
||||
{
|
||||
sFormPath: '{$this->oField->GetFormPath()}',
|
||||
sFieldId: '{$this->oField->GetId()}'
|
||||
}
|
||||
);
|
||||
oModalElem.modal('show');
|
||||
});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,7 +46,6 @@ class BsSimpleFieldRenderer extends FieldRenderer
|
||||
$sFieldClass = get_class($this->oField);
|
||||
$sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : '';
|
||||
|
||||
// TODO : Shouldn't we have a field type so we don't have to maintain FQN classname ?
|
||||
// Rendering field in edition mode
|
||||
if (!$this->oField->GetReadOnly() && !$this->oField->GetHidden())
|
||||
{
|
||||
@@ -64,15 +63,59 @@ class BsSimpleFieldRenderer extends FieldRenderer
|
||||
break;
|
||||
|
||||
case 'Combodo\\iTop\\Form\\Field\\TextAreaField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\CaseLogField':
|
||||
$bRichEditor = ($this->oField->GetFormat() === TextAreaField::ENUM_FORMAT_HTML);
|
||||
|
||||
|
||||
$oOutput->AddHtml('<div class="form-group ' . $sFieldMandatoryClass . '">');
|
||||
if ($this->oField->GetLabel() !== '')
|
||||
{
|
||||
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')->AddHtml($this->oField->GetLabel(), true)->AddHtml('</label>');
|
||||
}
|
||||
$oOutput->AddHtml('<div class="help-block"></div>');
|
||||
// First the edition area
|
||||
$oOutput->AddHtml('<div>');
|
||||
$oOutput->AddHtml('<textarea id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" class="form-control" rows="8">' . $this->oField->GetCurrentValue() . '</textarea>');
|
||||
$oOutput->AddHtml('</div>');
|
||||
// Then the previous entries if necessary
|
||||
if ($sFieldClass === 'Combodo\\iTop\\Form\\Field\\CaseLogField')
|
||||
{
|
||||
$aEntries = $this->oField->GetEntries();
|
||||
if (count($aEntries) > 0)
|
||||
{
|
||||
$oOutput->AddHtml('<div>');
|
||||
for ($i = 0; $i < count($aEntries); $i++)
|
||||
{
|
||||
$sEntryDate = $aEntries[$i]['date'];
|
||||
$sEntryUser = $aEntries[$i]['user_login'];
|
||||
$sEntryHeader = Dict::Format('UI:CaseLog:Header_Date_UserName', $sEntryDate, $sEntryUser);
|
||||
|
||||
// Only the last 2 entries are expanded by default
|
||||
$sEntryContentExpanded = ($i < 2) ? 'true' : 'false';
|
||||
$sEntryHeaderButtonClass = ($i < 2) ? '' : 'collapsed';
|
||||
$sEntryContentClass = ($i < 2) ? 'in' : '';
|
||||
$sEntryContentId = 'caselog_field_entry_content-' . $this->oField->GetGlobalId() . '-' . $i;
|
||||
|
||||
// Note : We use CKEditor stylesheet to format this
|
||||
$oOutput->AddHtml(
|
||||
<<<EOF
|
||||
<div class="caselog_field_entry cke_inner">
|
||||
<div class="caselog_field_entry_header">
|
||||
{$sEntryHeader}
|
||||
<div class="pull-right">
|
||||
<span class="caselog_field_entry_button {$sEntryHeaderButtonClass}" data-toggle="collapse" href="#{$sEntryContentId}" aria-expanded="{$sEntryContentExpanded}" aria-controls="{$sEntryContentId}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="caselog_field_entry_content collapse {$sEntryContentClass}" id="{$sEntryContentId}">
|
||||
{$aEntries[$i]['message_html']}
|
||||
</div>
|
||||
</div>
|
||||
EOF
|
||||
);
|
||||
}
|
||||
$oOutput->AddHtml('</div>');
|
||||
}
|
||||
}
|
||||
|
||||
$oOutput->AddHtml('</div>');
|
||||
// Some additional stuff if we are displaying it with a rich editor
|
||||
if ($bRichEditor)
|
||||
@@ -145,7 +188,7 @@ EOF
|
||||
// ... and in read-only mode (or hidden)
|
||||
else
|
||||
{
|
||||
// ... specific rendering for fields with mulltiple values
|
||||
// ... specific rendering for fields with multiple values
|
||||
if (($this->oField instanceof Combodo\iTop\Form\Field\MultipleChoicesField) && ($this->oField->GetMultipleValuesEnabled()))
|
||||
{
|
||||
// TODO
|
||||
@@ -190,7 +233,6 @@ EOF
|
||||
case 'Combodo\\iTop\\Form\\Field\\RadioField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\SelectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\SelectObjectField': // TODO : This should be check for external key, as we would display it differently
|
||||
$aFieldChoices = $this->oField->GetChoices();
|
||||
$sFieldValue = (isset($aFieldChoices[$this->oField->GetCurrentValue()])) ? $aFieldChoices[$this->oField->GetCurrentValue()] : Dict::S('UI:UndefinedObject');
|
||||
|
||||
@@ -216,9 +258,9 @@ EOF
|
||||
{
|
||||
case 'Combodo\\iTop\\Form\\Field\\StringField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\TextAreaField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\CaseLogField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\SelectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\SelectObjectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\HiddenField':
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
@@ -272,7 +314,6 @@ EOF
|
||||
case 'Combodo\\iTop\\Form\\Field\\StringField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\SelectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\MultipleSelectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\SelectObjectField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\HiddenField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\RadioField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\CheckboxField':
|
||||
@@ -283,6 +324,7 @@ EOF
|
||||
);
|
||||
break;
|
||||
case 'Combodo\\iTop\\Form\\Field\\TextAreaField':
|
||||
case 'Combodo\\iTop\\Form\\Field\\CaseLogField':
|
||||
$oOutput->AddJs(
|
||||
<<<EOF
|
||||
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").portal_form_field_html($sFormFieldOptions);
|
||||
|
||||
@@ -28,7 +28,7 @@ class BsSubFormFieldRenderer extends FieldRenderer
|
||||
public function Render()
|
||||
{
|
||||
$oOutput = new RenderingOutput();
|
||||
|
||||
|
||||
if (($this->oField->GetLabel() !== null) && ($this->oField->GetLabel() !== ''))
|
||||
{
|
||||
$oOutput->AddHtml('<fieldset><legend>' . $this->oField->GetLabel() . '</legend>');
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
namespace Combodo\iTop\Renderer;
|
||||
|
||||
use \Dict;
|
||||
use \DBObject;
|
||||
use \Combodo\iTop\Form\Field\Field;
|
||||
|
||||
@@ -61,6 +62,32 @@ abstract class FieldRenderer
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON encoded string that contains the field's validators as an array.
|
||||
*
|
||||
* eg :
|
||||
* {
|
||||
* validator_id_1 : {reg_exp: /[0-9]/, message: "Error message"},
|
||||
* validator_id_2 : {reg_exp: /[a-z]/, message: "Another error message"},
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function GetValidatorsAsJson()
|
||||
{
|
||||
$aValidators = array();
|
||||
foreach ($this->oField->GetValidators() as $oValidator)
|
||||
{
|
||||
$aValidators[$oValidator::GetName()] = array(
|
||||
'reg_exp' => $oValidator->GetRegExp(),
|
||||
'message' => Dict::S($oValidator->GetErrorMessage())
|
||||
);
|
||||
}
|
||||
// - Formatting options
|
||||
return json_encode($aValidators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a Field as a RenderingOutput
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user