From f718b26b7bacf28af7411d4b50d2bfc7eec9e1a0 Mon Sep 17 00:00:00 2001 From: acognet Date: Wed, 4 Nov 2020 13:36:20 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B02847=20-=20Tranform=20old=20itop=20datat?= =?UTF-8?q?able=20to=20jquery=20DataTable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/cmdbabstract.class.inc.php | 143 +---- application/dashboard.class.inc.php | 2 + application/datatable.class.inc.php | 398 +----------- application/displayblock.class.inc.php | 36 +- application/shortcut.class.inc.php | 1 + .../ui.linksdirectwidget.class.inc.php | 5 +- application/ui.linkswidget.class.inc.php | 17 +- .../ui.searchformforeignkeys.class.inc.php | 5 +- core/attributedef.class.inc.php | 26 + css/backoffice/components/_all.scss | 1 + css/backoffice/components/_datatable.scss | 85 +++ css/backoffice/components/_title.scss | 26 + css/light-grey.scss | 125 ++-- .../portal/templates/layout.html.twig | 26 +- dictionaries/cs.dictionary.itop.ui.php | 3 +- dictionaries/da.dictionary.itop.ui.php | 3 +- dictionaries/de.dictionary.itop.ui.php | 3 +- dictionaries/en.dictionary.itop.ui.php | 3 +- dictionaries/es_cr.dictionary.itop.ui.php | 3 +- dictionaries/fr.dictionary.itop.ui.php | 3 +- dictionaries/ja.dictionary.itop.ui.php | 3 +- dictionaries/nl.dictionary.itop.ui.php | 3 +- dictionaries/pt_br.dictionary.itop.ui.php | 3 +- dictionaries/ru.dictionary.itop.ui.php | 3 +- dictionaries/sk.dictionary.itop.ui.php | 3 +- .../en.dictionary.itop.datatable.php | 33 + dictionaries/zh_cn.dictionary.itop.ui.php | 3 +- documentation/deprecations.md | 7 +- js/dataTables.pipeline.js | 125 ++++ js/dataTables.settings.js | 359 +++++++++++ js/datatable.js | 1 + js/linkswidget.js | 12 +- js/search/search_form_criteria.js | 2 +- js/search/search_form_handler.js | 18 +- js/utils.js | 8 +- lib/composer/autoload_classmap.php | 4 +- lib/composer/autoload_static.php | 4 +- .../css/dataTables.bootstrap.min.css | 0 .../css/fixedHeader.bootstrap.min.css | 0 .../css/responsive.bootstrap.min.css | 0 .../datatables/css/scroller.bootstrap.min.css | 0 .../datatables/css/select.bootstrap.min.css | 0 .../datatables/css/select.dataTables.min.css | 0 .../datatables/images/sort_asc.png | Bin .../datatables/images/sort_asc_disabled.png | Bin .../datatables/images/sort_both.png | Bin .../datatables/images/sort_desc.png | Bin .../datatables/images/sort_desc_disabled.png | Bin .../datatables/js/dataTables.bootstrap.min.js | 0 .../js/dataTables.fixedHeader.min.js | 0 .../js/dataTables.responsive.min.js | 0 .../datatables/js/dataTables.scroller.min.js | 0 .../datatables/js/dataTables.select.min.js | 0 .../datatables/js/datetime-moment.js | 0 .../datatables/js/jquery.dataTables.min.js | 0 pages/UI.php | 27 +- pages/ajax.render.php | 188 +++++- pages/ajax.searchform.php | 2 +- .../UI/Component/DataTable/DataTable.php | 127 ++++ .../Component/DataTable/DataTableFactory.php | 589 ++++++++++++++++++ .../Component/DataTable/DataTableSettings.php | 355 +++++++++++ sources/application/WebPage/NiceWebPage.php | 71 +-- .../search/searchform.class.inc.php | 52 +- .../components/datatable/layout.html.twig | 61 ++ templates/components/datatable/layout.js.twig | 256 ++++++++ templates/components/panel/layout.html.twig | 4 +- .../expected/css/light-grey.scss | 1 - 67 files changed, 2506 insertions(+), 732 deletions(-) create mode 100644 css/backoffice/components/_datatable.scss create mode 100644 dictionaries/ui/components/en.dictionary.itop.datatable.php create mode 100644 js/dataTables.pipeline.js create mode 100644 js/dataTables.settings.js rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/css/dataTables.bootstrap.min.css (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/css/fixedHeader.bootstrap.min.css (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/css/responsive.bootstrap.min.css (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/css/scroller.bootstrap.min.css (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/css/select.bootstrap.min.css (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/css/select.dataTables.min.css (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/images/sort_asc.png (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/images/sort_asc_disabled.png (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/images/sort_both.png (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/images/sort_desc.png (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/images/sort_desc_disabled.png (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/js/dataTables.bootstrap.min.js (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/js/dataTables.fixedHeader.min.js (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/js/dataTables.responsive.min.js (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/js/dataTables.scroller.min.js (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/js/dataTables.select.min.js (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/js/datetime-moment.js (100%) rename {datamodels/2.x/itop-portal-base/portal/public/lib => lib}/datatables/js/jquery.dataTables.min.js (100%) create mode 100644 sources/application/UI/Component/DataTable/DataTable.php create mode 100644 sources/application/UI/Component/DataTable/DataTableFactory.php create mode 100644 sources/application/UI/Component/DataTable/DataTableSettings.php create mode 100644 templates/components/datatable/layout.html.twig create mode 100644 templates/components/datatable/layout.js.twig diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 71b3e2040..05e69376e 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -20,6 +20,7 @@ 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\DataContainer\DataContainerFactory; use Combodo\iTop\Application\UI\Component\Field\Field; use Combodo\iTop\Application\UI\Component\FieldSet\FieldSet; use Combodo\iTop\Application\UI\Component\Form\Form; @@ -57,6 +58,7 @@ require_once(APPROOT.'sources/application/search/criterionconversionabstract.cla require_once(APPROOT.'sources/application/search/criterionconversion/criteriontooql.class.inc.php'); require_once(APPROOT.'sources/application/search/criterionconversion/criteriontosearchform.class.inc.php'); +use Combodo\iTop\Application\UI\Component\DataTable\DataTableFactory; /** * Class cmdbAbstractObject */ @@ -1042,7 +1044,7 @@ HTML */ public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { - $oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams)); + $oPage->AddUiBlock(self::GetDisplaySetBlock($oPage, $oSet, $aExtraParams)); } /** @@ -1100,14 +1102,16 @@ HTML * * @throws \CoreException*@throws \Exception * @throws \ApplicationException + * @deprecated 3.0.0 use GetDisplaySetBlock */ public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { - if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) - { - return self::GetDisplaySetForPrinting($oPage, $oSet, $aExtraParams); + $oPage->AddUiBlock(GetDisplaySetBlock( $oPage, $oSet, $aExtraParams )); + return ""; } + public static function GetDisplaySetBlock(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + { if (empty($aExtraParams['currentId'])) { $iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !! @@ -1117,136 +1121,10 @@ HTML $iListId = $aExtraParams['currentId']; } - // Initialize and check the parameters - $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; - $sLinkageAttribute = isset($aExtraParams['link_attr']) ? $aExtraParams['link_attr'] : ''; - $iLinkedObjectId = isset($aExtraParams['object_id']) ? $aExtraParams['object_id'] : 0; - $sTargetAttr = isset($aExtraParams['target_attr']) ? $aExtraParams['target_attr'] : ''; - if (!empty($sLinkageAttribute)) - { - if ($iLinkedObjectId == 0) - { - // if 'links' mode is requested the id of the object to link to must be specified - throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); - } - if ($sTargetAttr == '') - { - // if 'links' mode is requested the d of the object to link to must be specified - throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_target_attr')); - } - } - $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; - $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; - $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; + $oDataTable = DataTableFactory::MakeForResult($oPage, $iListId, $oSet, $aExtraParams); - $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', - trim($aExtraParams['extra_fields'])) : array(); - $aExtraFields = array(); - foreach($aExtraFieldsRaw as $sFieldName) - { - // Ignore attributes not of the main queried class - if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) - { - $sClassAlias = $aMatches[1]; - $sAttCode = $aMatches[2]; - if ($sClassAlias == $oSet->GetFilter()->GetClassAlias()) - { - $aExtraFields[] = $sAttCode; - } - } - else - { - $aExtraFields[] = $sFieldName; - } - } - $sClassName = $oSet->GetFilter()->GetClass(); - $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; - if ($sZListName !== false) - { - $aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); - $aList = array_merge($aList, $aExtraFields); - } - else - { - $aList = $aExtraFields; - } - - // Filter the list to removed linked set since we are not able to display them here - foreach($aList as $index => $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); - if ($oAttDef instanceof AttributeLinkedSet) - { - // Removed from the display list - unset($aList[$index]); - } - } - - - if (!empty($sLinkageAttribute)) - { - // The set to display is in fact a set of links between the object specified in the $sLinkageAttribute - // and other objects... - // The display will then group all the attributes related to the link itself: - // | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n | - $aDisplayList = array(); - $aAttDefs = MetaModel::ListAttributeDefs($sClassName); - assert(isset($aAttDefs[$sLinkageAttribute])); - $oAttDef = $aAttDefs[$sLinkageAttribute]; - assert($oAttDef->IsExternalKey()); - // First display all the attributes specific to the link record - foreach($aList as $sLinkAttCode) - { - $oLinkAttDef = $aAttDefs[$sLinkAttCode]; - if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) - { - $aDisplayList[] = $sLinkAttCode; - } - } - // Then display all the attributes neither specific to the link record nor to the 'linkage' object (because the latter are constant) - foreach($aList as $sLinkAttCode) - { - $oLinkAttDef = $aAttDefs[$sLinkAttCode]; - if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute)) - || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) - { - $aDisplayList[] = $sLinkAttCode; - } - } - // First display all the attributes specific to the link - // Then display all the attributes linked to the other end of the relationship - $aList = $aDisplayList; - } - - $sSelectMode = 'none'; - if ($bSelectMode) - { - $sSelectMode = $bSingleSelectMode ? 'single' : 'multiple'; - } - - $sClassAlias = $oSet->GetClassAlias(); - $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; - - $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; - $aClassAliases = array($sClassAlias => $sClassName); - $oDataTable = new DataTable($iListId, $oSet, $aClassAliases, $sTableId); - $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); - - if ($bDisplayLimit) - { - $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', - MetaModel::GetConfig()->GetMinDisplayLimit()); - $oSettings->iDefaultPageSize = $iDefaultPageSize; - } - else - { - $oSettings->iDefaultPageSize = 0; - } - $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - - return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); + return $oDataTable; } - /** * @param \WebPage $oPage * @param \CMDBObjectSet $oSet @@ -1265,6 +1143,7 @@ HTML * @throws \MissingQueryArgument * @throws \MySQLException * @throws \MySQLHasGoneAwayException + * @deprecated since 3.0.0 */ public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php index 3a0428054..3539baa8d 100644 --- a/application/dashboard.class.inc.php +++ b/application/dashboard.class.inc.php @@ -20,6 +20,8 @@ use Combodo\iTop\Application\UI\Component\Button\ButtonFactory; use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar; +use Combodo\iTop\Application\UI\Component\DataTable\DataTableSettings; + require_once(APPROOT.'application/dashboardlayout.class.inc.php'); require_once(APPROOT.'application/dashlet.class.inc.php'); require_once(APPROOT.'core/modelreflection.class.inc.php'); diff --git a/application/datatable.class.inc.php b/application/datatable.class.inc.php index a975e3ad3..0765c4190 100644 --- a/application/datatable.class.inc.php +++ b/application/datatable.class.inc.php @@ -28,7 +28,7 @@ class DataTable protected $sTableId; // identifier for saving the settings (combined with the class aliases) protected $oSet; // The set of objects to display protected $aClassAliases; // The aliases (alias => class) inside the set - protected $iNbObjects; // Total number of objects inthe set + protected $iNbObjects; // Total number of objects in the set protected $bUseCustomSettings; // Whether or not the current display uses custom settings protected $oDefaultSettings; // the default settings for displaying such a list protected $bShowObsoleteData; @@ -211,7 +211,7 @@ class DataTable return $sHtml; } - + /** * When refreshing the body of a paginated table, get the rows of the table (inside the TBODY) * return string The HTML rows to insert inside the node @@ -525,6 +525,7 @@ EOF; return $aAttribs; } + /** * @param $aColumns * @param $sSelectMode @@ -872,396 +873,3 @@ class PrintableDataTable extends DataTable return $sHtml; } } - -class DataTableSettings implements Serializable -{ - public $aClassAliases; - public $sTableId; - public $iDefaultPageSize; - public $aColumns; - - - /** - * DataTableSettings constructor. - * - * @param $aClassAliases - * @param null $sTableId - */ - public function __construct($aClassAliases, $sTableId = null) - { - $this->aClassAliases = $aClassAliases; - $this->sTableId = $sTableId; - $this->iDefaultPageSize = 10; - $this->aColumns = array(); - } - - /** - * @param $iDefaultPageSize - * @param $aSortOrder - * @param $aColumns - */ - protected function Init($iDefaultPageSize, $aSortOrder, $aColumns) - { - $this->iDefaultPageSize = $iDefaultPageSize; - $this->aColumns = $aColumns; - $this->FixVisibleColumns(); - } - - /** - * @return string - */ - public function serialize() - { - // Save only the 'visible' columns - $aColumns = array(); - foreach($this->aClassAliases as $sAlias => $sClass) - { - $aColumns[$sAlias] = array(); - foreach($this->aColumns[$sAlias] as $sAttCode => $aData) - { - unset($aData['label']); // Don't save the display name - unset($aData['alias']); // Don't save the alias (redundant) - unset($aData['code']); // Don't save the code (redundant) - if ($aData['checked']) - { - $aColumns[$sAlias][$sAttCode] = $aData; - } - } - } - return serialize( - array( - 'iDefaultPageSize' => $this->iDefaultPageSize, - 'aColumns' => $aColumns, - ) - ); - } - - /** - * @param string $sData - * - * @throws \Exception - */ - public function unserialize($sData) - { - $aData = unserialize($sData); - $this->iDefaultPageSize = $aData['iDefaultPageSize']; - $this->aColumns = $aData['aColumns']; - foreach($this->aClassAliases as $sAlias => $sClass) - { - foreach($this->aColumns[$sAlias] as $sAttCode => $aData) - { - $aFieldData = false; - if ($sAttCode == '_key_') - { - $aFieldData = $this->GetFieldData($sAlias, $sAttCode, null, true /* bChecked */, $aData['sort']); - } - else if (MetaModel::isValidAttCode($sClass, $sAttCode)) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $aData['sort']); - } - - if ($aFieldData) - { - $this->aColumns[$sAlias][$sAttCode] = $aFieldData; - } - else - { - unset($this->aColumns[$sAlias][$sAttCode]); - } - } - } - $this->FixVisibleColumns(); - } - - /** - * @param $aClassAliases - * @param $bViewLink - * @param $aDefaultLists - * - * @return \DataTableSettings - * @throws \CoreException - * @throws \DictExceptionMissingString - */ - public static function GetDataModelSettings($aClassAliases, $bViewLink, $aDefaultLists) - { - $oSettings = new DataTableSettings($aClassAliases); - // Retrieve the class specific settings for each class/alias based on the 'list' ZList - //TODO let the caller pass some other default settings (another Zlist, extre fields...) - $aColumns = array(); - foreach ($aClassAliases as $sAlias => $sClass) - { - if ($aDefaultLists == null) - { - $aList = cmdbAbstract::FlattenZList(MetaModel::GetZListItems($sClass, 'list')); - } - else - { - $aList = $aDefaultLists[$sAlias]; - } - - $aSortOrder = MetaModel::GetOrderByDefault($sClass); - if ($bViewLink) - { - $sSort = 'none'; - if(array_key_exists('friendlyname', $aSortOrder)) - { - $sSort = $aSortOrder['friendlyname'] ? 'asc' : 'desc'; - } - $sNormalizedFName = MetaModel::NormalizeFieldSpec($sClass, 'friendlyname'); - if(array_key_exists($sNormalizedFName, $aSortOrder)) - { - $sSort = $aSortOrder[$sNormalizedFName] ? 'asc' : 'desc'; - } - - $aColumns[$sAlias]['_key_'] = $oSettings->GetFieldData($sAlias, '_key_', null, true /* bChecked */, $sSort); - } - foreach($aList as $sAttCode) - { - $sSort = 'none'; - if(array_key_exists($sAttCode, $aSortOrder)) - { - $sSort = $aSortOrder[$sAttCode] ? 'asc' : 'desc'; - } - $oAttDef = Metamodel::GetAttributeDef($sClass, $sAttCode); - $aFieldData = $oSettings->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $sSort); - if ($aFieldData) $aColumns[$sAlias][$sAttCode] = $aFieldData; - } - } - $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit()); - $oSettings->Init($iDefaultPageSize, $aSortOrder, $aColumns); - return $oSettings; - } - - /** - * @throws \CoreException - */ - protected function FixVisibleColumns() - { - foreach($this->aClassAliases as $sAlias => $sClass) - { - if (!isset($this->aColumns[$sAlias])) - { - continue; - } - foreach($this->aColumns[$sAlias] as $sAttCode => $aData) - { - // Remove non-existent columns - // TODO: check if the existing ones are still valid (in case their type changed) - if (($sAttCode != '_key_') && (!MetaModel::IsValidAttCode($sClass, $sAttCode))) - { - unset($this->aColumns[$sAlias][$sAttCode]); - } - } - $aList = MetaModel::ListAttributeDefs($sClass); - - // Add the other (non visible ones), sorted in alphabetical order - $aTempData = array(); - foreach($aList as $sAttCode => $oAttDef) - { - if ( (!array_key_exists($sAttCode, $this->aColumns[$sAlias])) && (!($oAttDef instanceof AttributeLinkedSet || $oAttDef instanceof AttributeDashboard))) - { - $aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, false /* bChecked */, 'none'); - if ($aFieldData) $aTempData[$aFieldData['label']] = $aFieldData; - } - } - ksort($aTempData); - foreach($aTempData as $sLabel => $aFieldData) - { - $this->aColumns[$sAlias][$aFieldData['code']] = $aFieldData; - } - } - } - - /** - * @param $aClassAliases - * @param null $sTableId - * @param bool $bOnlyOnTable - * - * @return \DataTableSettings|null - * @throws \Exception - */ - static public function GetTableSettings($aClassAliases, $sTableId = null, $bOnlyOnTable = false) - { - $pref = null; - $oSettings = new DataTableSettings($aClassAliases, $sTableId); - - if ($sTableId != null) - { - // An identified table, let's fetch its own settings (if any) - $pref = appUserPreferences::GetPref($oSettings->GetPrefsKey($sTableId), null); - } - - if ($pref == null) - { - if (!$bOnlyOnTable) - { - // Try the global preferred values for this class / set of classes - $pref = appUserPreferences::GetPref($oSettings->GetPrefsKey(null), null); - } - if ($pref == null) - { - // no such settings, use the default values provided by the data model - return null; - } - } - $oSettings->unserialize($pref); - - return $oSettings; - } - - /** - * @return array - */ - public function GetSortOrder() - { - $aSortOrder = array(); - foreach($this->aColumns as $sAlias => $aColumns) - { - foreach($aColumns as $aColumn) - { - if ($aColumn['sort'] != 'none') - { - $sCode = ($aColumn['code'] == '_key_') ? 'friendlyname' : $aColumn['code']; - $aSortOrder[$sCode] = ($aColumn['sort']=='asc'); // true for ascending, false for descending - } - } - break; // TODO: For now the Set object supports only sorting on the first class of the set - } - return $aSortOrder; - } - - /** - * @param null $sTargetTableId - * - * @return bool - */ - public function Save($sTargetTableId = null) - { - $sSaveId = is_null($sTargetTableId) ? $this->sTableId : $sTargetTableId; - if ($sSaveId == null) return false; // Cannot save, the table is not identified, use SaveAsDefault instead - - $sSettings = $this->serialize(); - appUserPreferences::SetPref($this->GetPrefsKey($sSaveId), $sSettings); - return true; - } - - /** - * @return bool - */ - public function SaveAsDefault() - { - $sSettings = $this->serialize(); - appUserPreferences::SetPref($this->GetPrefsKey(null), $sSettings); - return true; - } - - - /** - * Clear the preferences for this particular table - * @param $bResetAll boolean If true,the settings for all tables of the same class(es)/alias(es) are reset - */ - public function ResetToDefault($bResetAll) - { - if (($this->sTableId == null) && (!$bResetAll)) return false; // Cannot reset, the table is not identified, use force $bResetAll instead - if ($bResetAll) - { - // Turn the key into a suitable PCRE pattern - $sKey = $this->GetPrefsKey(null); - $sPattern = str_replace(array('|'), array('\\|'), $sKey); // escape the | character - $sPattern = '#^'.str_replace(array('*'), array('.*'), $sPattern).'$#'; // Don't use slash as the delimiter since it's used in our key to delimit aliases - appUserPreferences::UnsetPref($sPattern, true); - } - else - { - appUserPreferences::UnsetPref($this->GetPrefsKey($this->sTableId), false); - } - return true; - } - - /** - * @param null $sTableId - * - * @return string - */ - protected function GetPrefsKey($sTableId = null) - { - return static::GetAppUserPreferenceKey($this->aClassAliases, $sTableId); - } - - public static function GetAppUserPreferenceKey($aClassAliases, $sTableId) - { - if ($sTableId === null) - { - $sTableId = '*'; - } - - $aKeys = array(); - foreach($aClassAliases as $sAlias => $sClass) - { - $aKeys[] = $sAlias.'-'.$sClass; - } - return implode('/', $aKeys).'|'.$sTableId; - } - - /** - * @param $sAlias - * @param $sAttCode - * @param $oAttDef - * @param $bChecked - * @param $sSort - * - * @return array|bool - * @throws \CoreException - * @throws \DictExceptionMissingString - */ - protected function GetFieldData($sAlias, $sAttCode, $oAttDef, $bChecked, $sSort) - { - $ret = false; - if ($sAttCode == '_key_') - { - $sLabel = Dict::Format('UI:ExtKey_AsLink', MetaModel::GetName($this->aClassAliases[$sAlias])); - $ret = array( - 'label' => $sLabel, - 'checked' => true, - 'disabled' => true, - 'alias' => $sAlias, - 'code' => $sAttCode, - 'sort' => $sSort, - ); - } - else if (!$oAttDef->IsLinkSet()) - { - $sLabel = $oAttDef->GetLabel(); - if ($oAttDef->IsExternalKey()) - { - $sLabel = Dict::Format('UI:ExtKey_AsLink', $oAttDef->GetLabel()); - } - else if ($oAttDef->IsExternalField()) - { - if ($oAttDef->IsFriendlyName()) - { - $sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel()); - } - else - { - $oExtAttDef = $oAttDef->GetExtAttDef(); - $sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel()); - } - } - elseif ($oAttDef instanceof AttributeFriendlyName) - { - $sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel()); - } - $ret = array( - 'label' => $sLabel, - 'checked' => $bChecked, - 'disabled' => false, - 'alias' => $sAlias, - 'code' => $sAttCode, - 'sort' => $sSort, - ); - } - return $ret; - } -} diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 12b0ec042..0e75a5662 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -20,6 +20,7 @@ use Combodo\iTop\Application\UI\Component\Badge\BadgeFactory; use Combodo\iTop\Application\UI\Component\Button\ButtonFactory; use Combodo\iTop\Application\UI\Component\Dashlet\DashletFactory; +use Combodo\iTop\Application\UI\Component\DataTable\DataTableFactory; use Combodo\iTop\Application\UI\Component\Html\Html; use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar; use Combodo\iTop\Application\UI\iUIBlock; @@ -238,18 +239,15 @@ class DisplayBlock $sFilter = addslashes($this->m_oFilter->serialize(false, $aQueryParams)); // Used either for asynchronous or auto_reload if (!$this->m_bAsynchronous) { // render now - $oHtml->AddHtml("
\n"); try { $oHtml->AddSubBlock($this->GetRenderContent($oPage, $aExtraParams, $sId)); } catch (Exception $e) { IssueLog::Error('Exception during GetDisplay: '.$e->getMessage()); } - $oHtml->AddHtml("
\n"); } else { // render it as an Ajax (asynchronous) call - $oHtml->AddHtml("
\n"); + $oHtml->AddCSSClasses("display_block loading"); $oHtml->AddHtml($oPage->GetP(" ".Dict::S('UI:Loading'))); - $oHtml->AddHtml("
\n"); $oPage->add_script(' $.post("ajax.render.php?style='.$this->m_sStyle.'", { operation: "ajax", filter: "'.$sFilter.'", extra_params: "'.$sExtraParams.'" }, @@ -573,7 +571,16 @@ class DisplayBlock $index++; } $oSet = new CMDBObjectSet($oBlockFilter, array(), $aArgs); - $sHtml .= "".cmdbAbstractObject::GetDisplayExtendedSet($oPage, $oSet, $aExtraParams)."\n"; + if (empty($aExtraParams['currentId'])) + { + $iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !! + } + else + { + $iListId = $aExtraParams['currentId']; + } + $oBlock = DataTableFactory::MakeForRendering( $iListId, $this->m_oSet, $aExtraParams); + $sHtml .= "".render_block($oBlock)."\n"; } } $sHtml .= "\n"; @@ -599,7 +606,15 @@ class DisplayBlock { if($this->m_oSet->CountWithLimit(1) > 0) { - $sHtml .= cmdbAbstractObject::GetDisplayExtendedSet($oPage, $this->m_oSet, $aExtraParams); + if (empty($aExtraParams['currentId'])) + { + $iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !! + } + else + { + $iListId = $aExtraParams['currentId']; + } + $oBlock = DataTableFactory::MakeForObject($oPage, $iListId, $this->m_oSet, $aExtraParams); } else { @@ -618,7 +633,7 @@ class DisplayBlock // The list is made of only 1 class of objects, actions on the list are possible if ( ($this->m_oSet->CountWithLimit(1)> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) { - $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams); + $oBlock = cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams); } else { @@ -784,10 +799,9 @@ class DisplayBlock case 'search': if (!$oPage->IsPrintableVersion()) { - $sHtml .= "
\n"; $aExtraParams['currentId'] = $sId; - $sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams); - $sHtml .= "
\n"; + $oSearchForm = new \Combodo\iTop\Application\Search\SearchForm(); + $oBlock = $oSearchForm->GetSearchFormUIBlock($oPage, $this->m_oSet, $aExtraParams); } break; @@ -1800,7 +1814,7 @@ class MenuBlock extends DisplayBlock } if ($bToolkitMenu) { $sLabel = Dict::S('UI:ConfigureThisList'); - $aActions['iTop::ConfigureList'] = ['label' => $sLabel, 'url' => '#', 'onclick' => "$('#datatable_dlg_{$sId}').dialog('open');"]; + $aActions['iTop::ConfigureList'] = ['label' => $sLabel, 'url' => '#', 'onclick' => "$('#datatable_dlg_datatable_{$sId}').dialog('open');"]; utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $param, $aActions); } break; diff --git a/application/shortcut.class.inc.php b/application/shortcut.class.inc.php index 42d220053..1453966c9 100644 --- a/application/shortcut.class.inc.php +++ b/application/shortcut.class.inc.php @@ -15,6 +15,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with iTop. If not, see +use Combodo\iTop\Application\UI\Component\DataTable\DataTableSettings; /** diff --git a/application/ui.linksdirectwidget.class.inc.php b/application/ui.linksdirectwidget.class.inc.php index de4803312..b11e353e2 100644 --- a/application/ui.linksdirectwidget.class.inc.php +++ b/application/ui.linksdirectwidget.class.inc.php @@ -184,7 +184,7 @@ class UILinksWidgetDirect // For security reasons: check that the "proposed" class is actually a subclass of the linked class // and that the current user is allowed to create objects of this class $sRealClass = ''; - $oPage->add('
'); + //$oPage->add('
'); $aSubClasses = MetaModel::EnumChildClasses($this->sLinkedClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself $aPossibleClasses = array(); foreach($aSubClasses as $sCandidateClass) @@ -294,7 +294,7 @@ class UILinksWidgetDirect */ public function GetObjectsSelectionDlg($oPage, $oCurrentObj, $aAlreadyLinked, $aPrefillFormParam = array()) { - $oPage->add("
\n"); + //$oPage->add("
\n"); $oHiddenFilter = new DBObjectSearch($this->sLinkedClass); if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($this->sLinkedClass, $this->sClass)) { @@ -355,7 +355,6 @@ class UILinksWidgetDirect    -
HTML ); } diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index cdf9a86e4..54048e9ac 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -368,7 +368,8 @@ JS protected function DisplayFormTable(WebPage $oP, $aConfig, $aData) { $sHtml = "m_sAttCode}{$this->m_sNameSuffix}\" value=\"\">"; - $sHtml .= "\n"; + $sHtml .= "
\n"; + $oP->add_ready_script("$('#dt_{$this->m_sAttCode}{$this->m_sNameSuffix}').DataTable({\"language\": {\"emptyTable\": \"".str_replace("\"","\\\"",Dict::S('UI:Message:EmptyList:UseAdd'))."\"},search:false});"); // Header $sHtml .= "\n"; $sHtml .= "\n"; @@ -391,11 +392,11 @@ JS { $sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId); } - $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}_empty_row\">"; + //$sHtml .= "m_sAttCode}{$this->m_sNameSuffix}_empty_row\">"; $sHtml .= "\n"; // Footer - $sHtml .= "
".Dict::S('UI:Message:EmptyList:UseAdd')."
".Dict::S('UI:Message:EmptyList:UseAdd')."
\n"; + $sHtml .= "
\n"; return $sHtml; } @@ -463,6 +464,7 @@ JS $aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly); } $sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm); + // To prevent adding forms inside the main form $sHtmlValue .= "     m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >"; $sHtmlValue .= "   m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget{$this->m_iInputId}.AddObjects();\">m_sAttCode}{$this->m_sNameSuffix}_indicatorAdd\">\n"; @@ -513,7 +515,7 @@ JS */ public function GetObjectPickerDialog($oPage, $oCurrentObj, $sJson, $aAlreadyLinkedIds = array(), $aPrefillFormParam = array()) { - $oPage->add("
\n"); + //$oPage->add("
\n"); $oAlreadyLinkedFilter = new DBObjectSearch($this->m_sRemoteClass); if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0) { @@ -555,14 +557,13 @@ JS

{$sEmptyList}

-    +    + -
HTML ); - $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget{$this->m_iInputId}.UpdateSizes });"); - $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."'});"); + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."' , autoOpen: false, modal: true, resizeStop: oWidget{$this->m_iInputId}.UpdateSizes });"); $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->m_iInputId}.SearchObjectsToAdd);"); $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget{$this->m_iInputId}.UpdateSizes);"); } diff --git a/application/ui.searchformforeignkeys.class.inc.php b/application/ui.searchformforeignkeys.class.inc.php index 80fdb0cb2..8fd25a329 100644 --- a/application/ui.searchformforeignkeys.class.inc.php +++ b/application/ui.searchformforeignkeys.class.inc.php @@ -40,7 +40,6 @@ class UISearchFormForeignKeys */ public function ShowModalSearchForeignKeys($oPage, $sTitle) { - $oPage->add("
\n"); $oFilter = new DBObjectSearch($this->m_sRemoteClass); @@ -65,9 +64,9 @@ class UISearchFormForeignKeys

{$sEmptyList}

-    +    + -
HTML ); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index e3e8cb04c..f7f46ff03 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1273,6 +1273,15 @@ abstract class AttributeDefinition { return (string)$value; } + + /* + * return string + */ + public function GetRenderForDataTable(string $sClassAlias) :string + { + $sRenderFunction = "return data;"; + return $sRenderFunction; + } } class AttributeDashboard extends AttributeDefinition @@ -4906,6 +4915,11 @@ class AttributeEmailAddress extends AttributeString return ''.parent::GetAsHTML($sValue).''; } + public function GetRenderForDataTable(string $sClassAlias) :string + { + $sRenderFunction = "return ' '+data+'' ;"; + return $sRenderFunction; + } } /** @@ -4992,6 +5006,12 @@ class AttributePhoneNumber extends AttributeString return ''.parent::GetAsHTML($sValue).''; } + + public function GetRenderForDataTable(string $sClassAlias) :string + { + $sRenderFunction = "return ' '+data+'' ;"; + return $sRenderFunction; + } } /** @@ -6781,6 +6801,12 @@ class AttributeExternalKey extends AttributeDBFieldVoid return DBObject::MakeHyperLink($this->GetTargetClass(), $sValue); } + + public function GetRenderForDataTable(string $sClassAlias) :string + { + $sRenderFunction = "return 'm_aParams['targetclass']."&id='+data+'\'>'+row['".$sClassAlias."/".$this->m_sCode."_friendlyname']+'' ;"; + return $sRenderFunction; + } } /** diff --git a/css/backoffice/components/_all.scss b/css/backoffice/components/_all.scss index 8998ff7ed..6d066080f 100644 --- a/css/backoffice/components/_all.scss +++ b/css/backoffice/components/_all.scss @@ -18,6 +18,7 @@ @import "input/all"; @import "title"; +@import "datatable"; @import "form"; @import "fieldset"; @import "field"; diff --git a/css/backoffice/components/_datatable.scss b/css/backoffice/components/_datatable.scss new file mode 100644 index 000000000..6ad6b0644 --- /dev/null +++ b/css/backoffice/components/_datatable.scss @@ -0,0 +1,85 @@ +.ibo-datatable { + padding-bottom: 2em; + padding-top: 2em; + + thead tr th, tbody tr td { + line-height: 30px; + padding-right: 1em; + padding-left: 1em; + @extend %ibo-font-ral-nor-100; + + a { + color: $ibo-color-primary-600; + } + } + + tbody tr:nth-child(odd) { + background-color: $ibo-color-secondary-500; + } +} + .dataTables_length + { + position: relative; + float: right; + padding-top: 0.755em; + padding-right: 0.5em; + } + .dataTables_info + { + position: relative; + float: right; + padding-top: 0.755em; + padding-right: 2em; + padding-left: 0em; + } + .select-info{ + padding-left: 2em; + } + + .pagination + { + left: 1.21%; + right: 1.21%; + top: 91.58%; + bottom: 4.85%; + .paginate_button { + margin-left: 1em; + margin-right: 1em; + } + } + .dataTables_paginate + { + /* position: relative;*/ + float: left; + padding-top: 0.755em; + padding-left: 2em; + } + .paginate_button { + box-sizing: border-box; + display: inline-block; + } + .paginate_button a{ + color: $ibo-color-grey-800; + @extend %ibo-font-ral-nor-100; + } + .paginate_button a:hover { + color: $ibo-color-blue-grey-200; + } + .paginate_button.active a { + color: $ibo-color-grey-900; + border: 2px solid var(--ibo-color-grey-300); + background-color: $ibo-color-grey-200; + } +.ibo-datatable-toolbar { + /*position: relative;*/ + height: 30px; +} +.ibo-criterion-area{ + font-size: $ibo-font-size-50 ; +} +.ibo-search-form{ + padding-top: $ibo-panel--spacing-top ; +} +.ibo-form-group{ + position: fixed; +} \ No newline at end of file diff --git a/css/backoffice/components/_title.scss b/css/backoffice/components/_title.scss index de0b60378..64f4402e7 100644 --- a/css/backoffice/components/_title.scss +++ b/css/backoffice/components/_title.scss @@ -79,4 +79,30 @@ $ibo-title--status-dot--border-radius: $ibo-border-radius-full !default; color: map-get($aColors, 'secondary-color'); background-color: map-get($aColors, 'primary-color'); } +} + + + +.ibo-title-for-dashlet { + padding-top: 2em; +} +.ibo-title-for-dashlet--title { + @extend %ibo-font-ral-nor-350; +} + +.ibo-title-for-dashlet--content{ + background-color: $ibo-color-white-100; + border-radius: 5px; + border: 1px solid ; + border-color:$ibo-color-grey-400; + padding-bottom:1em; +} +.ibo-title-separator{ + border-radius: 5px 5px 0px 0px; + border-color:$ibo-color-blue-600; + color:$ibo-color-blue-600; + background-color:$ibo-color-blue-600; + border: 3px solid ; + margin:0; + padding:0; } \ No newline at end of file diff --git a/css/light-grey.scss b/css/light-grey.scss index 0669cdc17..d5dfb3658 100644 --- a/css/light-grey.scss +++ b/css/light-grey.scss @@ -93,66 +93,66 @@ text-decoration: none; } - table.datatable { - width: 100%; - border: 0; - padding: 0; - } + //table.datatable { + // width: 100%; + // border: 0; + // padding: 0; + //} td.menucontainer { text-align: right; } - table.listResults { - padding: 0px; - border-top: 3px solid $frame-background-color; - border-left: 3px solid $frame-background-color; - border-bottom: 3px solid #e6e6e1; - border-right: 3px solid #e6e6e1; - width: 100%; - background-color: $white; - } + //table.listResults { + // padding: 0px; + // border-top: 3px solid $frame-background-color; + // border-left: 3px solid $frame-background-color; + // border-bottom: 3px solid #e6e6e1; + // border-right: 3px solid #e6e6e1; + // width: 100%; + // background-color: $white; + //} - table.listResults td { - padding: 2px; - } + //table.listResults td { + // padding: 2px; + //} - table.listResults td .view-image { + //table.listResults td .view-image { // Counteract the forced dimensions (usefull for displaying in the details view) - width: inherit !important; - height: inherit !important; + // width: inherit !important; + // height: inherit !important; - img { - max-width: 48px !important; - max-height: 48px !important; - display: block; - margin-left: auto; - margin-right: auto; - } - } + // img { + // max-width: 48px !important; + // max-height: 48px !important; + // display: block; + // margin-left: auto; + // margin-right: auto; + // } + //} - table.listResults > tbody > tr.selected > * { - } + //table.listResults > tbody > tr.selected > * { + //} - table.listResults > tbody > tr > * { - transition: background-color 400ms linear; - } + //table.listResults > tbody > tr > * { + // transition: background-color 400ms linear; + //} - table.listResults > tbody > tr:hover > * { - cursor: pointer; - } + //table.listResults > tbody > tr:hover > * { + // cursor: pointer; + //} - table.listResults > tbody > tr.selected:hover > * { + //table.listResults > tbody > tr.selected:hover > * { /* hover on lines is currently done toggling td.hover, and having a rule for links */ - background-color: $brand-primary-lightest; - color: $text-color; - } + // background-color: $brand-primary-lightest; + // color: $text-color; + //} - table.listResults > tbody > tr:hover > * { + //table.listResults > tbody > tr:hover > * { /* hover on lines is currently done toggling td.hover, and having a rule for links */ - background-color: $table-hover-background; - color: $text-color; - } + // background-color: $table-hover-background; + // color: $text-color; + //} .edit-image { .view-image { @@ -278,13 +278,13 @@ color: $text-color; } - th { - font-family: Tahoma, Verdana, Arial, Helvetica; - font-size: 8pt; - color: $complement-color; - height: 20px; - background: $frame-background-color bottom repeat-x; - } + //th { + // font-family: Tahoma, Verdana, Arial, Helvetica; + // font-size: 8pt; + // color: $complement-color; + // height: 20px; + // background: $frame-background-color bottom repeat-x; + //} th.header { cursor: pointer; @@ -986,14 +986,14 @@ } .search_form_handler { - position: relative; - z-index: 10; - width: 100%; - margin-left: auto; - margin-right: auto; - font-size: 12px; - text-align: left; /* To compensate .search_box:text-align */ - border: 1px solid $search-form-container-bg-color; + // position: relative; + // z-index: 10; + // width: 100%; + // margin-left: auto; + // margin-right: auto; + // font-size: 12px; + // text-align: left; /* To compensate .search_box:text-align */ + // border: 1px solid $search-form-container-bg-color; //transition: width 0.3s ease-in-out; /* Sizing reset */ @@ -1137,7 +1137,6 @@ background-color: $white; .sf_criterion_row { - position: relative;; &:not(:first-child) { margin-top: 20px; @@ -1165,6 +1164,11 @@ .sf_criterion_group { display: inline; + .sfc_fg_button, + .sfc_header { + border: 1px solid #E1E7EC; /* Must be equal to .search_form_criteria:margin-bottom + this:padding-bottom */ + border-radius: 3px; + } } } @@ -1197,7 +1201,6 @@ box-shadow: $box-shadow-regular; } - .sfc_form_group, .sfm_content { position: absolute; z-index: -1; diff --git a/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig index f1c429f4d..e6123d6dc 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig @@ -31,12 +31,12 @@ {# - Bootstrap Datetime picker #} {# - Datatables #} - - - - - - + + + + + + {# - Font OpenSans #} {# - Font Combodo #} @@ -112,13 +112,13 @@ {# Moment.js with locales#} {# Datatables #} - - - - - - - + + + + + + + {# Export for Datatables #} diff --git a/dictionaries/cs.dictionary.itop.ui.php b/dictionaries/cs.dictionary.itop.ui.php index a876cb021..45946c3e7 100755 --- a/dictionaries/cs.dictionary.itop.ui.php +++ b/dictionaries/cs.dictionary.itop.ui.php @@ -1167,7 +1167,8 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array( 'UI:ColumnsAndSortOrder' => 'Sloupce a jejich řazení:', 'UI:UseDefaultSettings' => 'Použít výchozí nastavení', 'UI:UseSpecificSettings' => 'Použít následující nastavení:', - 'UI:Display_X_ItemsPerPage' => 'Zobrazit %1$s položek na stránku', + 'UI:Display_X_ItemsPerPage_prefix' => 'Zobrazit', + 'UI:Display_X_ItemsPerPage_suffix' => 'položek na stránku', 'UI:UseSavetheSettings' => 'Uložit nastavení', 'UI:OnlyForThisList' => 'Jen pro tento seznam', 'UI:ForAllLists' => 'Pro všechny seznamy', diff --git a/dictionaries/da.dictionary.itop.ui.php b/dictionaries/da.dictionary.itop.ui.php index a9580962e..ad527e31c 100644 --- a/dictionaries/da.dictionary.itop.ui.php +++ b/dictionaries/da.dictionary.itop.ui.php @@ -1156,7 +1156,8 @@ Ved tilknytningen til en trigger, bliver hver handling tildelt et "rækkefølge" 'UI:ColumnsAndSortOrder' => 'Kolonner og sortering:', 'UI:UseDefaultSettings' => 'Brug de anbefalede indstillinger', 'UI:UseSpecificSettings' => 'Brug følgende indstillinger:', - 'UI:Display_X_ItemsPerPage' => 'Vis %1$s emner per side', + 'UI:Display_X_ItemsPerPage_prefix' => 'Vis', + 'UI:Display_X_ItemsPerPage_suffix' => 'emner per side', 'UI:UseSavetheSettings' => 'Gem indstillinger', 'UI:OnlyForThisList' => 'Kun for denne liste', 'UI:ForAllLists' => 'For alle lister', diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index 65e9ef506..c4bccd924 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -1154,7 +1154,8 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm 'UI:ColumnsAndSortOrder' => 'Spalten und Sortierrheienfolge:', 'UI:UseDefaultSettings' => 'Verwende Default-Einstellungen', 'UI:UseSpecificSettings' => 'Verwende folgende Einstellungen:', - 'UI:Display_X_ItemsPerPage' => '%1$s Elemente pro Seite anzeigen', + 'UI:Display_X_ItemsPerPage_prefix' => '', + 'UI:Display_X_ItemsPerPage_suffix' => 'Elemente pro Seite anzeigen', 'UI:UseSavetheSettings' => 'Einstellungen speichern', 'UI:OnlyForThisList' => 'Nur für diese Liste', 'UI:ForAllLists' => 'Standard für alle Listen', diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 05746117c..58c61c314 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -1173,7 +1173,8 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:ColumnsAndSortOrder' => 'Columns and sort order:', 'UI:UseDefaultSettings' => 'Use the Default Settings', 'UI:UseSpecificSettings' => 'Use the Following Settings:', - 'UI:Display_X_ItemsPerPage' => 'Display %1$s items per page', + 'UI:Display_X_ItemsPerPage_prefix' => 'Display', + 'UI:Display_X_ItemsPerPage_suffix' => 'items per page', 'UI:UseSavetheSettings' => 'Save the Settings', 'UI:OnlyForThisList' => 'Only for this list', 'UI:ForAllLists' => 'Default for all lists', diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 0e5699ad1..d5c1ab9c7 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -1169,7 +1169,8 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden", 'UI:ColumnsAndSortOrder' => 'Columnas y Ordenamiento:', 'UI:UseDefaultSettings' => 'Usar Configuración por Omisión', 'UI:UseSpecificSettings' => 'Usar la Siguiente Configuración:', - 'UI:Display_X_ItemsPerPage' => 'Desplegar %1$s elementos por página', + 'UI:Display_X_ItemsPerPage_prefix' => 'Desplegar', + 'UI:Display_X_ItemsPerPage_suffix' => 'elementos por página', 'UI:UseSavetheSettings' => 'Guardar Configuraciones', 'UI:OnlyForThisList' => 'Sólo esta Lista', 'UI:ForAllLists' => 'Defecto en todas las listas', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 296d3ab2e..6bd6b5e0d 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1153,7 +1153,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:ColumnsAndSortOrder' => 'Colonnes et ordre de tri:', 'UI:UseDefaultSettings' => 'Utiliser les réglages par défaut', 'UI:UseSpecificSettings' => 'Utiliser les réglages suivants:', - 'UI:Display_X_ItemsPerPage' => 'Afficher %1$s éléments par page', + 'UI:Display_X_ItemsPerPage_prefix' => 'Afficher', + 'UI:Display_X_ItemsPerPage_suffix' => 'éléments par page', 'UI:UseSavetheSettings' => 'Enregistrer ces réglages', 'UI:OnlyForThisList' => 'Seulement pour cette liste', 'UI:ForAllLists' => 'Défaut pour toutes les listes', diff --git a/dictionaries/ja.dictionary.itop.ui.php b/dictionaries/ja.dictionary.itop.ui.php index 39644c3ab..a2b90f9c6 100644 --- a/dictionaries/ja.dictionary.itop.ui.php +++ b/dictionaries/ja.dictionary.itop.ui.php @@ -1154,7 +1154,8 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'UI:ColumnsAndSortOrder' => 'カラムと並び順:', 'UI:UseDefaultSettings' => '既定のセッティングを使用', 'UI:UseSpecificSettings' => '次のセッティングを使用:', - 'UI:Display_X_ItemsPerPage' => '1ページに %1$s アイテムを表示', + 'UI:Display_X_ItemsPerPage_prefix' => '1ページに', + 'UI:Display_X_ItemsPerPage_suffix' => 'アイテムを表示', 'UI:UseSavetheSettings' => 'セッティングを保存', 'UI:OnlyForThisList' => 'このリストのみ', 'UI:ForAllLists' => 'すべてのリストのデフォルト', diff --git a/dictionaries/nl.dictionary.itop.ui.php b/dictionaries/nl.dictionary.itop.ui.php index 0991e5bf0..f134d8442 100644 --- a/dictionaries/nl.dictionary.itop.ui.php +++ b/dictionaries/nl.dictionary.itop.ui.php @@ -1177,7 +1177,8 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:ColumnsAndSortOrder' => 'Kolommen en sorteervolgorde:', 'UI:UseDefaultSettings' => 'Gebruik de standaard instellingen', 'UI:UseSpecificSettings' => 'Gebruik de volgende instellingen:', - 'UI:Display_X_ItemsPerPage' => 'Geef %1$s items per pagina weer', + 'UI:Display_X_ItemsPerPage_prefix' => 'Geef', + 'UI:Display_X_ItemsPerPage_suffix' => 'items per pagina weer', 'UI:UseSavetheSettings' => 'Sla de instellingen op', 'UI:OnlyForThisList' => 'Alleen voor deze lijst', 'UI:ForAllLists' => 'Standaard voor alle lijsten', diff --git a/dictionaries/pt_br.dictionary.itop.ui.php b/dictionaries/pt_br.dictionary.itop.ui.php index f435f7944..2d03baa08 100644 --- a/dictionaries/pt_br.dictionary.itop.ui.php +++ b/dictionaries/pt_br.dictionary.itop.ui.php @@ -1167,7 +1167,8 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:ColumnsAndSortOrder' => 'Colunas e ordem de classificação:', 'UI:UseDefaultSettings' => 'Use a configuração padrão', 'UI:UseSpecificSettings' => 'Use as seguintes configurações:', - 'UI:Display_X_ItemsPerPage' => 'Mostrar %1$s itens por página', + 'UI:Display_X_ItemsPerPage_prefix' => 'Mostrar', + 'UI:Display_X_ItemsPerPage_suffix' => 'itens por página', 'UI:UseSavetheSettings' => 'Salvar configurações', 'UI:OnlyForThisList' => 'Somente para esta lista', 'UI:ForAllLists' => 'Para todas as listas', diff --git a/dictionaries/ru.dictionary.itop.ui.php b/dictionaries/ru.dictionary.itop.ui.php index c95697a79..a0d6ba9ff 100644 --- a/dictionaries/ru.dictionary.itop.ui.php +++ b/dictionaries/ru.dictionary.itop.ui.php @@ -1145,7 +1145,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'UI:ColumnsAndSortOrder' => 'Колонки и порядок сортировки:', 'UI:UseDefaultSettings' => 'Использовать настройки по умолчанию', 'UI:UseSpecificSettings' => 'Использовать эти настройки:', - 'UI:Display_X_ItemsPerPage' => 'Показывать %1$s элементов на странице', + 'UI:Display_X_ItemsPerPage_prefix' => 'Показывать', + 'UI:Display_X_ItemsPerPage_suffix' => 'элементов на странице', 'UI:UseSavetheSettings' => 'Сохранить настройки', 'UI:OnlyForThisList' => 'Только для текущего списка', 'UI:ForAllLists' => 'Для всех списков', diff --git a/dictionaries/sk.dictionary.itop.ui.php b/dictionaries/sk.dictionary.itop.ui.php index d2b164b05..8505aeac6 100644 --- a/dictionaries/sk.dictionary.itop.ui.php +++ b/dictionaries/sk.dictionary.itop.ui.php @@ -1156,7 +1156,8 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp 'UI:ColumnsAndSortOrder' => 'Stĺpce a triediaci príkaz:', 'UI:UseDefaultSettings' => 'Použite štandardné nastavenia', 'UI:UseSpecificSettings' => 'Použite nasledovné nastavenia:', - 'UI:Display_X_ItemsPerPage' => 'Zobraziť %1$s položiek na stránku', + 'UI:Display_X_ItemsPerPage_prefix' => 'Zobraziť', + 'UI:Display_X_ItemsPerPage_suffix' => 'položiek na stránku', 'UI:UseSavetheSettings' => 'Uložiť nastavenia', 'UI:OnlyForThisList' => 'Iba pre tento zoznam', 'UI:ForAllLists' => 'Pre všetky zoznamy', diff --git a/dictionaries/ui/components/en.dictionary.itop.datatable.php b/dictionaries/ui/components/en.dictionary.itop.datatable.php new file mode 100644 index 000000000..9268242d4 --- /dev/null +++ b/dictionaries/ui/components/en.dictionary.itop.datatable.php @@ -0,0 +1,33 @@ + 'Please wait...', + 'UI:Datatables:Language:Search' => 'Filter:', + 'UI:Datatables:Language:LengthMenu' => 'Showing _MENU_ out', + 'UI:Datatables:Language:ZeroRecords' => 'No result', + 'UI:Datatables:Language:Info' => 'of _TOTAL_ items', + 'UI:Datatables:Language:InfoEmpty' => 'No information', + 'UI:Datatables:Language:InfoFiltered' => 'filtered out of _MAX_ items', + 'UI:Datatables:Language:EmptyTable' => 'No data available in this table', + 'UI:Datatables:Language:DisplayLength:All' => 'All', + 'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort', + 'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort', +)); \ No newline at end of file diff --git a/dictionaries/zh_cn.dictionary.itop.ui.php b/dictionaries/zh_cn.dictionary.itop.ui.php index 52bca2ff4..32335cd68 100644 --- a/dictionaries/zh_cn.dictionary.itop.ui.php +++ b/dictionaries/zh_cn.dictionary.itop.ui.php @@ -1167,7 +1167,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:ColumnsAndSortOrder' => '列和排序顺序:', 'UI:UseDefaultSettings' => '使用默认配置', 'UI:UseSpecificSettings' => '使用下面的配置:', - 'UI:Display_X_ItemsPerPage' => '每页显示 %1$s 个项目', + 'UI:Display_X_ItemsPerPage_prefix' => '每页显示', + 'UI:Display_X_ItemsPerPage_suffix' => '个项目', 'UI:UseSavetheSettings' => '保存设置', 'UI:OnlyForThisList' => '仅这个列表', 'UI:ForAllLists' => '默认所有列表', diff --git a/documentation/deprecations.md b/documentation/deprecations.md index fcc1d19b9..4ab9c91e3 100644 --- a/documentation/deprecations.md +++ b/documentation/deprecations.md @@ -1,4 +1,4 @@ -# Deprecated in 2.8.0 +# Deprecated in 3.0.0 * TabManager::GetCurrentTabLength() * TabManager::TruncateTab() @@ -6,8 +6,9 @@ * TabManager::RenderIntoContent() * iTopWebPage::SelectTab() +*cmdbabstract->GetDisplaySet +*cmdbabstract->GetDisplayExtendedSet - -# Modified in 2.8.0 +# Modified in 3.0.0 * iTabbedPage::AddTabContainer -> one optional parameter added \ No newline at end of file diff --git a/js/dataTables.pipeline.js b/js/dataTables.pipeline.js new file mode 100644 index 000000000..eb4e8398e --- /dev/null +++ b/js/dataTables.pipeline.js @@ -0,0 +1,125 @@ +// +// Pipelining function for DataTables. To be used to the `ajax` option of DataTables +// +$.fn.dataTable.pipeline = function ( opts ) { + // Configuration options + var conf = $.extend( { + pages: 5, // number of pages to cache + url: '', // script url + data: null, // function or object with parameters to send to the server + // matching how `ajax.data` works in DataTables + method: 'GET' // Ajax HTTP method + }, opts ); + + // Private variables for storing the cache + var cacheLower = -1; + var cacheUpper = null; + var cacheLastRequest = null; + var cacheLastJson = null; + var draw_number = 1; + + return function ( request, drawCallback, settings ) { + var ajax = false; + var requestStart = request.start; + var drawStart = request.start; + var requestLength = request.length; + if(request.start=undefined) + { + requestStart = settings._iDisplayStart; + drawStart = settings._iDisplayStart; + requestLength = settings._iDisplayLength; + } + var requestEnd = requestStart + requestLength; + + if ( settings.clearCache ) { + // API requested that the cache be cleared + ajax = true; + settings.clearCache = false; + } + else if ( cacheLower < 0 || requestStart < cacheLower || requestEnd > cacheUpper ) { + // outside cached data - need to make a request + ajax = true; + } + else if ( JSON.stringify( request.order ) !== JSON.stringify( cacheLastRequest.order ) || + JSON.stringify( request.columns ) !== JSON.stringify( cacheLastRequest.columns ) || + JSON.stringify( request.search ) !== JSON.stringify( cacheLastRequest.search ) + ) { + // properties changed (ordering, columns, searching) + ajax = true; + } + else if(cacheLastJson == undefined || cacheLastJson.length==0) + { + ajax = true; + } + + // Store the request for checking next time around + cacheLastRequest = $.extend( true, {}, request ); + + if ( ajax ) { + // Need data from the server + if ( requestStart < cacheLower ) { + requestStart = requestStart - (requestLength*(conf.pages-1)); + + if ( requestStart < 0 ) { + requestStart = 0; + } + } + + cacheLower = requestStart; + cacheUpper = requestStart + (requestLength * conf.pages); + + request.start = requestStart; + request.length = requestLength*conf.pages; + request.end = requestStart+ requestLength*conf.pages; + + // Provide the same `data` options as DataTables. + if ( typeof conf.data === 'function' ) { + // As a function it is executed with the data object as an arg + // for manipulation. If an object is returned, it is used as the + // data object to submit + var d = conf.data( request ); + if ( d ) { + $.extend( request, d ); + } + } + else if ( $.isPlainObject( conf.data ) ) { + // As an object, the data given extends the default + $.extend( request, conf.data ); + } + return $.ajax( { + "type": conf.method, + "url": conf.url, + "data": request, + "dataType": "json", + "cache": false, + "success": function ( json ) { + cacheLastJson = $.extend(true, {}, json); + + if ( cacheLower != drawStart ) { + json.data.splice( 0, drawStart-cacheLower ); + } + if ( requestLength >= -1 ) { + json.data.splice( requestLength, json.data.length ); + } + drawCallback( json ); + } + } ); + } + else { + json = $.extend( true, {}, cacheLastJson ); + json.draw = request.draw; // Update the echo for each response + json.data.splice( 0, requestStart-cacheLower ); + json.data.splice( requestLength, json.data.length ); + + drawCallback(json); + } + } +}; + +// Register an API method that will empty the pipelined data, forcing an Ajax +// fetch on the next draw (i.e. `table.clearPipeline().draw()`) +$.fn.dataTable.Api.register( 'clearPipeline()', function () { + return this.iterator( 'table', function ( settings ) { + settings.clearCache = true; + } ); +} ); \ No newline at end of file diff --git a/js/dataTables.settings.js b/js/dataTables.settings.js new file mode 100644 index 000000000..53ff1647c --- /dev/null +++ b/js/dataTables.settings.js @@ -0,0 +1,359 @@ +// jQuery UI style "widget" for selecting and sorting "fields" +$(function() +{ + // the widget definition, where "itop" is the namespace, + // "datatable" the widget name + $.widget( "itop.DataTableSettings", +{ + // default options + options: + { + sListId: '', + oColumns: {}, + sSelectMode: '', + sViewLink: 'true', + iPageSize: -1, + oClassAliases: {}, + sTableId : null, + oExtraParams: {}, + sRenderUrl: 'index.php', + oRenderParameters: {}, + oDefaultSettings: {}, + oLabels: { moveup: 'Move Up', movedown: 'Move Down' } + }, + + // the constructor + _create: function(mydatatable, options) + { + this.aDlgStateParams = ['iDefaultPageSize', 'oColumns']; + console.warn('datatablesettings'); + this.element.addClass('itop-datatable'); + + var me = this; + var bViewLink = (this.options.sViewLink == 'true'); + $('#sfl_'+me.options.sListId).fieldsorter({hasKeyColumn: bViewLink, labels: this.options.oLabels, fields: this.options.oColumns, onChange: function() { me._onSpecificSettings(); } }); + $('#datatable_dlg_'+me.options.sListId).find('input[name=page_size]').click(function() { me._onSpecificSettings(); }); + $('#datatable_dlg_'+me.options.sListId).find('input[name=save_settings]').click(function() { me._updateSaveScope(); }); + this.element.find('.itop_popup > ul li').popupmenu(); + this._updateSaveScope(); + this._saveDlgState(); + }, + + // called when created, and later when changing options + _refresh: function() + { + oParams = this.options.oData; + oParams.operation = 'search_and_refresh'; + + oParams.start = 0; + oParams.end = this.options.iPageSize; + oParams.select_mode = this.options.sSelectMode; + oParams.display_key = this.options.sViewLink; + oParams.class_aliases = this.options.oClassAliases; + oParams.columns = this.options.oColumns; + var iSortCol = 0; + var aCurrentSort = []; + for(var k1 in oParams.columns) //Aliases + { + for(var k2 in oParams.columns[k1]) //Attribute codes + { + if (oParams.columns[k1][k2].sort != 'none') + { + oParams.sort_col = iSortCol; + oParams.sort_order = oParams.columns[k1][k2].sort; + aCurrentSort.push([iSortCol, (oParams.columns[k1][k2].sort == 'asc') ? 0 : 1]); + break; //TODO make this more generic, Sort on just one column for now + } + iSortCol++; + } + break; //TODO: DBObjectSet supports only sorting on the first alias of the set + } + oParams.list_id = this.options.sListId; + var me = this; + this.element.block(); + + $('#'+me.options.sListId).DataTable().ajax.reload(); + $.post(this.options.sRenderUrl, oParams, function(data) { + // Nasty workaround to clear the pager's state for paginated lists !!! + // See jquery.tablesorter.pager.js / saveParams / restoreParams + if (window.pager_params) + { + window.pager_params['pager'+me.options.sListId] = undefined; + } + // End of workaround + console.warn("update:"); + console.warn(data); + + // try { + var toto = $('#'+me.options.sListId).parent().parent(); + $('#'+me.options.sListId).DataTable().destroy(true); + var entete=""; + var aOptions = JSON.parse(data); + $.each(aOptions[0]['allColumns'], function(i, item) { + $.each(item, function(j, champs) { + if(champs.checked == 'true') { + entete += ""+champs.label+""; + } + }); + }); + $.each(aOptions[0]['columns'], function(i, item) { + aOptions[0]["columns"][i]["render"]["display"] = new Function ( "data, type, row" , aOptions[0]["columns"][i]["render"]["display"]); + }); + + toto.append( "" + + ""+entete+"
" ); + //$('#'+me.options.sListId).DataTable().clear(); + //$('#'+me.options.sListId).empty(); + aOptions[0]["lengthMenu"]= [[oParams.end, oParams.end*2, oParams.end*3, oParams.end*4, -1], [oParams.end, oParams.end*2, oParams.end*3, oParams.end*4, aOptions[0]["lengthMenu"]]]; + aOptions[0]["ajax"]=eval(aOptions[0]["ajax"]); + $('#'+me.options.sListId).DataTable(aOptions[0]); + //me.element.find('.datacontents').html(data); + // restore the sort order on columns + //me.element.find('table.listResults').trigger('fakesorton', [aCurrentSort]); + + + + + + + /*} catch (e) { + // ugly hacks for IE 8/9 first... + if (!window.console) console.error = {}; + if (!window.console.error) { + console.error = function () { + }; + } + console.error("Can not inject data : "+data); + }*/ + me.element.unblock(); + + }, 'html' ); + + }, + _useDefaultSettings: function(bResetAll) + { + var oParams = this.options.oData; + oParams.operation = 'datatable_reset_settings'; + + oParams.table_id = this.options.sTableId; + oParams.defaults = bResetAll; + oParams.class_aliases = this.options.oClassAliases; + + var me = this; + $.post(this.options.sRenderUrl, oParams, function(data) { + // Do nothing... + }, 'html' ); + }, + _saveSettings: function(bSaveAsDefaults) + { + var oParams = this.options.oData ; + oParams.operation = 'datatable_save_settings'; + oParams.page_size = this.options.iPageSize; + oParams.table_id = this.options.sTableId; + oParams.defaults = bSaveAsDefaults; + oParams.columns = this.options.oColumns; + var iSortCol = 0; + var sSortOrder = ''; + for(var i in this.options.oColumns) + { + if (this.options.oColumns[i].checked) + { + if (this.options.oColumns[i].sort != 'none') + { + sSortOrder = this.options.oColumns[i].sort; + } + else + { + iSortCol++; + } + } + } + /*A voir, je ne sais pas à quoi ça sert + if ((this.options.sSelectMode != '') && (this.options.sSelectMode != 'none')) + { + iSortCol++; + }*/ + oParams.sort_col = iSortCol; + oParams.sort_order = sSortOrder; + var me = this; + $.post(this.options.sRenderUrl, oParams, function(data) { + // Do nothing... + }, 'html' ); + }, + onDlgOk: function() + { + var oOptions = {}; + oSettings = $('#datatable_dlg_'+this.options.sListId).find('input[name=settings]:checked'); + if (oSettings.val() == 'defaults') + { + oOptions = { iPageSize: this.options.oDefaultSettings.iDefaultPageSize, + oColumns: this.options.oDefaultSettings.oColumns + }; + } + else + { + var oDisplayColumns = {}; + var iColIdx = 0; + var iSortIdx = 0; + var sSortDirection = 'asc'; + var oColumns = $('#datatable_dlg_'+this.options.sListId).find(':itop-fieldsorter').fieldsorter('get_params'); + var iPageSize = parseInt($('#datatable_dlg_'+this.options.sListId+' input[name=page_size]').val(), 10); + + oOptions = {oColumns: oColumns, iPageSize: iPageSize, iDefaultPageSize: iPageSize }; + } + this._setOptions(oOptions); + + // Check if we need to save the settings or not... + var oSaveCheck = $('#datatable_dlg_'+this.options.sListId).find('input[name=save_settings]'); + var oSaveScope = $('#datatable_dlg_'+this.options.sListId).find('input[name=scope]:checked'); + if (oSaveCheck.prop('checked')) + { + if (oSettings.val() == 'defaults') + { + this._useDefaultSettings((oSaveScope.val() == 'defaults')); + } + else + { + this._saveSettings((oSaveScope.val() == 'defaults')); + } + } + this._saveDlgState(); + + }, + onDlgCancel: function() + { + this._restoreDlgState(); + }, + _onSpecificSettings: function() + { + $('#datatable_dlg_'+this.options.sListId).find('input.specific_settings').prop('checked', true); + }, + _updateSaveScope: function() + { + var oSaveCheck = $('#datatable_dlg_'+this.options.sListId).find('input[name=save_settings]'); + if (oSaveCheck.prop('checked')) + { + $('#datatable_dlg_'+this.options.sListId).find('input[name=scope]').each(function() { + if ($(this).attr('stay-disabled') != 'true') + { + $(this).prop('disabled', false); + } + }); + } + else + { + $('#datatable_dlg_'+this.options.sListId).find('input[name=scope]').prop('disabled', true); + } + }, + // events bound via _bind are removed automatically + // revert other modifications here + _destroy: function() + { + this.element.removeClass('itop-datatable'); + + $('#sfl_'+this.options.sListId).remove(); + $('#datatable_dlg_'+this.options.sListId).remove(); + }, + // _setOptions is called with a hash of all options that are changing + _setOptions: function() + { + // in 1.9 would use _superApply + this._superApply(arguments); + this._refresh(); + }, + // _setOption is called for each individual option that is changing + _setOption: function( key, value ) + { + // in 1.9 would use _super + this._superApply(arguments); + }, + UpdateState: function( config ) + { + console.warn('datatablesettings:UpdateState'); + var iPageSize = config.page_size; + if (iPageSize == -1) + { + iPageSize = 0; + } + this.options.iPageSize = iPageSize; + + var iPos = 0; + for (alias in this.options.oColumns) + { + for (attcode in this.options.oColumns[alias]) + { + this.options.oColumns[alias][attcode]['sort'] = 'none'; + if (this.options.oColumns[alias][attcode]['checked']) + { + if (iPos == config.sort_index) + { + this.options.oColumns[alias][attcode]['sort'] = config.sort_order; + } + iPos++; + } + } + } + + var dlgElement = $('#datatable_dlg_'+this.options.sListId); + dlgElement.find('input[name=page_size]').val(iPageSize); + dlgElement.find(':itop-fieldsorter').fieldsorter('option', { fields: this.options.oColumns }); + }, + _saveDlgState: function() + { + this.originalState = {}; + for(k in this.aDlgStateParams) + { + this.originalState[this.aDlgStateParams[k]] = this.options[this.aDlgStateParams[k]]; + } + this.originalState.oFields = $('#datatable_dlg_'+this.options.sListId).find(':itop-fieldsorter').fieldsorter('get_params'); + }, + _restoreDlgState: function() + { + var dlgElement = $('#datatable_dlg_'+this.options.sListId); + + for(k in this.aDlgStateParams) + { + this._setOption(this.aDlgStateParams[k], this.originalState[this.aDlgStateParams[k]]); + } + + dlgElement.find('input[name=page_size]').val(this.originalState.iDefaultPageSize); + + dlgElement.find(':itop-fieldsorter').fieldsorter('option', { fields: this.originalState.oFields }); + + $('#datatable_dlg_'+this.options.sListId).unblock(); + + }, + IsDialogOpen: function() + { + var oDlgOpen = $('#datatable_dlg_'+this.options.sListId+' :visible'); + + return (oDlgOpen.length > 0); + }, + DoRefresh: function() + { + this._refresh(); + }, + GetMultipleSelectionParams: function() + { + var oRes = {}; + + oRes.selectionMode = ''; + if (this.element.find(':input[name=selectionMode]').length > 0) + { + oRes.selectionMode = this.element.find(':input[name=selectionMode]').val(); + } + + oRes.selectObject = []; + this.element.find(':input[name^=selectObject]:checked').each(function() { + oRes.selectObject.push($(this).val()); + }); + + oRes.storedSelection = []; + this.element.find(':input[name^=storedSelection]').each(function() { + oRes.storedSelection.push($(this).val()); + }); + + return oRes; + } + }); +}); \ No newline at end of file diff --git a/js/datatable.js b/js/datatable.js index 37c0efd27..d57f7c129 100644 --- a/js/datatable.js +++ b/js/datatable.js @@ -1,4 +1,5 @@ // jQuery UI style "widget" for selecting and sorting "fields" +//@deprecated $(function() { // the widget definition, where "itop" is the namespace, diff --git a/js/linkswidget.js b/js/linkswidget.js index baee7f95b..40d7d8e62 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -193,7 +193,10 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH theMap['selectionMode'] = sMode; $('#fs_SearchFormToAdd_'+me.id+' :input').each( function (i) { - theMap[this.name] = this.value; + if(this.name !="") + { + theMap[this.name] = this.value; + } } ); theMap['sRemoteClass'] = theMap['class']; // swap 'class' (defined in the form) and 'remoteClass' @@ -206,10 +209,11 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH theMap[this.name].push(this.value); $(this).remove(); // Remove the selection for the next time the dialog re-opens }); + theMap["storedSelection[]"]=eval("oSelectedItemsadd_"+me.id); + // Retrieve the 'filter' definition - var table = $('#ResultsToAdd_'+me.id).find('table.listResults')[0]; - theMap['filter'] = table.config.filter; - theMap['extra_params'] = table.config.extra_params; + theMap['filter'] = $(':input[name=filter]', context).val(); + theMap['extra_params'] = $(':input[name=extra_params]', context).val(); } // else // { diff --git a/js/search/search_form_criteria.js b/js/search/search_form_criteria.js index 2dd528ef0..40baf7f12 100644 --- a/js/search/search_form_criteria.js +++ b/js/search/search_form_criteria.js @@ -380,7 +380,7 @@ $(function() // Prepare base DOM structure this.element .append('
') - .append('
'); + .append('
'); // Bind events // Note: No event to handle criteria closing when clicking outside of it as it is already handle by the form handler. diff --git a/js/search/search_form_handler.js b/js/search/search_form_handler.js index 3e68900e3..368ea5fdf 100644 --- a/js/search/search_form_handler.js +++ b/js/search/search_form_handler.js @@ -203,8 +203,8 @@ $(function() $('#ibo-breadcrumbs') - .breadcrumb('destroy') - .breadcrumb({ + .breadcrumbs('destroy') + .breadcrumbs({ itop_instance_id: oData['breadcrumb_instance_id'], max_count: oData['breadcrumb_max_count'], new_entry: { @@ -373,17 +373,17 @@ $(function() { oCriterionAreaElem = $('
').appendTo(this.element); } - oCriterionAreaElem.addClass('sf_criterion_area'); + oCriterionAreaElem.addClass('sf_criterion_area ibo-criterion-area'); this.elements.criterion_area = oCriterionAreaElem; // Clean area oCriterionAreaElem .html('') - .append('
'); + .append('
'); // Prepare content - this._prepareMoreCriterionMenu(); this._prepareExistingCriterion(); + this._prepareMoreCriterionMenu(); this._prepareSubmitButton(); }, // - Prepare "more" button @@ -398,7 +398,7 @@ $(function() // Header part var oHeaderElem = $('
') - .append('' + Dict.S('UI:Search:Criterion:MoreMenu:AddCriteria') + '') + .append('') .appendTo(this.elements.more_criterion); // Content part @@ -695,7 +695,7 @@ $(function() else { var oCriterionRowElem = $('
') - .addClass('sf_criterion_row') + .addClass('sf_criterion_row ibo-criterion-row') .appendTo(this.elements.criterion_area); } @@ -706,7 +706,7 @@ $(function() else { var oCriterionGroupElem = $('
') - .addClass('sf_criterion_group') + .addClass('sf_criterion_group ibo-criterion-group') .appendTo(oCriterionRowElem); } @@ -754,7 +754,7 @@ $(function() oResultAreaElem = $('
').insertAfter(this.element.closest('.display_block')); } } - oResultAreaElem.addClass('sf_results_area'); + //oResultAreaElem.addClass('display_block sf_results_area'); // Make placeholder if nothing yet if(oResultAreaElem.html() === '') diff --git a/js/utils.js b/js/utils.js index b08a39cab..eb708930a 100644 --- a/js/utils.js +++ b/js/utils.js @@ -262,11 +262,11 @@ function UpdateFileName(id, sNewFileName) { * Reload a search form for the specified class */ function ReloadSearchForm(divId, sClassName, sBaseClass, sContext, sTableId, sExtraParams) { - var oDiv = $('#ds_'+divId); + var oDiv = $('#'+divId); oDiv.block(); // deprecated in jQuery 1.8 //var oFormEvents = $('#ds_'+divId+' form').data('events'); - var oForm = $('#ds_'+divId+' form'); + var oForm = $('#'+divId+' form'); var oFormEvents = $._data(oForm[0], "events"); // Save the submit handlers @@ -276,11 +276,11 @@ function ReloadSearchForm(divId, sClassName, sBaseClass, sContext, sTableId, sEx aSubmit [index] = {data: oFormEvents.submit[index].data, namespace: oFormEvents.submit[index].namespace, handler: oFormEvents.submit[index].handler}; } } - sAction = $('#ds_'+divId+' form').attr('action'); + sAction = $('#'+divId+' form').attr('action'); // Save the current values in the form var oMap = {}; - $('#ds_'+divId+" form :input[name!='']").each(function () { + $('#'+divId+" form :input[name!='']").each(function () { oMap[this.name] = this.value; }); oMap.operation = 'search_form'; diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index c1d33ded2..757f2dc5a 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -161,6 +161,9 @@ return array( 'Combodo\\iTop\\Application\\UI\\Component\\Dashlet\\DashletContainer' => $baseDir . '/sources/application/UI/Component/Dashlet/DashletContainer.php', 'Combodo\\iTop\\Application\\UI\\Component\\Dashlet\\DashletFactory' => $baseDir . '/sources/application/UI/Component/Dashlet/DashletFactory.php', 'Combodo\\iTop\\Application\\UI\\Component\\Dashlet\\DashletHeaderStatic' => $baseDir . '/sources/application/UI/Component/Dashlet/DashletHeaderStatic.php', + 'Combodo\\iTop\\Application\\UI\\Component\\DataTable\\DataTableBlock' => $baseDir . '/sources/application/UI/Component/DataTable/DataTable.php', + 'Combodo\\iTop\\Application\\UI\\Component\\DataTable\\DataTableFactory' => $baseDir . '/sources/application/UI/Component/DataTable/DataTableFactory.php', + 'Combodo\\iTop\\Application\\UI\\Component\\DataTable\\DataTableSettings' => $baseDir . '/sources/application/UI/Component/DataTable/DataTableSettings.php', 'Combodo\\iTop\\Application\\UI\\Component\\FieldSet\\FieldSet' => $baseDir . '/sources/application/UI/Component/FieldSet/FieldSet.php', 'Combodo\\iTop\\Application\\UI\\Component\\Field\\Field' => $baseDir . '/sources/application/UI/Component/Field/Field.php', 'Combodo\\iTop\\Application\\UI\\Component\\Form\\Form' => $baseDir . '/sources/application/UI/Component/Form/Form.php', @@ -331,7 +334,6 @@ return array( 'DashletProxy' => $baseDir . '/application/dashlet.class.inc.php', 'DashletUnknown' => $baseDir . '/application/dashlet.class.inc.php', 'DataTable' => $baseDir . '/application/datatable.class.inc.php', - 'DataTableSettings' => $baseDir . '/application/datatable.class.inc.php', 'Datamatrix' => $vendorDir . '/combodo/tcpdf/include/barcodes/datamatrix.php', 'DateTimeFormat' => $baseDir . '/core/datetimeformat.class.inc.php', 'DeadLockLog' => $baseDir . '/core/log.class.inc.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index c65c884fc..a88877f24 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -391,6 +391,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Application\\UI\\Component\\Dashlet\\DashletContainer' => __DIR__ . '/../..' . '/sources/application/UI/Component/Dashlet/DashletContainer.php', 'Combodo\\iTop\\Application\\UI\\Component\\Dashlet\\DashletFactory' => __DIR__ . '/../..' . '/sources/application/UI/Component/Dashlet/DashletFactory.php', 'Combodo\\iTop\\Application\\UI\\Component\\Dashlet\\DashletHeaderStatic' => __DIR__ . '/../..' . '/sources/application/UI/Component/Dashlet/DashletHeaderStatic.php', + 'Combodo\\iTop\\Application\\UI\\Component\\DataTable\\DataTableBlock' => __DIR__ . '/../..' . '/sources/application/UI/Component/DataTable/DataTable.php', + 'Combodo\\iTop\\Application\\UI\\Component\\DataTable\\DataTableFactory' => __DIR__ . '/../..' . '/sources/application/UI/Component/DataTable/DataTableFactory.php', + 'Combodo\\iTop\\Application\\UI\\Component\\DataTable\\DataTableSettings' => __DIR__ . '/../..' . '/sources/application/UI/Component/DataTable/DataTableSettings.php', 'Combodo\\iTop\\Application\\UI\\Component\\FieldSet\\FieldSet' => __DIR__ . '/../..' . '/sources/application/UI/Component/FieldSet/FieldSet.php', 'Combodo\\iTop\\Application\\UI\\Component\\Field\\Field' => __DIR__ . '/../..' . '/sources/application/UI/Component/Field/Field.php', 'Combodo\\iTop\\Application\\UI\\Component\\Form\\Form' => __DIR__ . '/../..' . '/sources/application/UI/Component/Form/Form.php', @@ -561,7 +564,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'DashletProxy' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php', 'DashletUnknown' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php', 'DataTable' => __DIR__ . '/../..' . '/application/datatable.class.inc.php', - 'DataTableSettings' => __DIR__ . '/../..' . '/application/datatable.class.inc.php', 'Datamatrix' => __DIR__ . '/..' . '/combodo/tcpdf/include/barcodes/datamatrix.php', 'DateTimeFormat' => __DIR__ . '/../..' . '/core/datetimeformat.class.inc.php', 'DeadLockLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/dataTables.bootstrap.min.css b/lib/datatables/css/dataTables.bootstrap.min.css similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/dataTables.bootstrap.min.css rename to lib/datatables/css/dataTables.bootstrap.min.css diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/fixedHeader.bootstrap.min.css b/lib/datatables/css/fixedHeader.bootstrap.min.css similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/fixedHeader.bootstrap.min.css rename to lib/datatables/css/fixedHeader.bootstrap.min.css diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/responsive.bootstrap.min.css b/lib/datatables/css/responsive.bootstrap.min.css similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/responsive.bootstrap.min.css rename to lib/datatables/css/responsive.bootstrap.min.css diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/scroller.bootstrap.min.css b/lib/datatables/css/scroller.bootstrap.min.css similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/scroller.bootstrap.min.css rename to lib/datatables/css/scroller.bootstrap.min.css diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.bootstrap.min.css b/lib/datatables/css/select.bootstrap.min.css similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.bootstrap.min.css rename to lib/datatables/css/select.bootstrap.min.css diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.dataTables.min.css b/lib/datatables/css/select.dataTables.min.css similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/css/select.dataTables.min.css rename to lib/datatables/css/select.dataTables.min.css diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc.png b/lib/datatables/images/sort_asc.png similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc.png rename to lib/datatables/images/sort_asc.png diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc_disabled.png b/lib/datatables/images/sort_asc_disabled.png similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_asc_disabled.png rename to lib/datatables/images/sort_asc_disabled.png diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_both.png b/lib/datatables/images/sort_both.png similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_both.png rename to lib/datatables/images/sort_both.png diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc.png b/lib/datatables/images/sort_desc.png similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc.png rename to lib/datatables/images/sort_desc.png diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc_disabled.png b/lib/datatables/images/sort_desc_disabled.png similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/images/sort_desc_disabled.png rename to lib/datatables/images/sort_desc_disabled.png diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.bootstrap.min.js b/lib/datatables/js/dataTables.bootstrap.min.js similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.bootstrap.min.js rename to lib/datatables/js/dataTables.bootstrap.min.js diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.fixedHeader.min.js b/lib/datatables/js/dataTables.fixedHeader.min.js similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.fixedHeader.min.js rename to lib/datatables/js/dataTables.fixedHeader.min.js diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.responsive.min.js b/lib/datatables/js/dataTables.responsive.min.js similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.responsive.min.js rename to lib/datatables/js/dataTables.responsive.min.js diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.scroller.min.js b/lib/datatables/js/dataTables.scroller.min.js similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.scroller.min.js rename to lib/datatables/js/dataTables.scroller.min.js diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.select.min.js b/lib/datatables/js/dataTables.select.min.js similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/dataTables.select.min.js rename to lib/datatables/js/dataTables.select.min.js diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/datetime-moment.js b/lib/datatables/js/datetime-moment.js similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/datetime-moment.js rename to lib/datatables/js/datetime-moment.js diff --git a/datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/jquery.dataTables.min.js b/lib/datatables/js/jquery.dataTables.min.js similarity index 100% rename from datamodels/2.x/itop-portal-base/portal/public/lib/datatables/js/jquery.dataTables.min.js rename to lib/datatables/js/jquery.dataTables.min.js diff --git a/pages/UI.php b/pages/UI.php index 856f2b0ed..f9fb91d28 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -208,6 +208,7 @@ function SetObjectBreadCrumbEntry(DBObject $oObj, WebPage $oPage) */ function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', $sFormat = '', $bDoSearch = true, $bSearchFormOpen = true) { + $oBlockForm=null; if ($bSearchForm) { $aParams = array('open' => $bSearchFormOpen, 'table_id' => '1'); @@ -215,22 +216,24 @@ function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', { $aParams['baseClass'] = $sBaseClass; } - $oBlock = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, $aParams); - $oBlock->Display($oP, 0); + $oBlockForm = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, $aParams); + + if (!$bDoSearch) + { + $oBlockForm->Display($oP, 0); + } } if ($bDoSearch) { if (strtolower($sFormat) == 'csv') { $oBlock = new DisplayBlock($oFilter, 'csv', false); - $oBlock->Display($oP, 1); // Adjust the size of the Textarea containing the CSV to fit almost all the remaining space $oP->add_ready_script(" $('#1>textarea').height($('#1').parent().height() - $('#0').outerHeight() - 30).width( $('#1').parent().width() - 20);"); // adjust the size of the block } else { $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oP, 1); // Breadcrumb //$iCount = $oBlock->GetDisplayedCount(); @@ -238,6 +241,22 @@ function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', $sLabel = MetaModel::GetName($oFilter->GetClass()); $oP->SetBreadCrumbEntry($sPageId, $sLabel, '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); } + if ($bSearchForm) { + $oUIBlockForm=$oBlockForm->GetDisplay($oP,'0'); + $sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data'); + if($sTableId=='') + { + $sTableId = '1'; + } + $oUIBlock=$oBlock->GetDisplay($oP, $sTableId); + $oUIBlock->AddCSSClasses("display_block sf_results_area"); + //$oUIBlockForm->AddSubBlock($oUIBlock); + $oP->AddUiBlock($oUIBlockForm); + $oP->AddUiBlock($oUIBlock); + } + else { + $oBlock->Display($oP, 1); + } } } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 9ca402b9f..283498030 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -17,10 +17,12 @@ * You should have received a copy of the GNU Affero General Public License */ +use Combodo\iTop\Application\UI\Component\DataTable\DataTableSettings; use Combodo\iTop\Application\UI\Layout\ActivityPanel\ActivityEntry\ActivityEntryFactory; use Combodo\iTop\Controller\AjaxRenderController; use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\Console\ConsoleFormRenderer; +use Combodo\iTop\Application\UI\Component\DataTable\DataTableFactory; require_once('../approot.inc.php'); require_once(APPROOT.'application/application.inc.php'); @@ -67,7 +69,7 @@ try } LoginWebPage::DoLoginEx($sRequestedPortalId, false); - $oPage = new ajax_page(""); + $oPage = new AjaxPage(""); $oPage->no_cache(); @@ -230,6 +232,190 @@ try $oKPI->ComputeAndReport('Data fetch and format'); break; + case 'search_and_refresh': + $oPage->SetContentType('application/json'); + $extraParams = utils::ReadParam('extra_param', '', false, 'raw_data'); + $aExtraParams = array(); + if (is_array($extraParams)) + { + $aExtraParams = $extraParams; + } + else + { + $sExtraParams = stripslashes($extraParams); + if (!empty($sExtraParams)) + { + $val = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */); + if ($val !== null) + { + $aExtraParams = $val; + } + } + } + $iLength = utils::ReadParam('end', 10); + $aColumns = utils::ReadParam('columns', array(), false, 'raw_data'); + $sSelectMode = utils::ReadParam('select_mode', ''); + $aResult = DataTableFactory::GetOptionsForRendering( $aColumns, $sSelectMode, $sFilter, $iLength, $aExtraParams); + $oPage->add(json_encode($aResult)); + break; + + case 'search': + $oPage->SetContentType('application/json'); + $extraParams = utils::ReadParam('extra_param', '', false, 'raw_data'); + $aExtraParams = array(); + if (is_array($extraParams)) + { + $aExtraParams = $extraParams; + } + else + { + $sExtraParams = stripslashes($extraParams); + if (!empty($sExtraParams)) + { + $val = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */); + if ($val !== null) + { + $aExtraParams = $val; + } + } + } + if ($sEncoding == 'oql') + { + $oFilter = DBSearch::FromOQL($sFilter); + } + else + { + $oFilter = DBSearch::unserialize($sFilter); + } + $iStart = utils::ReadParam('start', 0); + $iEnd = utils::ReadParam('end', 1); + $iDrawNumber= utils::ReadParam('draw', 1); + + $iSortCol = utils::ReadParam('sort_col', 'null'); + $sSelectMode = utils::ReadParam('select_mode', ''); + if (!empty($sSelectMode) && ($sSelectMode != 'none')) + { + // The first column is used for the selection (radio / checkbox) and is not sortable + $iSortCol--; + } + $bDisplayKey = utils::ReadParam('display_key', 'true') == 'true'; + $aColumns = utils::ReadParam('columns', array(), false, 'raw_data'); + $aClassAliases = utils::ReadParam('class_aliases', array()); + $iListId = utils::ReadParam('list_id', 0); + + // Filter the list to removed linked set since we are not able to display them here + $sIdName =""; + $aOrderBy = array(); + $iSortIndex = 0; + + $aColumnsLoad = array(); + foreach($aClassAliases as $sAlias => $sClassName) + { + $aColumnsLoad[$sAlias] = array(); + foreach($aColumns[$sAlias] as $sAttCode => $aData) + { + if ($aData['checked'] == 'true') + { + $aColumns[$sAlias][$sAttCode]['checked'] = true; + if ($sAttCode == '_key_') + { + if($sIdName == ""){ + $sIdName = $sAlias."/_key_"; + } + if ($iSortCol == $iSortIndex) + { + if (!MetaModel::HasChildrenClasses($oFilter->GetClass())) + { + $aNameSpec = MetaModel::GetNameSpec($oFilter->GetClass()); + if ($aNameSpec[0] == '%1$s') + { + // The name is made of a single column, let's sort according to the sort algorithm for this column + $aOrderBy[$sAlias.'.'.$aNameSpec[1][0]] = (utils::ReadParam('sort_order', 'asc') == 'asc'); + } + else + { + $aOrderBy[$sAlias.'.'.'friendlyname'] = (utils::ReadParam('sort_order', 'asc') == 'asc'); + } + } + else + { + $aOrderBy[$sAlias.'.'.'friendlyname'] = (utils::ReadParam('sort_order', 'asc') == 'asc'); + } + } + } + else + { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + if ($oAttDef instanceof AttributeLinkedSet) + { + // Removed from the display list + unset($aColumns[$sAlias][$sAttCode]); + } + else + { + $aColumnsLoad[$sAlias][] = $sAttCode; + } + if ($iSortCol == $iSortIndex) + { + if ($oAttDef->IsExternalKey()) + { + $sSortCol = $sAttCode.'_friendlyname'; + } + else + { + $sSortCol = $sAttCode; + } + $aOrderBy[$sAlias.'.'.$sSortCol] = (utils::ReadParam('sort_order', 'asc') == 'asc'); + } + } + $iSortIndex++; + } + else + { + $aColumns[$sAlias][$sAttCode]['checked'] = false; + } + } + + } + + // Load only the requested columns + $oSet = new DBObjectSet($oFilter, $aOrderBy, $aExtraParams, null, $iEnd - $iStart, $iStart); + $oSet->OptimizeColumnLoad($aColumnsLoad); + + if (isset($aExtraParams['show_obsolete_data'])) { + $bShowObsoleteData = $aExtraParams['show_obsolete_data']; + } + else { + $bShowObsoleteData = utils::ShowObsoleteData(); + } + $oSet->SetShowObsoleteData($bShowObsoleteData); + $oKPI = new ExecutionKPI(); + $aResult["draw"] = $iDrawNumber; + $aResult["recordsTotal"] = $oSet->Count() ; + $aResult["recordsFiltered"] = $oSet->Count() ; + $aResult["data"] = []; + while ($aObject = $oSet->FetchAssoc()) { + foreach($aClassAliases as $sAlias=>$sClass) { + foreach($aColumns[$sAlias] as $sAttCode => $oAttDef) { + if($sAttCode=="_key_") { + $aObj[$sAlias."/".$sAttCode ] = $aObject[$sAlias]->GetKey(); + } + else { + $aObj[$sAlias."/".$sAttCode ] = $aObject[$sAlias]->Get($sAttCode); + } + } + } + if($sIdName!="") + { + $aObj["id" ] = $aObj[$sIdName]; + } + array_push($aResult["data"], $aObj); + } + + $oPage->add(json_encode($aResult)); + $oKPI->ComputeAndReport('Data fetch and format'); + break; + case 'datatable_save_settings': $oPage->SetContentType('text/plain'); $iPageSize = utils::ReadParam('page_size', 10); diff --git a/pages/ajax.searchform.php b/pages/ajax.searchform.php index b55bea343..a58e664dc 100644 --- a/pages/ajax.searchform.php +++ b/pages/ajax.searchform.php @@ -47,7 +47,7 @@ try throw new AjaxSearchException("Invalid query (empty filter)", 400); } - $oPage = new ajax_page(""); + $oPage = new AjaxPage(""); $oPage->no_cache(); $oPage->SetContentType('text/html'); diff --git a/sources/application/UI/Component/DataTable/DataTable.php b/sources/application/UI/Component/DataTable/DataTable.php new file mode 100644 index 000000000..d12d4ba2f --- /dev/null +++ b/sources/application/UI/Component/DataTable/DataTable.php @@ -0,0 +1,127 @@ +sAjaxUrl; + } + + /** + * @param mixed $sAjaxUrl + */ + public function SetAjaxUrl(string $sAjaxUrl): void + { + $this->sAjaxUrl = $sAjaxUrl; + } + + /** + * @return mixed + */ + public function GetAjaxData(): string + { + return $this->sAjaxData; + } + + /** + * @param mixed $sAjaxData + */ + public function SetAjaxData(string $sAjaxData): void + { + $this->sAjaxData = $sAjaxData; + } + + /** + * @return mixed + */ + public function GetDisplayColumns(): array + { + return $this->aDisplayColumns; + } + + /** + * @param mixed $aColumns + */ + public function SetDisplayColumns($aColumns): void + { + $this->aDisplayColumns = $aColumns; + } + /** + * @return mixed + */ + public function GetResultColumns(): array + { + return $this->aResultColumns; + } + /** + * @return mixed + */ + public function GetResultColumnsAsJson(): string + { + return json_encode($this->aResultColumns); + } + + /** + * @param mixed $aColumns + */ + public function SetResultColumns($aColumns): void + { + $this->aResultColumns = $aColumns; + } + /** + * @return mixed + */ + public function GetOptions(): array + { + return $this->aOptions; + } + + /** + * @param mixed $aOptions + */ + public function SetOptions($aOptions): void + { + $this->aOptions = $aOptions; + } + + + +} diff --git a/sources/application/UI/Component/DataTable/DataTableFactory.php b/sources/application/UI/Component/DataTable/DataTableFactory.php new file mode 100644 index 000000000..e32fec712 --- /dev/null +++ b/sources/application/UI/Component/DataTable/DataTableFactory.php @@ -0,0 +1,589 @@ +GetClass(), "Result"); + $oDataTable = DataTableFactory::MakeForRendering( $sListId, $oSet, $aExtraParams ); + $oPanel->AddMainBlock($oDataTable); + + $oMenuBlock = new MenuBlock($oSet->GetFilter(), 'list'); + $oBlock = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $sListId); + $oBlockMenu = new UIContentBlock(); + $oBlockMenu->AddSubBlock($oBlock); + $oPanel->AddToolbarBlock($oBlockMenu); + + return $oPanel; + } + public static function MakeForObject(WebPage $oPage, string $sListId, CMDBObjectSet $oSet, $aExtraParams = array()) + { + $oPanel = PanelFactory::MakeForClass( $oSet->GetClass(), "Result"); + $oDataTable = DataTableFactory::MakeForRenderingObject( $sListId, $oSet, $aExtraParams ); + $oPanel->AddMainBlock($oDataTable); + + $oMenuBlock = new MenuBlock($oSet->GetFilter(), 'list'); + $oBlock = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $sListId); + $oBlockMenu = new UIContentBlock(); + $oBlockMenu->AddSubBlock($oBlock); + $oPanel->AddToolbarBlock($oBlockMenu); + + return $oPanel; + } + /** + * Make a basis Panel component + * + * @param string $sTitle + * + * @return DataTableBlock + */ + public static function MakeForRendering(string $sListId, CMDBObjectSet $oSet, $aExtraParams = array()) + { + $oDataTable = new DataTableBlock('datatable_'.$sListId); + /////////////////////////////////////////////////// + /*TODO 3.0.0 PrintableVersion + if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) + { + return self::GetDisplaySetForPrinting($oPage, $oSet, $aExtraParams); + } + */ + + // Initialize and check the parameters + $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; + $sLinkageAttribute = isset($aExtraParams['link_attr']) ? $aExtraParams['link_attr'] : ''; + $iLinkedObjectId = isset($aExtraParams['object_id']) ? $aExtraParams['object_id'] : 0; + $sTargetAttr = isset($aExtraParams['target_attr']) ? $aExtraParams['target_attr'] : ''; + if (!empty($sLinkageAttribute)) { + if ($iLinkedObjectId == 0) { + // if 'links' mode is requested the id of the object to link to must be specified + throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); + } + if ($sTargetAttr == '') { + // if 'links' mode is requested the d of the object to link to must be specified + throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_target_attr')); + } + } + $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; + $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; + + $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', + trim($aExtraParams['extra_fields'])) : array(); + $aExtraFields = array(); + foreach ($aExtraFieldsRaw as $sFieldName) { + // Ignore attributes not of the main queried class + if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) { + $sClassAlias = $aMatches[1]; + $sAttCode = $aMatches[2]; + if ($sClassAlias == $oSet->GetFilter()->GetClassAlias()) {//$oSet->GetFilter()->GetSelectedClasses() + $aExtraFields[] = $sAttCode; + } + } else { + $aExtraFields[] = $sFieldName; + } + } + $sClassName = $oSet->GetFilter()->GetClass(); + $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; + if ($sZListName !== false) { + $aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); + $aList = array_merge($aList, $aExtraFields); + } else { + $aList = $aExtraFields; + } + + // Filter the list to removed linked set since we are not able to display them here + foreach ($aList as $index => $sAttCode) { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + if ($oAttDef instanceof AttributeLinkedSet) { + // Removed from the display list + unset($aList[$index]); + } + } + + if (!empty($sLinkageAttribute)) { + // The set to display is in fact a set of links between the object specified in the $sLinkageAttribute + // and other objects... + // The display will then group all the attributes related to the link itself: + // | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n | + $aDisplayList = array(); + $aAttDefs = MetaModel::ListAttributeDefs($sClassName); + assert(isset($aAttDefs[$sLinkageAttribute])); + $oAttDef = $aAttDefs[$sLinkageAttribute]; + assert($oAttDef->IsExternalKey()); + // First display all the attributes specific to the link record + foreach ($aList as $sLinkAttCode) { + $oLinkAttDef = $aAttDefs[$sLinkAttCode]; + if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) { + $aDisplayList[] = $sLinkAttCode; + } + } + // Then display all the attributes neither specific to the link record nor to the 'linkage' object (because the latter are constant) + foreach ($aList as $sLinkAttCode) { + $oLinkAttDef = $aAttDefs[$sLinkAttCode]; + if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute)) + || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) { + $aDisplayList[] = $sLinkAttCode; + } + } + // First display all the attributes specific to the link + // Then display all the attributes linked to the other end of the relationship + $aList = $aDisplayList; + } + + $sSelectMode = 'none'; + if ($bSelectMode) { + $sSelectMode = $bSingleSelectMode ? 'single' : 'multiple'; + } + + $sClassAlias = $oSet->GetClassAlias(); + $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; + + $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; + $aClassAliases = array($sClassAlias => $sClassName); + $oDefaultSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); + + if ($bDisplayLimit) { + $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit()); + $oDefaultSettings->iDefaultPageSize = $iDefaultPageSize; + } else { + $oDefaultSettings->iDefaultPageSize = 0; + } + $oDefaultSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); + + $bUseCustomSettings = false; + // Identified tables can have their own specific settings + $oCustomSettings = DataTableSettings::GetTableSettings($aClassAliases, $sTableId); + + if ($oCustomSettings != null) { + // Custom settings overload the default ones + $bUseCustomSettings = true; + if ($oDefaultSettings->iDefaultPageSize == 0) { + $oCustomSettings->iDefaultPageSize = 0; + } + } else { + $oCustomSettings = $oDefaultSettings; + } + + if ($oCustomSettings->iDefaultPageSize > 0) { + $oSet->SetLimit($oCustomSettings->iDefaultPageSize); + } + $oSet->SetOrderBy($oCustomSettings->GetSortOrder()); + + // Load only the requested columns + $aColumnsToLoad = array(); + foreach ($oCustomSettings->aColumns as $sAlias => $aColumnsInfo) { + foreach ($aColumnsInfo as $sAttCode => $aData) { + if ($sAttCode != '_key_') { + if ($aData['checked']) { + $aColumnsToLoad[$sAlias][] = $sAttCode; + } else { + // See if this column is a must to load + $sClass = $aClassAliases[$sAlias]; + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->alwaysLoadInTables()) { + $aColumnsToLoad[$sAlias][] = $sAttCode; + } + } + } + } + } + $oSet->OptimizeColumnLoad($aColumnsToLoad); + + $aColumnDefinition = []; + foreach ($aClassAliases as $sClassAlias => $sClassName) { + foreach ($oCustomSettings->aColumns[$sClassAlias] as $sAttCode => $aData) { + if ($aData['checked']) { + if ($sAttCode == '_key_') { + $aColumnDefinition[] = [ + 'description' => $aData['label'], + 'object_class' => $sClassName, + 'class_alias' => $sClassAlias, + 'attribute_code' => $sAttCode, + 'attribute_type' => '_key_', + 'attribute_label' => $aData['alias'], + "render" => "return ''+row['".$sClassAlias."/friendlyname']+'' ;", + ]; + } else { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + $sAttDefClass = get_class($oAttDef); + $sAttLabel = MetaModel::GetLabel($sClassName, $sAttCode); + $aColumnDefinition[] = [ + 'description' => $oAttDef->GetOrderByHint(), + 'object_class' => $sClassName, + 'class_alias' => $sClassAlias, + 'attribute_code' => $sAttCode, + 'attribute_type' => $sAttDefClass, + 'attribute_label' => $sAttLabel, + "render" => $oAttDef->GetRenderForDataTable($sClassAlias), + ]; + } + } + } + } + + $aOptions = []; + if($oDefaultSettings != null) + { + $aOptions['oDefaultSettings'] = json_encode(array('iDefaultPageSize' => $oDefaultSettings->iDefaultPageSize, 'oColumns' => $oDefaultSettings->aColumns)); + } + + if ($sSelectMode == 'multiple') { + $aOptions['select'] = "multi"; + } else if ($sSelectMode == 'single') { + $aOptions['select'] = "single"; + } + + $aOptions['iPageSize'] = 10; + if ($oCustomSettings->iDefaultPageSize > 0) { + $aOptions['iPageSize'] = $oCustomSettings->iDefaultPageSize; + } + + $aOptions['sTableId'] =$sTableId; + $aOptions['bUseCustomSettings'] =$bUseCustomSettings; + $aOptions['bViewLink'] =$bViewLink; + + $oDataTable->SetOptions($aOptions); + $oDataTable->SetAjaxUrl("ajax.render.php"); + $oDataTable->SetAjaxData(json_encode([ + "operation" => 'search', + "filter" => $oSet->GetFilter()->serialize(), + "columns" => $oCustomSettings->aColumns, + "extra_params" => $aExtraParams, + "class_aliases" => $aClassAliases, + ])); + $oDataTable->SetDisplayColumns($aColumnDefinition); + $oDataTable->SetResultColumns($oCustomSettings->aColumns); + + return $oDataTable; + } + public static function MakeForRenderingObject(string $sListId, CMDBObjectSet $oSet, $aExtraParams = array()) + { + $oDataTable = new DataTableBlock('datatable_'.$sListId); + $aList = array(); + + // Initialize and check the parameters + $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; + // Check if there is a list of aliases to limit the display to... + $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', + $aExtraParams['display_aliases']) : array(); + $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; + + $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', + trim($aExtraParams['extra_fields'])) : array(); + $aExtraFields = array(); + $sAttCode = ''; + foreach($aExtraFieldsRaw as $sFieldName) + { + // Ignore attributes not of the main queried class + if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) + { + $sClassAlias = $aMatches[1]; + $sAttCode = $aMatches[2]; + if (array_key_exists($sClassAlias, $oSet->GetSelectedClasses())) + { + $aExtraFields[$sClassAlias][] = $sAttCode; + } + } + else + { + $aExtraFields['*'] = $sAttCode; + } + } + + $aClassAliases = $oSet->GetFilter()->GetSelectedClasses(); + $aAuthorizedClasses = array(); + foreach($aClassAliases as $sAlias => $sClassName) + { + if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) && + ((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))) + { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } + foreach($aAuthorizedClasses as $sAlias => $sClassName) + { + if (array_key_exists($sAlias, $aExtraFields)) + { + $aList[$sAlias] = $aExtraFields[$sAlias]; + } + else + { + $aList[$sAlias] = array(); + } + if ($sZListName !== false) + { + $aDefaultList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); + + $aList[$sAlias] = array_merge($aDefaultList, $aList[$sAlias]); + } + + // Filter the list to removed linked set since we are not able to display them here + foreach ($aList[$sAlias] as $index => $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + if ($oAttDef instanceof AttributeLinkedSet) + { + // Removed from the display list + unset($aList[$sAlias][$index]); + } + } + + if (empty($aList[$sAlias])) + { + unset($aList[$sAlias], $aAuthorizedClasses[$sAlias]); + } + } + + $sSelectMode = 'none'; + + $oDefaultSettings = DataTableSettings::GetDataModelSettings($aAuthorizedClasses, $bViewLink, $aList); + + $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; + if ($bDisplayLimit) + { + $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', + MetaModel::GetConfig()->GetMinDisplayLimit()); + $oDefaultSettings->iDefaultPageSize = $iDefaultPageSize; + } + + $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; + $oDefaultSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); + + $bUseCustomSettings = false; + // Identified tables can have their own specific settings + $oCustomSettings = DataTableSettings::GetTableSettings($aClassAliases, $sTableId); + + if ($oCustomSettings != null) { + // Custom settings overload the default ones + $bUseCustomSettings = true; + if ($oDefaultSettings->iDefaultPageSize == 0) { + $oCustomSettings->iDefaultPageSize = 0; + } + } else { + $oCustomSettings = $oDefaultSettings; + } + + if ($oCustomSettings->iDefaultPageSize > 0) { + $oSet->SetLimit($oCustomSettings->iDefaultPageSize); + } + $oSet->SetOrderBy($oCustomSettings->GetSortOrder()); + + // Load only the requested columns + $aColumnsToLoad = array(); + foreach ($oCustomSettings->aColumns as $sAlias => $aColumnsInfo) { + foreach ($aColumnsInfo as $sAttCode => $aData) { + if ($sAttCode != '_key_') { + if ($aData['checked']) { + $aColumnsToLoad[$sAlias][] = $sAttCode; + } else { + // See if this column is a must to load + $sClass = $aClassAliases[$sAlias]; + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->alwaysLoadInTables()) { + $aColumnsToLoad[$sAlias][] = $sAttCode; + } + } + } + } + } + $oSet->OptimizeColumnLoad($aColumnsToLoad); + + $aColumnDefinition = []; + foreach ($aClassAliases as $sClassAlias => $sClassName) { + foreach ($oCustomSettings->aColumns[$sClassAlias] as $sAttCode => $aData) { + if ($aData['checked']) { + if ($sAttCode == '_key_') { + $aColumnDefinition[] = [ + 'description' => $aData['label'], + 'object_class' => $sClassName, + 'class_alias' => $sClassAlias, + 'attribute_code' => $sAttCode, + 'attribute_type' => '_key_', + 'attribute_label' => $aData['alias'], + "render" => "return ''+row['".$sClassAlias."/friendlyname']+'' ;", + ]; + } else { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + $sAttDefClass = get_class($oAttDef); + $sAttLabel = MetaModel::GetLabel($sClassName, $sAttCode); + $aColumnDefinition[] = [ + 'description' => $oAttDef->GetOrderByHint(), + 'object_class' => $sClassName, + 'class_alias' => $sClassAlias, + 'attribute_code' => $sAttCode, + 'attribute_type' => $sAttDefClass, + 'attribute_label' => $sAttLabel, + "render" => $oAttDef->GetRenderForDataTable($sClassAlias), + ]; + } + } + } + } + + $aOptions = []; + if($oDefaultSettings != null) + { + $aOptions['oDefaultSettings'] = json_encode(array('iDefaultPageSize' => $oDefaultSettings->iDefaultPageSize, 'oColumns' => $oDefaultSettings->aColumns)); + } + + if ($sSelectMode == 'multiple') { + $aOptions['select'] = "multi"; + } else if ($sSelectMode == 'single') { + $aOptions['select'] = "single"; + } + + $aOptions['iPageSize'] = 10; + if ($oCustomSettings->iDefaultPageSize > 0) { + $aOptions['iPageSize'] = $oCustomSettings->iDefaultPageSize; + } + + $aOptions['sTableId'] =$sTableId; + $aOptions['bUseCustomSettings'] =$bUseCustomSettings; + $aOptions['bViewLink'] =$bViewLink; + + $oDataTable->SetOptions($aOptions); + $oDataTable->SetAjaxUrl("ajax.render.php"); + $oDataTable->SetAjaxData(json_encode([ + "operation" => 'search', + "filter" => $oSet->GetFilter()->serialize(), + "columns" => $oCustomSettings->aColumns, + "extra_params" => $aExtraParams, + "class_aliases" => $aClassAliases, + ])); + $oDataTable->SetDisplayColumns($aColumnDefinition); + $oDataTable->SetResultColumns($oCustomSettings->aColumns); + + return $oDataTable; + } + public static function GetOptionsForRendering(array $aColumns,string $sSelectMode, string $sFilter, int $iLength, array $aExtraParams) + { + $aOptions = []; + + $aColumnsDefinitions = []; + $aColumnDefinition = []; + $aClassAliases = []; + + foreach ($aColumns as $sClassName => $aClassColumns) { + $aClassAliases[$sClassName]=$sClassName; + foreach ($aClassColumns as $sAttCode => $aData) { + if ($aData['checked'] == "true") { + $aColumnDefinition["width"] = "auto"; + $aColumnDefinition["searchable"]= false; + $aColumnDefinition["sortable"]= true; + $aColumnDefinition["defaultContent"]= ""; + $aColumnDefinition["type"]= "html"; + + if ($sAttCode == '_key_') { + $aColumnDefinition["title"] =$aData['alias']; + $aColumnDefinition['metadata'] =[ + 'object_class'=> $sClassName, + 'attribute_code'=> $sAttCode, + 'attribute_type'=> '_key_', + 'attribute_label'=> $aData['alias'], + ]; + $aColumnDefinition["data"] = $sClassName."/".$sAttCode; + $aColumnDefinition["render"] =[ + "display"=> "return ''+row['".$sClassName."/friendlyname']+'' ;", + "_"=>$sClassName."/".$sAttCode, + ]; + } else { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + $sAttDefClass = get_class($oAttDef); + $sAttLabel = MetaModel::GetLabel($sClassName, $sAttCode); + + $aColumnDefinition["title"] =$sAttLabel; + $aColumnDefinition['metadata'] =[ + 'object_class'=> $sClassName, + 'attribute_code'=> $sAttCode, + 'attribute_type'=> $sAttDefClass, + 'attribute_label'=> $sAttLabel, + ]; + $aColumnDefinition["data"] = $sClassName."/".$sAttCode; + $aColumnDefinition["render"] =[ + "display"=> $oAttDef->GetRenderForDataTable($sClassName), + "_"=>$sClassName."/".$sAttCode, + ]; + } + array_push($aColumnsDefinitions,$aColumnDefinition); + } + } + } + + $aOptions['select'] =$sSelectMode; + + $aOptions['pageLength'] = $iLength; + + $sAjaxData=json_encode([ + "operation" => 'search', + "filter" => $sFilter, + "columns" => $aColumns, + "extra_params" => $aExtraParams, + "class_aliases" => $aClassAliases, + ]); + + + $aOptions[] = [ + "language" => + ["processing"=> Dict::Format('UI:Datatables:Language:Processing'), + "search"=> Dict::Format('UI:Datatables:Language:Search'), + "lengthMenu"=> Dict::Format('UI:Datatables:Language:LengthMenu'), + "zeroRecords"=> Dict::Format('UI:Datatables:Language:ZeroRecords'), + "info"=> Dict::Format('UI:Datatables:Language:Info'), + "infoEmpty"=> Dict::Format('UI:Datatables:Language:InfoEmpty'), + "infoFiltered"=> Dict::Format('UI:Datatables:Language:InfoFiltered'), + "emptyTable"=> Dict::Format('UI:Datatables:Language:EmptyTable'), + "paginate"=> [ + "first"=> "<<", + "previous"=> "<", + "next"=> ">", + "last"=> ">>" + ], + "aria"=> [ + "sortAscending"=> Dict::Format( 'UI:Datatables:Language:Sort:Ascending'), + "sortDescending"=> Dict::Format('UI:Datatables:Language:Sort:Descending') + ], + ], + "lengthMenu" => Dict::Format( 'Portal:Datatables:Language:DisplayLength:All'), + "dom"=> "<'ibo-datatable-toolbar'pil>t<'ibo-datatable-toolbar'pil>", + "order"=> [], + "filter"=> false, + "processing"=> true, + "serverSide"=> true, + "columns"=> $aColumnsDefinitions, + "allColumns"=> $aColumns, + 'ajax' => '$.fn.dataTable.pipeline( { + "url": "ajax.render.php", + "data": '.$sAjaxData.', + "method": "post", + "pages": 5 // number of pages to cache + } )' + ]; + + + return $aOptions; + } +} \ No newline at end of file diff --git a/sources/application/UI/Component/DataTable/DataTableSettings.php b/sources/application/UI/Component/DataTable/DataTableSettings.php new file mode 100644 index 000000000..6b5f5a2f0 --- /dev/null +++ b/sources/application/UI/Component/DataTable/DataTableSettings.php @@ -0,0 +1,355 @@ +aClassAliases = $aClassAliases; + $this->sTableId = $sTableId; + $this->iDefaultPageSize = 10; + $this->aColumns = array(); + } + + /** + * @param $iDefaultPageSize + * @param $aSortOrder + * @param $aColumns + */ + protected function Init($iDefaultPageSize, $aSortOrder, $aColumns) + { + $this->iDefaultPageSize = $iDefaultPageSize; + $this->aColumns = $aColumns; + $this->FixVisibleColumns(); + } + + /** + * @return string + */ + public function serialize() + { + // Save only the 'visible' columns + $aColumns = array(); + foreach ($this->aClassAliases as $sAlias => $sClass) { + $aColumns[$sAlias] = array(); + foreach ($this->aColumns[$sAlias] as $sAttCode => $aData) { + unset($aData['label']); // Don't save the display name + unset($aData['alias']); // Don't save the alias (redundant) + unset($aData['code']); // Don't save the code (redundant) + if ($aData['checked']) { + $aColumns[$sAlias][$sAttCode] = $aData; + } + } + } + return serialize( + array( + 'iDefaultPageSize' => $this->iDefaultPageSize, + 'aColumns' => $aColumns, + ) + ); + } + + /** + * @param string $sData + * + * @throws \Exception + */ + public function unserialize($sData) + { + $aData = unserialize($sData); + $this->iDefaultPageSize = $aData['iDefaultPageSize']; + $this->aColumns = $aData['aColumns']; + foreach ($this->aClassAliases as $sAlias => $sClass) { + foreach ($this->aColumns[$sAlias] as $sAttCode => $aData) { + $aFieldData = false; + if ($sAttCode == '_key_') { + $aFieldData = $this->GetFieldData($sAlias, $sAttCode, null, true /* bChecked */, $aData['sort']); + } else if (MetaModel::isValidAttCode($sClass, $sAttCode)) { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $aData['sort']); + } + + if ($aFieldData) { + $this->aColumns[$sAlias][$sAttCode] = $aFieldData; + } else { + unset($this->aColumns[$sAlias][$sAttCode]); + } + } + } + $this->FixVisibleColumns(); + } + + /** + * @param $aClassAliases + * @param $bViewLink + * @param $aDefaultLists + * + * @return \DataTableSettings + * @throws \CoreException + * @throws \DictExceptionMissingString + */ + public static function GetDataModelSettings($aClassAliases, $bViewLink, $aDefaultLists) + { + $oSettings = new DataTableSettings($aClassAliases); + // Retrieve the class specific settings for each class/alias based on the 'list' ZList + //TODO let the caller pass some other default settings (another Zlist, extre fields...) + $aColumns = array(); + foreach ($aClassAliases as $sAlias => $sClass) { + if ($aDefaultLists == null) { + $aList = cmdbAbstract::FlattenZList(MetaModel::GetZListItems($sClass, 'list')); + } else { + $aList = $aDefaultLists[$sAlias]; + } + + $aSortOrder = MetaModel::GetOrderByDefault($sClass); + if ($bViewLink) { + $sSort = 'none'; + if (array_key_exists('friendlyname', $aSortOrder)) { + $sSort = $aSortOrder['friendlyname'] ? 'asc' : 'desc'; + } + $sNormalizedFName = MetaModel::NormalizeFieldSpec($sClass, 'friendlyname'); + if (array_key_exists($sNormalizedFName, $aSortOrder)) { + $sSort = $aSortOrder[$sNormalizedFName] ? 'asc' : 'desc'; + } + + $aColumns[$sAlias]['_key_'] = $oSettings->GetFieldData($sAlias, '_key_', null, true /* bChecked */, $sSort); + } + foreach ($aList as $sAttCode) { + $sSort = 'none'; + if (array_key_exists($sAttCode, $aSortOrder)) { + $sSort = $aSortOrder[$sAttCode] ? 'asc' : 'desc'; + } + $oAttDef = Metamodel::GetAttributeDef($sClass, $sAttCode); + $aFieldData = $oSettings->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $sSort); + if ($aFieldData) $aColumns[$sAlias][$sAttCode] = $aFieldData; + } + } + $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit()); + $oSettings->Init($iDefaultPageSize, $aSortOrder, $aColumns); + return $oSettings; + } + + /** + * @throws \CoreException + */ + protected function FixVisibleColumns() + { + foreach ($this->aClassAliases as $sAlias => $sClass) { + if (!isset($this->aColumns[$sAlias])) { + continue; + } + foreach ($this->aColumns[$sAlias] as $sAttCode => $aData) { + // Remove non-existent columns + // TODO: check if the existing ones are still valid (in case their type changed) + if (($sAttCode != '_key_') && (!MetaModel::IsValidAttCode($sClass, $sAttCode))) { + unset($this->aColumns[$sAlias][$sAttCode]); + } + } + $aList = MetaModel::ListAttributeDefs($sClass); + + // Add the other (non visible ones), sorted in alphabetical order + $aTempData = array(); + foreach ($aList as $sAttCode => $oAttDef) { + if ((!array_key_exists($sAttCode, $this->aColumns[$sAlias])) && (!($oAttDef instanceof AttributeLinkedSet || $oAttDef instanceof AttributeDashboard))) { + $aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, false /* bChecked */, 'none'); + if ($aFieldData) $aTempData[$aFieldData['label']] = $aFieldData; + } + } + ksort($aTempData); + foreach ($aTempData as $sLabel => $aFieldData) { + $this->aColumns[$sAlias][$aFieldData['code']] = $aFieldData; + } + } + } + + /** + * @param $aClassAliases + * @param null $sTableId + * @param bool $bOnlyOnTable + * + * @return \DataTableSettings|null + * @throws \Exception + */ + static public function GetTableSettings($aClassAliases, $sTableId = null, $bOnlyOnTable = false) + { + $pref = null; + $oSettings = new DataTableSettings($aClassAliases, $sTableId); + + if ($sTableId != null) { + // An identified table, let's fetch its own settings (if any) + $pref = appUserPreferences::GetPref($oSettings->GetPrefsKey($sTableId), null); + } + + if ($pref == null) { + if (!$bOnlyOnTable) { + // Try the global preferred values for this class / set of classes + $pref = appUserPreferences::GetPref($oSettings->GetPrefsKey(null), null); + } + if ($pref == null) { + // no such settings, use the default values provided by the data model + return null; + } + } + $oSettings->unserialize($pref); + + return $oSettings; + } + + /** + * @return array + */ + public function GetSortOrder() + { + $aSortOrder = array(); + foreach ($this->aColumns as $sAlias => $aColumns) { + foreach ($aColumns as $aColumn) { + if ($aColumn['sort'] != 'none') { + $sCode = ($aColumn['code'] == '_key_') ? 'friendlyname' : $aColumn['code']; + $aSortOrder[$sCode] = ($aColumn['sort'] == 'asc'); // true for ascending, false for descending + } + } + break; // TODO: For now the Set object supports only sorting on the first class of the set + } + return $aSortOrder; + } + + /** + * @param null $sTargetTableId + * + * @return bool + */ + public function Save($sTargetTableId = null) + { + $sSaveId = is_null($sTargetTableId) ? $this->sTableId : $sTargetTableId; + if ($sSaveId == null) return false; // Cannot save, the table is not identified, use SaveAsDefault instead + + $sSettings = $this->serialize(); + appUserPreferences::SetPref($this->GetPrefsKey($sSaveId), $sSettings); + return true; + } + + /** + * @return bool + */ + public function SaveAsDefault() + { + $sSettings = $this->serialize(); + appUserPreferences::SetPref($this->GetPrefsKey(null), $sSettings); + return true; + } + + + /** + * Clear the preferences for this particular table + * @param $bResetAll boolean If true,the settings for all tables of the same class(es)/alias(es) are reset + */ + public function ResetToDefault($bResetAll) + { + if (($this->sTableId == null) && (!$bResetAll)) return false; // Cannot reset, the table is not identified, use force $bResetAll instead + if ($bResetAll) { + // Turn the key into a suitable PCRE pattern + $sKey = $this->GetPrefsKey(null); + $sPattern = str_replace(array('|'), array('\\|'), $sKey); // escape the | character + $sPattern = '#^' . str_replace(array('*'), array('.*'), $sPattern) . '$#'; // Don't use slash as the delimiter since it's used in our key to delimit aliases + appUserPreferences::UnsetPref($sPattern, true); + } else { + appUserPreferences::UnsetPref($this->GetPrefsKey($this->sTableId), false); + } + return true; + } + + /** + * @param null $sTableId + * + * @return string + */ + protected function GetPrefsKey($sTableId = null) + { + return static::GetAppUserPreferenceKey($this->aClassAliases, $sTableId); + } + + public static function GetAppUserPreferenceKey($aClassAliases, $sTableId) + { + if ($sTableId === null) { + $sTableId = '*'; + } + + $aKeys = array(); + foreach ($aClassAliases as $sAlias => $sClass) { + $aKeys[] = $sAlias . '-' . $sClass; + } + return implode('/', $aKeys) . '|' . $sTableId; + } + + /** + * @param $sAlias + * @param $sAttCode + * @param $oAttDef + * @param $bChecked + * @param $sSort + * + * @return array|bool + * @throws \CoreException + * @throws \DictExceptionMissingString + */ + protected function GetFieldData($sAlias, $sAttCode, $oAttDef, $bChecked, $sSort) + { + $ret = false; + if ($sAttCode == '_key_') { + $sLabel = Dict::Format('UI:ExtKey_AsLink', MetaModel::GetName($this->aClassAliases[$sAlias])); + $ret = array( + 'label' => $sLabel, + 'checked' => true, + 'disabled' => true, + 'alias' => $sAlias, + 'code' => $sAttCode, + 'sort' => $sSort, + ); + } else if (!$oAttDef->IsLinkSet()) { + $sLabel = $oAttDef->GetLabel(); + if ($oAttDef->IsExternalKey()) { + $sLabel = Dict::Format('UI:ExtKey_AsLink', $oAttDef->GetLabel()); + } else if ($oAttDef->IsExternalField()) { + if ($oAttDef->IsFriendlyName()) { + $sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel()); + } else { + $oExtAttDef = $oAttDef->GetExtAttDef(); + $sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel()); + } + } elseif ($oAttDef instanceof AttributeFriendlyName) { + $sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel()); + } + $ret = array( + 'label' => $sLabel, + 'checked' => $bChecked, + 'disabled' => false, + 'alias' => $sAlias, + 'code' => $sAttCode, + 'sort' => $sSort, + ); + } + return $ret; + } +} \ No newline at end of file diff --git a/sources/application/WebPage/NiceWebPage.php b/sources/application/WebPage/NiceWebPage.php index da9896b11..a3c9931c7 100644 --- a/sources/application/WebPage/NiceWebPage.php +++ b/sources/application/WebPage/NiceWebPage.php @@ -40,13 +40,31 @@ class NiceWebPage extends WebPage $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui.custom.min.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/table-selectable-lines.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_sorter.js'); + //TODO deprecated in 3.0.0 + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js'); // table sorting $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.pager.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablehover.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/table-selectable-lines.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_sorter.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js'); + //TODO end deprecated in 3.0.0 + // Datatables added in 3.0.0 + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'lib/datatables/js/jquery.dataTables.min.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'lib/datatables/js/dataTables.bootstrap.min.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'lib/datatables/js/dataTables.fixedHeader.min.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'lib/datatables/js/dataTables.responsive.min.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'lib/datatables/js/dataTables.scroller.min.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'lib/datatables/js/dataTables.select.min.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/dataTables.settings.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/dataTables.pipeline.js'); + /*$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'lib/datatables/css/dataTables.bootstrap.min.css'); + $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'lib/datatables/css/fixedHeader.bootstrap.min.css'); + $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'lib/datatables/css/responsive.bootstrap.min.css'); + $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'lib/datatables/css/scroller.bootstrap.min.css'); + $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'lib/datatables/css/select.bootstrap.min.css'); + $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'lib/datatables/css/select.dataTables.min.css');*/ + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.popupmenu.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/searchformforeignkeys.js'); @@ -69,52 +87,7 @@ class NiceWebPage extends WebPage $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/clipboardwidget.js'); $this->add_dict_entries('UI:Combo'); - - $this->add_ready_script( -<<< EOF - //add new widget called TruncatedList to properly display truncated lists when they are sorted - $.tablesorter.addWidget({ - // give the widget a id - id: "truncatedList", - // format is called when the on init and when a sorting has finished - format: function(table) - { - // Check if there is a "truncated" line - this.truncatedList = false; - if ($("tr td.truncated",table).length > 0) - { - this.truncatedList = true; - } - if (this.truncatedList) - { - $("tr td",table).removeClass('truncated'); - $("tr:last td",table).addClass('truncated'); - } - } - }); - - $.tablesorter.addWidget({ - // give the widget a id - id: "myZebra", - // format is called when the on init and when a sorting has finished - format: function(table) - { - // Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc.. - $("tbody tr:even",table).addClass('even'); - $("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even'); - $("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even'); - $("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even'); - // In case we sort again the table, we need to remove the added 'even' classes on odd rows - $("tbody tr:odd",table).removeClass('even'); - $("tbody tr.red_even:odd",table).removeClass('even').removeClass('red_even').addClass('red'); - $("tbody tr.orange_even:odd",table).removeClass('even').removeClass('orange_even').addClass('orange'); - $("tbody tr.green_even:odd",table).removeClass('even').removeClass('green_even').addClass('green'); - } - }); - $("table.listResults").tableHover(); // hover tables -EOF - ); - $this->LoadTheme(); + $this->LoadTheme(); $this->m_sRootUrl = $this->GetAbsoluteUrlAppRoot(); $sAbsURLAppRoot = addslashes($this->m_sRootUrl); diff --git a/sources/application/search/searchform.class.inc.php b/sources/application/search/searchform.class.inc.php index 86da53990..b2a96b089 100644 --- a/sources/application/search/searchform.class.inc.php +++ b/sources/application/search/searchform.class.inc.php @@ -31,6 +31,14 @@ use AttributeFriendlyName; use AttributeTagSet; use CMDBObjectSet; use Combodo\iTop\Application\Search\CriterionConversion\CriterionToSearchForm; +use Combodo\iTop\Application\UI\Component\Form\Form; +use Combodo\iTop\Application\UI\Component\DataContainer\DataContainer; +use Combodo\iTop\Application\UI\Component\Html\Html; +use Combodo\iTop\Application\UI\Component\Input\InputFactory; +use Combodo\iTop\Application\UI\Component\Panel\Panel; +use Combodo\iTop\Application\UI\Component\Panel\PanelFactory; +use Combodo\iTop\Application\UI\Layout\UIContentBlock; +use Combodo\iTop\Renderer\BlockRenderer; use CoreException; use DBObjectSearch; use DBObjectSet; @@ -41,6 +49,7 @@ use FieldExpression; use IssueLog; use MetaModel; use MissingQueryArgument; +use ScssPhp\ScssPhp\Block; use TrueExpression; use utils; use WebPage; @@ -60,7 +69,12 @@ class SearchForm */ public function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { - $sHtml = ''; + $oPage->AddUiBlock($this->GetSearchFormUIBlock($oPage, $oSet, $aExtraParams)); + return ''; + } + public function GetSearchFormUIBlock(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + { + $oUiBlock = new UIContentBlock(); $oAppContext = new ApplicationContext(); $sClassName = $oSet->GetFilter()->GetClass(); $aListParams = array(); @@ -79,7 +93,7 @@ class SearchForm { $iSearchFormId = $oPage->GetUniqueId(); $sSearchFormId = 'SimpleSearchForm'.$iSearchFormId; - $sHtml .= "
\n"; + $oUiBlock->AddHtml("
"); $aListParams['currentId'] = "$iSearchFormId"; } // Check if the current class has some sub-classes @@ -109,7 +123,7 @@ class SearchForm if (!isset($aExtraParams['result_list_outer_selector'])) { - if (isset($aExtraParams['table_id'])) + if (isset($aExtraParams['table_id']) ) { $aExtraParams['result_list_outer_selector'] = $aExtraParams['table_id']; } @@ -170,21 +184,34 @@ class SearchForm } $sAction = (isset($aExtraParams['action'])) ? $aExtraParams['action'] : utils::GetAbsoluteUrlAppRoot().'pages/UI.php'; - $sStyle = ($bOpen == 'true') ? '' : 'closed'; + $sStyle = "ibo-search-form"; + $sStyle .= ($bOpen == 'true') ? '' : ' closed'; $sStyle .= ($bAutoSubmit === true) ? '' : ' no_auto_submit'; - $sHtml .= "
\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php) - $sHtml .= "

" . Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo) . "" . Dict::S('UI:SearchToggle') . ""; - $sHtml .= ""; +//(string $sTitle = '', array $aSubBlocks = [], string $sColor = self::DEFAULT_COLOR, ?string $sId = null) + $oUiSearchBlock = new Panel(Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo), [],Panel::DEFAULT_COLOR, $sSearchFormId); + $oUiSearchBlock->SetCSSClasses("display_block"); + $oUiBlock->AddSubBlock($oUiSearchBlock); + + $sHtml = ""; $sHtml .= ""; $sHtml .= "" . Dict::S('UI:Search:Obsolescence:DisabledHint') . ""; $sHtml .= "
"; $sHtml .= "" . Dict::S('UI:Search:AutoSubmit:DisabledHint') . ""; $sHtml .= "
"; $sHtml .= "
"; - $sHtml .= "

\n"; - $sHtml .= "
\n"; - $sHtml .= "
\n
\n"; - $sHtml .= "
\n"; + $oUiSearchBlock->AddToolbarBlock(new Html($sHtml)); + + + + $oFormSearch=new Form("fs_".$sSearchFormId); + $oFormSearch->SetAction($sAction) + ->AddCSSClasses($sStyle); + $oUiSearchBlock->AddSubBlock($oFormSearch); + $oFormSearch->AddSubBlock(InputFactory::MakeForHidden("class", $sClassName)); + $oFormSearch->AddHtml( "
");//class sf_message header_message + + $oCriterionBlock = new UIContentBlock("fs_{$sSearchFormId}_criterion_outer","sf_criterion_area ibo-criterion-area"); + $oFormSearch->AddSubBlock($oCriterionBlock); if (isset($aExtraParams['query_params'])) { @@ -289,9 +316,8 @@ class SearchForm $oPage->add_ready_script('$("#fs_'.$sSearchFormId.'").search_form_handler('.json_encode($aSearchParams).');'); - return $sHtml; + return $oUiBlock; } - /** * @param \DBObjectSet $oSet * diff --git a/templates/components/datatable/layout.html.twig b/templates/components/datatable/layout.html.twig new file mode 100644 index 000000000..98d60cd11 --- /dev/null +++ b/templates/components/datatable/layout.html.twig @@ -0,0 +1,61 @@ +{% for oSubBlock in oUIBlock.GetSubBlocks() %}{{ render_block(oSubBlock, {aPage: aPage}) }}{% endfor %} +{% if oUIBlock.GetOptions()["select"] is defined %} + + + +{% endif %} + + + + {% if oUIBlock.GetOptions()["select"] is defined %} + + {% endif %} + {% for aColumn in oUIBlock.GetDisplayColumns() %} + + {% endfor %} + +
{{ aColumn["attribute_label"] }}
+ + \ No newline at end of file diff --git a/templates/components/datatable/layout.js.twig b/templates/components/datatable/layout.js.twig new file mode 100644 index 000000000..106dac842 --- /dev/null +++ b/templates/components/datatable/layout.js.twig @@ -0,0 +1,256 @@ +function checkAllDataTable(table, value) +{ + if (value) { + $(table).DataTable().rows().select(); + } + else { + $(table).DataTable().rows({ page: 'current' }).deselect(); + } + // Mark all the displayed items as check or unchecked depending on the value + $(table).find(':checkbox[name^=selectObj]:not([disabled])').each(function (index, element) { + var $currentCheckbox = $(this); + $currentCheckbox.prop('checked', value); + $currentLine = $currentCheckbox.closest("tr"); + (value) ? $currentLine.addClass("selected") : $currentLine.removeClass("selected"); + }); + // Set the 'selectionMode' for the future objects to load + var selectionMode = 'positive'; + if (value) + { + selectionMode = 'negative'; + } + $(table).parent().parent().find(':input[name=selectionMode]').val(selectionMode); + // Reset the list of saved selection... + $(':input[name^=storedSelection]').remove(); + $(table).parent().find(':checkbox[name^=selectObj]').trigger("change"); + //updateCounter(table); + return true; +} + +$('#{{ oUIBlock.GetId() }}').closest( "[role=dialog]" ).on( "dialogbeforeclose", function( event, ui ) { + console.warn("destroy on close"); + $('#{{ oUIBlock.GetId() }}').DataTable().clear(); +// $('#{{ oUIBlock.GetId() }}').DataTable().destroy(false); +} ); + +{% if oUIBlock.GetOptions()["select"] is defined %} + var oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }} = []; +{% endif %} + +if ( $.fn.dataTable.isDataTable( '#{{ oUIBlock.GetId() }}' ) ) { + console.warn("destroy on load"); + $('#{{ oUIBlock.GetId() }}').DataTable().destroy(false); +} + +var oTable{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').DataTable({ + "language": { + "processing": "{{ 'UI:Datatables:Language:Processing'|dict_s }}", + "search": "{{ 'UI:Datatables:Language:Search'|dict_s }}", + "lengthMenu": " Showing _MENU_ out",// "{{ 'UI:Datatables:Language:LengthMenu'|dict_s }}", + "zeroRecords": "{{ 'UI:Datatables:Language:ZeroRecords'|dict_s }}", + "info": "of _TOTAL_ items",//"{{ 'UI:Datatables:Language:Info'|dict_s }}", + "infoEmpty": "{{ 'UI:Datatables:Language:InfoEmpty'|dict_s }}", + "infoFiltered": "({{ 'UI:Datatables:Language:InfoFiltered'|dict_s }})", + "emptyTable": "{{ 'UI:Datatables:Language:EmptyTable'|dict_s }}", + "paginate": { + "first": "<<", + "previous": "<", + "next": ">", + "last": ">>" + }, + "aria": { + "sortAscending": ": {{ 'UI:Datatables:Language:Sort:Ascending'|dict_s }}", + "sortDescending": ": {{ 'UI:Datatables:Language:Sort:Descending'|dict_s }}" + } + }, + "lengthMenu": [[ {{ oUIBlock.GetOptions()["iPageSize"] }}, {{ oUIBlock.GetOptions()["iPageSize"]*2 }}, {{ oUIBlock.GetOptions()["iPageSize"]*3 }}, {{ oUIBlock.GetOptions()["iPageSize"]*4 }}, -1], [ {{ oUIBlock.GetOptions()["iPageSize"] }}, {{ oUIBlock.GetOptions()["iPageSize"]*2 }}, {{ oUIBlock.GetOptions()["iPageSize"]*3 }}, {{ oUIBlock.GetOptions()["iPageSize"]*4 }}, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]], + "dom": "<'ibo-datatable-toolbar'pil>t<'ibo-datatable-toolbar'pil>", + "order": [[1, "asc"]], + {% if oUIBlock.GetOptions()["select"] is defined %} + "select": { + "style": "{{ oUIBlock.GetOptions()["select"] }}" + }, + "rowCallback": function(oRow, oData){ + // Hiding pagination if only one page + if ($(this).closest('.ibo-panel--body').find('[name=selectionMode]')=="negative") { + if(! oData.id in oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }}) + { + $(oRow).select(); + $(oRow).find('td:first-child input').prop('checked', true); + } + //$(table).DataTable().rows({ page: 'current' }).select(); + } + else { + if(oData.id in oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }}) + { + $(oRow).select(); + $(oRow).find('td:first-child input').prop('checked', true); + } + } + }, + "drawCallback": function (settings) { + // Hiding pagination if only one page + if ($(this).closest('.ibo-panel--body').find('[name=selectionMode]')=="negative") { + $(this).find('[name=selectAll]').checked(); + $(table).DataTable().rows({ page: 'current' }).select(); + } + else { + $(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show(); + } + }, + {% endif %} + "rowId": "id", + "filter":false, + "retrieve": true, + "destroy": true, + "processing": true, + "serverSide": true, + "columns":[ + {% if oUIBlock.GetOptions()["select"] is defined %} + {"width": "auto", + "searchable": false, + "sortable": false, + "title": '', + "type": "html", + "data": "", + "render": function(data, type, row) + { + var oCheckboxElem = $(''); + if(row.limited_access) + { + oCheckboxElem.html('-'); + } + else + { + oCheckboxElem.find(':input').attr('data-object-id', row.id).attr('data-target-object-id', row.target_id); + } + return oCheckboxElem.prop('outerHTML'); + } + }, + {% endif %} + {% for aColumn in oUIBlock.GetDisplayColumns() %} + { + "width": "auto", + "searchable": false, + "sortable": true, + "title":"{{ aColumn["attribute_label"] }}", + "defaultContent": "", + "type": "html", + 'metadata': { + 'object_class': "{{ aColumn["object_class"] }}", + 'attribute_code': "{{ aColumn["attribute_code"] }}", + 'attribute_type': "{{ aColumn["attribute_type"] }}", + 'attribute_label': "{{ aColumn["attribute_label"] }}", + }, + "data": "{{ aColumn["class_alias"] }}/{{ aColumn["attribute_code"] }}", + "render": { + "display": function ( data, type, row ) { {{ aColumn["render"]|raw }}}, + "_":"{{ aColumn["class_alias"] }}/{{ aColumn["attribute_code"] }}", + }, + }, + {% endfor %} + ], + "ajax": $.fn.dataTable.pipeline( { + "url": "{{ oUIBlock.GetAjaxUrl() }}", + "data": {{ oUIBlock.GetAjaxData() |raw}}, + "method": "post", + "pages": 5 // number of pages to cache + } ) +}); + +oTable{{ oUIBlock.GetId() }}.off('select').on('select', function(oEvent, dt, type, indexes){ + var aData = oTable{{ oUIBlock.GetId() }}.rows(indexes).data().toArray(); + + // Checking input + $('#{{ oUIBlock.GetId() }} 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{{ oUIBlock.GetOptions()['sTableId'] }})) + { + oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }}.push(iItemId); + } + } + if ((oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }}.length == oTable{{ oUIBlock.GetId() }}.page.info()["recordsTotal"] && $(this).closest('.ibo-panel--body').find('[name=selectionMode]').val()=="negative")) + { + $('#btn_ok_{{ oUIBlock.GetOptions()['sTableId'] }}').prop('disabled', true); + } + else{ + $('#btn_ok_{{ oUIBlock.GetOptions()['sTableId'] }}').prop('disabled', false); + } +}); +oTable{{ oUIBlock.GetId() }}.off('deselect').on('deselect', function(oEvent, dt, type, indexes){ + var aData = oTable{{ oUIBlock.GetId() }}.rows(indexes).data().toArray(); + + // Checking input + $('#{{ oUIBlock.GetId() }} 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{{ oUIBlock.GetOptions()['sTableId'] }}) + { + oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }}.splice( $.inArray(removeItem, oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }}), 1 ); + } + } + if ((oSelectedItems{{ oUIBlock.GetOptions()['sTableId'] }}.length == 0 && $(this).closest('.ibo-panel--body').find('[name=selectionMode]').val()=="positive")) + { + $('#btn_ok_{{ oUIBlock.GetOptions()['sTableId'] }}').prop('disabled', true); + } + else{ + $('#btn_ok_{{ oUIBlock.GetOptions()['sTableId'] }}').prop('disabled', false); + } +}); + +// Handles submit button +//$('#form_{{ oUIBlock.GetId() }} .form_buttons .form_btn_submit').off('click').on('click', function(oEvent){ + // Extracting value(s) to be send back to the source form + {% if bMultipleSelect %} + //var oData = {values: oSelectedItems}; + {% else %} + /*var oData = {value: {}}; + var sItemId = Object.keys(oSelectedItems)[0]; + var sItemName = oSelectedItems[sItemId]; + + oData.value[sItemId] = sItemName;*/ + {% endif %} + + // Triggering value setting on the source field + //$('[data-form-path="{{aSource.sFormPath}}"][data-field-id="{{aSource.sFieldId}}"]').triggerHandler('set_current_value', oData); + + // Closing the modal + {% if tIsModal is defined and tIsModal == true %} + //$('#{{ sFormId }}').closest('.modal').modal('hide'); + {% endif %} +//}); + +$('#datatable_dlg_{{ oUIBlock.GetId() }}').dialog( +{ + autoOpen: false, + title: "{{ 'UI:ListConfigurationTitle'|dict_s }}", + width: 500, + close: function() { //save data and refresh + $('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings('onDlgCancel'); + } +}); + +$aOptions = { + 'sListId' : '{{ oUIBlock.GetId() }}', + 'oColumns' : {{ oUIBlock.GetResultColumnsAsJson()|raw }}, + 'sViewLink': '{{ oUIBlock.GetOptions()["bViewLink"] }}' , + 'iPageSize' : '{{ oUIBlock.GetOptions()["iPageSize"]}}', + 'sTableId' :'{{ oUIBlock.GetOptions()["sTableId"]}}', + "sRenderUrl": "{{ oUIBlock.GetAjaxUrl() }}", + "sSelectMode": "{{ oUIBlock.GetOptions()["select"] }}", + "oData": {{ oUIBlock.GetAjaxData() |raw}}, + 'oLabels' : {"moveup": "{{'UI:Button:MoveUp'|dict_s}}", "movedown": "{{'UI:Button:MoveDown'|dict_s}}"}, + 'oDefaultSettings' : {{ oUIBlock.GetOptions()["oDefaultSettings"]|raw}}, +}; +console.warn($aOptions); +//if (!typeof $('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings() !== "undefined") +if ($('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings()) +{ + $('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings( "destroy"); +} +$('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings( $aOptions); \ No newline at end of file diff --git a/templates/components/panel/layout.html.twig b/templates/components/panel/layout.html.twig index a7336b6e9..5a87ad589 100644 --- a/templates/components/panel/layout.html.twig +++ b/templates/components/panel/layout.html.twig @@ -1,10 +1,9 @@ {# @copyright Copyright (C) 2010-2020 Combodo SARL #} {# @license http://opensource.org/licenses/AGPL-3.0 #} {% apply spaceless %} -
+
{% block iboPanelHeader %} -
{% block iboPanelTitle %}{{ oUIBlock.GetTitle() }}{% endblock %}
{% block iboPanelToolbar %} {% for oToolbarBlock in oUIBlock.GetToolbarBlocks() %} @@ -12,6 +11,7 @@ {% endfor %} {% endblock %}
+
{% block iboPanelTitle %}{{ oUIBlock.GetTitle()|raw }}{% endblock %}
{% endblock %}
diff --git a/test/application/theme-handler/expected/css/light-grey.scss b/test/application/theme-handler/expected/css/light-grey.scss index 864323835..fe6bacc77 100644 --- a/test/application/theme-handler/expected/css/light-grey.scss +++ b/test/application/theme-handler/expected/css/light-grey.scss @@ -1093,7 +1093,6 @@ input.dp-applied { border-radius: $search-criteria-box-radius; box-shadow: $box-shadow-regular; } - .sfc_form_group, .sfm_content{ position: absolute; z-index: -1;