From c87de1024db9f1f3889700d10995711de0a0b9da Mon Sep 17 00:00:00 2001 From: acognet Date: Tue, 11 Jan 2022 07:33:20 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B04479=20-=20Impact=20analysis=20:=20Displ?= =?UTF-8?q?ay=20and=20apply=20filter=20before=20display=20impact=20analysi?= =?UTF-8?q?s=20graphical?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/displayablegraph.class.inc.php | 485 +++---- js/simple_graph.js | 1844 ++++++++++++++------------- pages/UI.php | 23 +- 3 files changed, 1187 insertions(+), 1165 deletions(-) diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index c2bda18ec..b87bfd25f 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -31,7 +31,7 @@ class DisplayableNode extends GraphNode { public $x; public $y; - + /** * Create a new node inside a graph * @param SimpleGraph $oGraph @@ -51,27 +51,27 @@ class DisplayableNode extends GraphNode { return $this->GetProperty('icon_url', ''); } - + public function GetLabel() { return $this->GetProperty('label', $this->sId); } - + public function GetWidth() { return max(32, 5*strlen($this->GetProperty('label'))); // approximation of the text's bounding box } - + public function GetHeight() { return 32; } - + public function Distance2(DisplayableNode $oNode) { $dx = $this->x - $oNode->x; $dy = $this->y - $oNode->y; - + $d2 = $dx*$dx + $dy*$dy - $this->GetHeight()*$this->GetHeight(); if ($d2 < 40) { @@ -79,12 +79,12 @@ class DisplayableNode extends GraphNode } return $d2; } - + public function Distance(DisplayableNode $oNode) { return sqrt($this->Distance2($oNode)); } - + public function GetForRaphael($aContextDefs) { $aNode = array(); @@ -100,7 +100,7 @@ class DisplayableNode extends GraphNode $aNode['label'] = $this->GetLabel(); $aNode['id'] = $this->GetId(); $fOpacity = ($this->GetProperty('is_reached') ? 1 : 0.4); - $aNode['icon_attr'] = array('opacity' => $fOpacity); + $aNode['icon_attr'] = array('opacity' => $fOpacity); $aNode['text_attr'] = array('opacity' => $fOpacity); $aNode['tooltip'] = $this->GetTooltip($aContextDefs); $aNode['context_icons'] = array(); @@ -114,7 +114,7 @@ class DisplayableNode extends GraphNode } return $aNode; } - + public function RenderAsPDF(iTopPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs) { $Alpha = 1.0; @@ -170,7 +170,7 @@ class DisplayableNode extends GraphNode $oPdf->SetTextColor(0, 0, 0); $oPdf->Text($this->x*$fScale - $width/2, ($this->y + 18)*$fScale, $this->GetProperty('label')); } - + /** * Create a "whitened" version of the icon (retaining the transparency) to be used a background for masking the underlying lines * @param string $sIconFile The path to the file containing the icon @@ -179,35 +179,35 @@ class DisplayableNode extends GraphNode protected function CreateWhiteIcon(DisplayableGraph $oGraph, $sIconFile) { $aInfo = getimagesize($sIconFile); - + $im = null; switch($aInfo['mime']) { case 'image/png': - if (function_exists('imagecreatefrompng')) - { - $im = imagecreatefrompng($sIconFile); - } - break; - + if (function_exists('imagecreatefrompng')) + { + $im = imagecreatefrompng($sIconFile); + } + break; + case 'image/gif': - if (function_exists('imagecreatefromgif')) - { - $im = imagecreatefromgif($sIconFile); - } - break; - + if (function_exists('imagecreatefromgif')) + { + $im = imagecreatefromgif($sIconFile); + } + break; + case 'image/jpeg': case 'image/jpg': - if (function_exists('imagecreatefromjpeg')) - { - $im = imagecreatefromjpeg($sIconFile); - } - break; - + if (function_exists('imagecreatefromjpeg')) + { + $im = imagecreatefromjpeg($sIconFile); + } + break; + default: - return null; - + return null; + } if($im && imagefilter($im, IMG_FILTER_COLORIZE, 255, 255, 255)) { @@ -222,17 +222,17 @@ class DisplayableNode extends GraphNode return null; } } - + public function GetObjectCount() { return 1; } - + public function GetObjectClass() { return is_object($this->GetProperty('object', null)) ? get_class($this->GetProperty('object', null)) : null; } - + protected function AddToStats($oNode, &$aNodesPerClass) { $sClass = $oNode->GetObjectClass(); @@ -256,9 +256,9 @@ class DisplayableNode extends GraphNode { $aNodesPerClass[$sClass][$sKey]['nodes'][$oNode->GetId()] = $oNode; $aNodesPerClass[$sClass][$sKey]['count'] += $oNode->GetObjectCount(); - } + } } - + /** * Retrieves the list of neighbour nodes, in the given direction: 'up' or 'down' * @param bool $bDirectionDown @@ -279,11 +279,11 @@ class DisplayableNode extends GraphNode foreach($this->GetIncomingEdges() as $oEdge) { $aNextNodes[] = $oEdge->GetSourceNode(); - } + } } return $aNextNodes; } - + /** * Replaces the next neighbour node (in the given direction: 'up' or 'down') by the supplied group node * preserving the connectivity of the graph @@ -351,7 +351,7 @@ class DisplayableNode extends GraphNode } } } - + if ($oGraph->GetNode($oNextNode->GetId())) { $oGraph->_RemoveNode($oNextNode); @@ -367,9 +367,9 @@ class DisplayableNode extends GraphNode { $oNewNode->AddObject($oNextNode->GetProperty('object')); } - } + } } - + /** * Group together (as a special kind of nodes) all the similar neighbours of the current node * @param DisplayableGraph $oGraph @@ -381,7 +381,7 @@ class DisplayableNode extends GraphNode { if ($this->GetProperty('grouped') === true) return; $this->SetProperty('grouped', true); - + $aNodesPerClass = array(); foreach($this->GetNextNodes($bDirectionDown) as $oNode) { @@ -412,7 +412,7 @@ class DisplayableNode extends GraphNode $oNewNode->SetProperty('is_reached', ($sStatus == 'reached')); $oNewNode->SetProperty('count', $aGroupProps['count']); } - + try { if ($bDirectionDown) @@ -427,8 +427,8 @@ class DisplayableNode extends GraphNode catch(Exception $e) { // Ignore this redundant egde - } - + } + foreach($aGroupProps['nodes'] as $oNextNode) { $this->ReplaceNextNodeBy($oGraph, $oNextNode, $oNewNode, $bDirectionDown); @@ -445,7 +445,7 @@ class DisplayableNode extends GraphNode } } } - + public function GetTooltip($aContextDefs) { $sHtml = ''; @@ -474,9 +474,9 @@ class DisplayableNode extends GraphNode $sHtml .= ''.$oAttDef->GetLabel().': '.$oCurrObj->GetAsHtml($sAttCode).''; } $sHtml .= ''; - return $sHtml; + return $sHtml; } - + /** * Get the description of the node in "dot" language * Used to generate the positions in the graph, but we'd better use fake label @@ -508,7 +508,7 @@ class DisplayableRedundancyNode extends DisplayableNode { return 24; } - + public function GetForRaphael($aContextDefs) { $aNode = array(); @@ -519,7 +519,7 @@ class DisplayableRedundancyNode extends DisplayableNode $aNode['x'] = $this->x; $aNode['y']= $this->y; $aNode['label'] = $this->GetLabel(); - $aNode['id'] = $this->GetId(); + $aNode['id'] = $this->GetId(); $fDiscOpacity = ($this->GetProperty('is_reached') ? 1 : 0.2); $sColor = ($this->GetProperty('is_reached_count') > $this->GetProperty('threshold')) ? '#c33' : '#999'; $aNode['disc_attr'] = array('stroke-width' => 2, 'stroke' => '#000', 'fill' => $sColor, 'opacity' => $fDiscOpacity); @@ -550,35 +550,35 @@ class DisplayableRedundancyNode extends DisplayableNode $height = $oPdf->GetStringHeight(1000, $sLabel); $xPos = (float)$this->x*$fScale - $width/2; $yPos = (float)$this->y*$fScale - $height/2; - + $oPdf->SetXY(($this->x - 16)*$fScale, ($this->y - 16)*$fScale); - + $oPdf->Cell(32*$fScale, 32*$fScale, $sLabel, 0, 0, 'C', 0, '', 0, false, 'T', 'C'); } - + /** * @see DisplayableNode::GroupSimilarNeighbours() */ public function GroupSimilarNeighbours(DisplayableGraph $oGraph, $iThresholdCount, $bDirectionUp = false, $bDirectionDown = true) { parent::GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown); - + if ($bDirectionUp) { $aNodesPerClass = array(); foreach($this->GetIncomingEdges() as $oEdge) { $oNode = $oEdge->GetSourceNode(); - + if (($oNode->GetObjectClass() !== null) && (!$oNode->GetProperty('is_reached'))) - { + { $this->AddToStats($oNode, $aNodesPerClass); } else { //$oNode->GroupSimilarNeighbours($oGraph, $iThresholdCount, $bDirectionUp, $bDirectionDown); } - } + } foreach($aNodesPerClass as $sClass => $aDefs) { foreach($aDefs as $sStatus => $aGroupProps) @@ -591,8 +591,8 @@ class DisplayableRedundancyNode extends DisplayableNode $oNewNode->SetProperty('is_reached', ($sStatus == 'is_reached')); $oNewNode->SetProperty('class', $sClass); $oNewNode->SetProperty('count', count($aGroupProps['nodes'])); - - + + $sNewId = $this->GetId().'::'.$sClass.'/'.(($sStatus == 'reached') ? '_reached': ''); $oNewNode = $oGraph->GetNode($sNewId); if ($oNewNode == null) @@ -604,7 +604,7 @@ class DisplayableRedundancyNode extends DisplayableNode $oNewNode->SetProperty('is_reached', ($sStatus == 'reached')); $oNewNode->SetProperty('count', $aGroupProps['count']); } - + try { $oOutgoingEdge = new DisplayableEdge($oGraph, '-'.$this->GetId().'-'.$oNewNode->GetId().'/'.$sStatus, $oNewNode, $this); @@ -613,7 +613,7 @@ class DisplayableRedundancyNode extends DisplayableNode { // Ignore this redundant egde } - + foreach($aGroupProps['nodes'] as $oNextNode) { $this->ReplaceNextNodeBy($oGraph, $oNextNode, $oNewNode, !$bDirectionUp); @@ -631,7 +631,7 @@ class DisplayableRedundancyNode extends DisplayableNode } } } - + public function GetTooltip($aContextDefs) { $sHtml = ''; @@ -640,9 +640,9 @@ class DisplayableRedundancyNode extends DisplayableNode $sHtml .= "".Dict::Format('UI:RelationTooltip:ImpactedItems_N_of_M' , $this->GetProperty('is_reached_count'), $this->GetProperty('min_up') + $this->GetProperty('threshold')).""; $sHtml .= "".Dict::Format('UI:RelationTooltip:CriticalThreshold_N_of_M' , $this->GetProperty('threshold'), $this->GetProperty('min_up') + $this->GetProperty('threshold')).""; $sHtml .= ''; - return $sHtml; + return $sHtml; } - + public function GetObjectCount() { @@ -666,7 +666,7 @@ class DisplayableEdge extends GraphEdge } $xStart = $oSourceNode->x * $fScale; $yStart = $oSourceNode->y * $fScale; - + $oSinkNode = $this->GetSinkNode(); if (($oSinkNode->x == null) || ($oSinkNode->y == null)) { @@ -674,9 +674,9 @@ class DisplayableEdge extends GraphEdge } $xEnd = $oSinkNode->x * $fScale; $yEnd = $oSinkNode->y * $fScale; - + $bReached = ($this->GetSourceNode()->GetProperty('is_reached') && $this->GetSinkNode()->GetProperty('is_reached')); - + $oPdf->setAlpha(1); if ($bReached) { @@ -688,8 +688,8 @@ class DisplayableEdge extends GraphEdge } $oPdf->SetLineStyle(array('width' => 2*$fScale, 'cap' => 'round', 'join' => 'miter', 'dash' => 0, 'color' => $aColor)); $oPdf->Line($xStart, $yStart, $xEnd, $yEnd); - - + + $vx = $xEnd - $xStart; $vy = $yEnd - $yStart; $l = sqrt($vx*$vx + $vy*$vy); @@ -699,24 +699,24 @@ class DisplayableEdge extends GraphEdge $uy = $vx; $lPos = max($l/2, $l - 40*$fScale); $iArrowSize = 5*$fScale; - + $x = $xStart + $lPos * $vx; $y = $yStart + $lPos * $vy; $oPdf->Line($x, $y, $x + $iArrowSize * ($ux-$vx), $y + $iArrowSize * ($uy-$vy)); - $oPdf->Line($x, $y, $x - $iArrowSize * ($ux+$vx), $y - $iArrowSize * ($uy+$vy)); + $oPdf->Line($x, $y, $x - $iArrowSize * ($ux+$vx), $y - $iArrowSize * ($uy+$vy)); } } class DisplayableGroupNode extends DisplayableNode { protected $aObjects; - + public function __construct(SimpleGraph $oGraph, $sId, $x = 0, $y = 0) { parent::__construct($oGraph, $sId, $x, $y); $this->aObjects = array(); } - + public function AddObject(DBObject $oObj = null) { if (is_object($oObj)) @@ -729,12 +729,12 @@ class DisplayableGroupNode extends DisplayableNode $this->aObjects[$oObj->GetKey()] = $oObj; } } - + public function GetObjects() { return $this->aObjects; } - + public function GetWidth() { return 50; @@ -760,7 +760,7 @@ class DisplayableGroupNode extends DisplayableNode $aNode['tooltip'] = $this->GetTooltip($aContextDefs); return $aNode; } - + public function RenderAsPDF(iTopPDF $oPdf, DisplayableGraph $oGraph, $fScale, $aContextDefs) { $bReached = $this->GetProperty('is_reached'); @@ -790,7 +790,7 @@ class DisplayableGroupNode extends DisplayableNode $oPdf->SetTextColor(0, 0, 0); $oPdf->Text($this->x * $fScale - $width / 2, ($this->y + 25) * $fScale, $this->GetProperty('label')); } - + public function GetTooltip($aContextDefs) { $iGroupIdx = $this->GetProperty('group_index'); @@ -823,7 +823,7 @@ class DisplayableGraph extends SimpleGraph protected $aTempImages; protected $aSourceObjects; protected $aSinkObjects; - + public function __construct() { parent::__construct(); @@ -831,14 +831,14 @@ class DisplayableGraph extends SimpleGraph $this->aSourceObjects = array(); $this->aSinkObjects = array(); } - + public function GetTempImageName() { $sNewTempName = tempnam(APPROOT.'data', 'img-'); $this->aTempImages[] = $sNewTempName; return $sNewTempName; } - + public function __destruct() { foreach($this->aTempImages as $sTempFile) @@ -918,7 +918,7 @@ class DisplayableGraph extends SimpleGraph $oSinkNode = $oNewGraph->GetNode($oEdge->GetSinkNode()->GetId()); $oNewEdge = new DisplayableEdge($oNewGraph, $oEdge->GetId(), $oSourceNode, $oSinkNode); } - + // Remove duplicate edges between two nodes $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge'); $aEdgeKeys = array(); @@ -946,7 +946,7 @@ class DisplayableGraph extends SimpleGraph } } } - + $oNodesIter = new RelationTypeIterator($oNewGraph, 'Node'); foreach($oNodesIter as $oNode) { @@ -981,7 +981,7 @@ class DisplayableGraph extends SimpleGraph } } } - + // Remove duplicate edges between two nodes $oEdgesIter = new RelationTypeIterator($oNewGraph, 'Edge'); $aEdgeKeys = array(); @@ -1010,10 +1010,10 @@ class DisplayableGraph extends SimpleGraph } } set_time_limit(intval($iPreviousTimeLimit)); - + return $oNewGraph; } - + /** * Initializes the positions by rendering using Graphviz in xdot format * and parsing the output. @@ -1026,7 +1026,7 @@ class DisplayableGraph extends SimpleGraph { throw new Exception($sDot); } - + $aChunks = explode(";", $sDot); foreach($aChunks as $sChunk) { @@ -1035,7 +1035,7 @@ class DisplayableGraph extends SimpleGraph $sId = $aMatches[1]; $xPos = $aMatches[2]; $yPos = $aMatches[3]; - + $oNode = $this->GetNode($sId); if ($oNode !== null) { @@ -1049,7 +1049,7 @@ class DisplayableGraph extends SimpleGraph } } } - + public function GetBoundingBox() { $xMin = null; @@ -1074,10 +1074,10 @@ class DisplayableGraph extends SimpleGraph $yMax = max($yMax, $oNode->y + $oNode->GetHeight() / 2); } } - + return array('xmin' => $xMin, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax); } - + function Translate($dx, $dy) { $oIterator = new RelationTypeIterator($this, 'Node'); @@ -1085,9 +1085,9 @@ class DisplayableGraph extends SimpleGraph { $oNode->x += $dx; $oNode->y += $dy; - } + } } - + public function UpdatePositions($aPositions) { foreach($aPositions as $sNodeId => $aPos) @@ -1107,7 +1107,7 @@ class DisplayableGraph extends SimpleGraph function GetAsJSON($sContextKey) { $aContextDefs = static::GetContextDefinitions($sContextKey, false); - + $aData = array('nodes' => array(), 'edges' => array(), 'groups' => array(), 'lists' => array()); $iGroupIdx = 0; $oIterator = new RelationTypeIterator($this, 'Node'); @@ -1131,7 +1131,7 @@ class DisplayableGraph extends SimpleGraph $aData['groups'][$iGroupIdx] = array('class' => $sClass, 'keys' => $aKeys); $oNode->SetProperty('group_index', $iGroupIdx); $iGroupIdx++; - + if ($oNode->GetProperty('is_reached')) { // Also add the objects from this group into the 'list' tab @@ -1139,11 +1139,11 @@ class DisplayableGraph extends SimpleGraph { $aData['lists'][$sClass] = $aKeys; } - else + else { $aData['lists'][$sClass] = array_merge($aData['lists'][$sClass], $aKeys); } - + } } if (($oNode instanceof DisplayableNode) && $oNode->GetProperty('is_reached') && is_object($oNode->GetProperty('object'))) @@ -1157,9 +1157,9 @@ class DisplayableGraph extends SimpleGraph } $aData['nodes'][] = $oNode->GetForRaphael($aContextDefs); } - + uksort($aData['lists'], array(get_class($this), 'SortOnClassLabel')); // sort on the localized names of the classes to provide a consistent and stable order - + $oIterator = new RelationTypeIterator($this, 'Edge'); foreach($oIterator as $sId => $oEdge) { @@ -1171,7 +1171,7 @@ class DisplayableGraph extends SimpleGraph $aEdge['attr'] = array('opacity' => $fOpacity, 'stroke' => '#000'); $aData['edges'][] = $aEdge; } - + return json_encode($aData); } @@ -1200,12 +1200,12 @@ class DisplayableGraph extends SimpleGraph { $aContextDefs = static::GetContextDefinitions($sContextKey, false); // No need to develop the parameters $oPdf = $oPage->get_tcpdf(); - + $aBB = $this->GetBoundingBox(); $this->Translate(-$aBB['xmin'], -$aBB['ymin']); - + $aMargins = $oPdf->getMargins(); - + if ($xMin == -1) { $xMin = $aMargins['left']; @@ -1222,7 +1222,7 @@ class DisplayableGraph extends SimpleGraph { $yMax = $oPdf->getPageHeight() - $aMargins['bottom']; } - + $fBreakMargin = $oPdf->getBreakMargin(); $oPdf->SetAutoPageBreak(false); $aRemainingArea = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax, $aContextDefs); @@ -1230,19 +1230,19 @@ class DisplayableGraph extends SimpleGraph $xMax = $aRemainingArea['xmax']; $yMin = $aRemainingArea['ymin']; $yMax = $aRemainingArea['ymax']; - + //$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 50, 50)); - + $fPageW = $xMax - $xMin; $fPageH = $yMax - $yMin; - - $w = $aBB['xmax'] - $aBB['xmin']; + + $w = $aBB['xmax'] - $aBB['xmin']; $h = $aBB['ymax'] - $aBB['ymin'] + 10; // Extra space for the labels which may appear "below" the icons - + $fScale = min($fPageW / $w, $fPageH / $h); $dx = ($fPageW - $fScale * $w) / 2; $dy = ($fPageH - $fScale * $h) / 2; - + $this->Translate(($xMin + $dx)/$fScale, ($yMin + $dy)/$fScale); $oIterator = new RelationTypeIterator($this, 'Edge'); @@ -1264,7 +1264,7 @@ class DisplayableGraph extends SimpleGraph $oPdf->SetAlpha(1); $oPdf->SetTextColor(0, 0, 0); } - + /** * Renders (in PDF) the key (legend) of the graphics vertically to the left of the specified zone (xmin,ymin, xmax,ymax), * and the comment (if any) at the bottom of the page. Returns the position of remaining area. @@ -1332,7 +1332,7 @@ class DisplayableGraph extends SimpleGraph $yPos += $fIconSize + 2 * $fPadding; } $oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yMin, 'D'); - + if ($sComments != '') { // Draw the comment text (surrounded by a rectangle) @@ -1347,10 +1347,10 @@ class DisplayableGraph extends SimpleGraph $oPdf->Rect($xPos, $yPos, $w + 2*$fPadding, $h + 2*$fPadding, 'D'); $yMax = $yPos - $fPadding; } - + return array('xmin' => $xMin + $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax); } - + /** * Get the context definitions from the parameters / configuration. The format of the "key" string is: * /relation_context/// @@ -1372,7 +1372,7 @@ class DisplayableGraph extends SimpleGraph else { $sLeafClass = $aLevels[2]; - + if (!MetaModel::IsValidClass($sLeafClass)) { IssueLog::Warning("GetContextDefinitions: invalid 'sLeafClass' = '$sLeafClass'. A valid class name is expected in 3rd position inside '$sContextKey' !"); @@ -1387,7 +1387,7 @@ class DisplayableGraph extends SimpleGraph $aContextDefs = array_merge($aContextDefs, $aRelationContext[$sClass][$aLevels[3]][$aLevels[4]]['items']); } } - + // Check if the queries are valid foreach($aContextDefs as $sKey => $sDefs) { @@ -1425,168 +1425,101 @@ class DisplayableGraph extends SimpleGraph * @throws \CoreException * @throws \DictExceptionMissingString */ - function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array()) - { - $aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams); - $aExcludedByClass = array(); - foreach($aExcludedObjects as $oObj) - { - if (!array_key_exists(get_class($oObj), $aExcludedByClass)) - { - $aExcludedByClass[get_class($oObj)] = array(); - } - $aExcludedByClass[get_class($oObj)][] = $oObj->GetKey(); - } - $sSftShort = Dict::S('UI:ElementsDisplayed'); - $sSearchToggle = Dict::S('UI:Search:Toggle'); - $oP->add("
\n"); - $oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_SCHEME_CYAN, 'ds_flash'); - $oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]); - $oUiSearchBlock->SetIsCollapsible(true); - $oUiHtmlBlock = new Combodo\iTop\Application\UI\Base\Component\Html\Html( -<< -
-EOF - ); - $oP->add_ready_script( -<< .sf_title").on('click', function() { - $("#dh_flash").toggleClass('closed'); - }); - $('#ReloadMovieBtn').button().button('disable'); -EOF - ); - $aSortedElements = array(); - foreach($aResults as $sClassIdx => $aObjects) - { - foreach($aObjects as $oCurrObj) - { - $sSubClass = get_class($oCurrObj); - $aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass); - } - } - - asort($aSortedElements); - $idx = 0; - foreach($aSortedElements as $sSubClass => $sClassName) - { - $oUiHtmlBlock->AddHtml("
"); - $idx++; - } - $oUiHtmlBlock->AddHtml("
"); - $oUiHtmlBlock->AddHtml("
"); - $oUiHtmlBlock->AddHtml("
\n"); - $oUiHtmlBlock->AddHtml("\n"); // class="not-printable" + function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array(), bool $sLazyLoading = false) + { + list($aExcludedByClass, $aAdditionalContexts) = $this->DisplayFiltering($sContextKey, $aContextParams, $aExcludedObjects, $oP, $aResults, $sLazyLoading); - $oUiSearchBlock->AddSubBlock($oUiHtmlBlock); - $oP->AddUiBlock($oUiSearchBlock); - $aAdditionalContexts = array(); - foreach($aContextDefs as $sKey => $aDefinition) - { - $aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql'], 'default' => (array_key_exists('default', $aDefinition) && ($aDefinition['default'] == 'yes'))); - } - - $sDirection = utils::ReadParam('d', 'horizontal'); $iGroupingThreshold = utils::ReadParam('g', 5); WebResourcesHelper::EnableSimpleGraphInWebPage($oP); - try - { + try { $this->InitFromGraphviz(); - $sExportAsPdfURL = ''; $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); - $oAppcontext = new ApplicationContext(); $sContext = $oAppContext->GetForLink(); $sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext; $sExportAsDocumentURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_attachment&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); $sLoadFromURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_json&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); $sAttachmentExportTitle = ''; - if (($sObjClass != null) && ($iObjKey != null)) - { + if (($sObjClass != null) && ($iObjKey != null)) { $oTargetObj = MetaModel::GetObject($sObjClass, $iObjKey, false); - if ($oTargetObj) - { + if ($oTargetObj) { $sAttachmentExportTitle = Dict::Format('UI:Relation:AttachmentExportOptions_Name', $oTargetObj->GetName()); } } - + $sId = 'graph'; $sStyle = ''; - if ($oP->IsPrintableVersion()) - { + if ($oP->IsPrintableVersion()) { // Optimize for printing on A4/Letter vertically $sStyle = 'margin-left:auto; margin-right:auto;'; $oP->add_ready_script("$('.simple-graph').width(18/2.54*96).resizable({ stop: function() { $(window).trigger('resized'); }});"); // Default width about 18 cm, since most browsers assume 96 dpi } $oP->add('
'); $aParams = array( - 'source_url' => $sLoadFromURL, - 'sources' => ($this->bDirectionDown ? $this->aSourceObjects : $this->aSinkObjects), - 'excluded' => $aExcludedByClass, - 'grouping_threshold' => $iGroupingThreshold, - 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), + 'source_url' => $sLoadFromURL, + 'sources' => ($this->bDirectionDown ? $this->aSourceObjects : $this->aSinkObjects), + 'excluded' => $aExcludedByClass, + 'grouping_threshold' => $iGroupingThreshold, + 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), 'export_as_attachment' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsAttachment'), 'obj_class' => $sObjClass, 'obj_key' => $iObjKey), - 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')), - 'labels' => array( - 'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'), + 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')), + 'labels' => array( + 'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'), 'export_as_attachment_title' => $sAttachmentExportTitle, - 'export' => Dict::S('UI:Button:Export'), - 'cancel' => Dict::S('UI:Button:Cancel'), - 'title' => Dict::S('UI:RelationOption:Title'), - 'untitled' => Dict::S('UI:RelationOption:Untitled'), - 'include_list' => Dict::S('UI:RelationOption:IncludeList'), - 'comments' => Dict::S('UI:RelationOption:Comments'), - 'grouping_threshold' => Dict::S('UI:RelationOption:GroupingThreshold'), - 'refresh' => Dict::S('UI:Button:Refresh'), - 'check_all' => Dict::S('UI:SearchValue:CheckAll'), - 'uncheck_all' => Dict::S('UI:SearchValue:UncheckAll'), - 'none_selected' => Dict::S('UI:Relation:NoneSelected'), - 'nb_selected' => Dict::S('UI:SearchValue:NbSelected'), - 'additional_context_info' => Dict::S('UI:Relation:AdditionalContextInfo'), - 'zoom' => Dict::S('UI:Relation:Zoom'), - 'loading' => Dict::S('UI:Loading'), + 'export' => Dict::S('UI:Button:Export'), + 'cancel' => Dict::S('UI:Button:Cancel'), + 'title' => Dict::S('UI:RelationOption:Title'), + 'untitled' => Dict::S('UI:RelationOption:Untitled'), + 'include_list' => Dict::S('UI:RelationOption:IncludeList'), + 'comments' => Dict::S('UI:RelationOption:Comments'), + 'grouping_threshold' => Dict::S('UI:RelationOption:GroupingThreshold'), + 'refresh' => Dict::S('UI:Button:Refresh'), + 'check_all' => Dict::S('UI:SearchValue:CheckAll'), + 'uncheck_all' => Dict::S('UI:SearchValue:UncheckAll'), + 'none_selected' => Dict::S('UI:Relation:NoneSelected'), + 'nb_selected' => Dict::S('UI:SearchValue:NbSelected'), + 'additional_context_info' => Dict::S('UI:Relation:AdditionalContextInfo'), + 'zoom' => Dict::S('UI:Relation:Zoom'), + 'loading' => Dict::S('UI:Loading'), ), - 'page_format' => array( - 'label' => Dict::S('UI:Relation:PDFExportPageFormat'), + 'page_format' => array( + 'label' => Dict::S('UI:Relation:PDFExportPageFormat'), 'values' => array( - 'A3' => Dict::S('UI:PageFormat_A3'), - 'A4' => Dict::S('UI:PageFormat_A4'), + 'A3' => Dict::S('UI:PageFormat_A3'), + 'A4' => Dict::S('UI:PageFormat_A4'), 'Letter' => Dict::S('UI:PageFormat_Letter'), ), ), - 'page_orientation' => array( - 'label' => Dict::S('UI:Relation:PDFExportPageOrientation'), + 'page_orientation' => array( + 'label' => Dict::S('UI:Relation:PDFExportPageOrientation'), 'values' => array( 'P' => Dict::S('UI:PageOrientation_Portrait'), 'L' => Dict::S('UI:PageOrientation_Landscape'), ), ), - 'additional_contexts' => $aAdditionalContexts, - 'context_key' => $sContextKey, + 'additional_contexts' => $aAdditionalContexts, + 'context_key' => $sContextKey, ); - if (!extension_loaded('gd')) - { + if (!extension_loaded('gd')) { // PDF export requires GD unset($aParams['export_as_pdf']); } - if (!extension_loaded('gd') || is_null($sObjClass) || is_null($iObjKey)) - { + if (!extension_loaded('gd') || is_null($sObjClass) || is_null($iObjKey)) { // Export as Attachment requires GD (for building the PDF) AND a valid objclass/objkey couple unset($aParams['export_as_attachment']); } - $oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");"); + if ($oP->IsPrintableVersion() || !$sLazyLoading) { + $oP->add_ready_script(" $('#$sId').simple_graph(".json_encode($aParams).");"); + } else { + $oP->add_script("function Load(){var aExcluded = []; $('input[name^=excluded]').each( function() {if (!$(this).prop('checked')) { aExcluded.push($(this).val()); }} ); var params= $.extend(".json_encode($aParams).", {excluded_classes: aExcluded}); $('#$sId').simple_graph(params);}"); + } } catch(Exception $e) { $oP->add('
'.$e->getMessage().'
'); } $oP->add_script( -<<GetKey(); + } + $sSftShort = Dict::S('UI:ElementsDisplayed'); + $oP->add("
\n"); + $oUiSearchBlock = new Panel($sSftShort, [], Panel::ENUM_COLOR_SCHEME_CYAN, 'dh_flash'); + $oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]); + $oUiSearchBlock->SetIsCollapsible(true); + $oUiHtmlBlock = new Combodo\iTop\Application\UI\Base\Component\Html\Html( + << +
+EOF + ); + $oP->add_ready_script( + << .sf_title").on("click", function() { + $("#dh_flash").toggleClass("closed"); + }); + $("#ReloadMovieBtn").button().button("disable"); +EOF + ); + if ($sLazyLoading) { + $oP->add_ready_script("$('#ReloadMovieBtn').button('enable');"); + } else { + $oP->add_ready_script("$('#dh_flash').addClass('closed');"); + } + $aSortedElements = array(); + foreach ($aResults as $sClassIdx => $aObjects) { + foreach ($aObjects as $oCurrObj) { + $sSubClass = get_class($oCurrObj); + $aSortedElements[$sSubClass] = MetaModel::GetName($sSubClass); + } + } + + asort($aSortedElements); + $idx = 0; + foreach ($aSortedElements as $sSubClass => $sClassName) { + $oUiHtmlBlock->AddHtml("
"); + $idx++; + } + $oUiHtmlBlock->AddHtml("
"); + if ($sLazyLoading) { + $sOnCLick = "Load(); $('#ReloadMovieBtn').attr('onclick','DoReload()');$('#ReloadMovieBtn').html('".Dict::S('UI:Button:Refresh')."');"; + $oUiHtmlBlock->AddHtml("
"); + } else { + $sOnCLick = "DoReload()"; + $oUiHtmlBlock->AddHtml("
"); + } + $oUiHtmlBlock->AddHtml("\n"); + $oUiHtmlBlock->AddHtml("\n"); // class="not-printable" + + $oUiSearchBlock->AddSubBlock($oUiHtmlBlock); + $oP->AddUiBlock($oUiSearchBlock); + + $aAdditionalContexts = array(); + foreach ($aContextDefs as $sKey => $aDefinition) { + $aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql'], 'default' => (array_key_exists('default', $aDefinition) && ($aDefinition['default'] == 'yes'))); + } + + return array($aExcludedByClass, $aAdditionalContexts); + } + } diff --git a/js/simple_graph.js b/js/simple_graph.js index 41d5783a7..1e2980bc8 100644 --- a/js/simple_graph.js +++ b/js/simple_graph.js @@ -9,955 +9,961 @@ $(function() // the widget definition, where "itop" is the namespace, // "dashboard" the widget name $.widget( "itop.simple_graph", - { - // default options - options: { - align: 'center', - 'vertical-align': 'middle', - source_url: null, - sources: {}, - excluded: {}, - export_as_pdf: null, - page_format: { label: 'Page Format:', values: { A3: 'A3', A4: 'A4', Letter: 'Letter' }, 'default': 'A4'}, - page_orientation: { label: 'Page Orientation:', values: { P: 'Portait', L: 'Landscape' }, 'default': 'L' }, - labels: { - export_pdf_title: 'PDF Export Options', - cancel: 'Cancel', 'export': 'Export', - title: 'Document Title', - include_list: 'Include the list of objects', - comments: 'Comments', - grouping_threshold: 'Grouping Threshold', - additional_context_info: 'Additional Context Info', - refresh: 'Refresh', - check_all: 'Check All', - uncheck_all: 'Uncheck All', - none_selected: 'None', - nb_selected: '# selected', - zoom: 'Zoom', - loading: 'Loading...' - }, - export_as_document: null, - drill_down: null, - grouping_threshold: 10, - excluded_classes: [], - attachment_obj_class: null, - attachment_obj_key: null, - additional_contexts: [], - context_key: '' - }, - css_classes: - { - has_focus: 'ibo-has-focus' - }, - // the constructor - _create: function() - { - var me = this; - this.aNodes = []; - this.aEdges = []; - this.fZoom = 1.0; - this.xOffset = 0; - this.yOffset = 0; - this.xPan = 0; - this.yPan = 0; - this.iTextHeight = 12; - this.fSliderZoom = 1.0; - this.bInUpdateSliderZoom = false; - this.bRedrawNeeded = false; - - this.oPaper = Raphael(this.element.get(0), this.element.width(), screen.availHeight * 2 / 3); - - this.element - .addClass('panel-resized') - .addClass('itop-simple-graph') - .addClass('graph'); - - this._create_toolkit_menu(); - this._build_context_menus(); - this.sTabId = null; - var jTabPanel = this.element.closest('.ui-tabs-panel'); - if (jTabPanel.length > 0) { - // We are inside a tab, find out which one and hook its activation - this.sTabId = jTabPanel.attr('id'); - var jTabs = this.element.closest('.ibo-tab-container'); - jTabs.on("tabsactivate", function (event, ui) { - me._on_tabs_activate(ui); - }); - } - $(window).bind('resized', function () { - var that = me; - window.setTimeout(function () { - that._on_resize(); - }, 50); - }); - $('#dh_flash').bind('toggle_complete', function () { - var that = me; - window.setTimeout(function () { - that._on_resize(); - }, 50); - }); - this.element.on('mousewheel', function (event, delta, deltaX, deltaY) { - return me._on_mousewheel(event, delta, deltaX, deltaY); - }); - $(document).on('click', function (e) { - if ($(e.target).closest(me.element).length === 0) { - me.element.removeClass(me.css_classes.has_focus); - } else { - me.element.addClass(me.css_classes.has_focus); - } - }); - if (this.options.source_url != null) { - this.load_from_url(this.options.source_url); - } - }, - // called when created, and later when changing options - _refresh: function() - { - this.draw(); - }, - // events bound via _bind are removed automatically - // revert other modifications here - _destroy: function() - { - var sId = this.element.attr('id'); - this.element - .removeClass('itop-simple-graph') - .removeClass('graph'); - - $('#tk_graph'+sId).remove(); - $('#graph_'+sId+'_export_as_pdf').remove(); - - }, - // _setOptions is called with a hash of all options that are changing - _setOptions: function() - { - this._superApply(arguments); - }, - // _setOption is called for each individual option that is changing - _setOption: function( key, value ) - { - this._superApply(arguments); - }, - draw: function() - { - this._updateBBox(); - this.auto_scale(); - this.oPaper.clear(); - this._reset - this.oPaper.setViewBox(this.xPan, this.yPan, this.element.width(), this.element.height(), false); - for(var k in this.aNodes) - { - this.aNodes[k].aElements = []; - this._draw_node(this.aNodes[k]); - } - for(var k in this.aEdges) - { - this.aEdges[k].aElements = []; - this._draw_edge(this.aEdges[k]); - } - var me = this; - this.oBackground = this.oPaper - .rect(-10000, -10000, 20000, 20000) - .attr({fill: '#fff', opacity: 0, cursor: 'move'}) - .toBack() - .drag(function(dx, dy, x, y, event) { me._on_background_move(dx, dy, x, y, event); }, function(x, y, event) { me._on_background_drag_start(x, y, event); }, function (event) { me._on_background_drag_end(event); }); - this._make_tooltips(); - }, - _draw_node: function(oNode) - { - var iWidth = oNode.width; - var iHeight = 32; - var iFontSize = 10; - var fTotalZoom = this.fZoom * this.fSliderZoom; - var xPos = Math.round(oNode.x * fTotalZoom + this.xOffset); - var yPos = Math.round(oNode.y * fTotalZoom + this.yOffset); - oNode.tx = 0; - oNode.ty = 0; - switch(oNode.shape) - { - case 'disc': - oScaledAttr = {}; - for(k in oNode.disc_attr) + // default options + options: { - value = oNode.disc_attr[k] - switch(k) - { - // Scalable attributes - case 'stroke-width': - value = value * fTotalZoom; - break; - } - oScaledAttr[k] = value; - } - oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*fTotalZoom / 2).attr(oScaledAttr)); - var oText = this.oPaper.text(xPos, yPos, oNode.label); - oNode.text_attr['font-size'] = iFontSize * fTotalZoom; - oText.attr(oNode.text_attr); - //oText.transform('s'+this.fZoom); - oNode.aElements.push(oText); - break; - - case 'group': - oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*fTotalZoom / 2).attr({fill: '#fff', 'stroke-width':0})); - oScaledAttr = {}; - for(k in oNode.disc_attr) - { - value = oNode.disc_attr[k] - switch(k) - { - // Scalable attributes - case 'stroke-width': - value = value * fTotalZoom; - break; - } - oScaledAttr[k] = value; - } - oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*fTotalZoom / 2).attr(oScaledAttr)); - var xIcon = xPos - 18 * fTotalZoom; - var yIcon = yPos - 18 * fTotalZoom; - oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon, yIcon, 16*fTotalZoom, 16*fTotalZoom).attr(oNode.icon_attr)); - oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 18*fTotalZoom, yIcon, 16*fTotalZoom, 16*fTotalZoom).attr(oNode.icon_attr)); - oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 9*fTotalZoom, yIcon + 18*fTotalZoom, 16*fTotalZoom, 16*fTotalZoom).attr(oNode.icon_attr)); - var oText = this.oPaper.text(xPos, yPos +2, oNode.label); - oNode.text_attr['font-size'] = iFontSize * fTotalZoom; - oText.attr(oNode.text_attr); - //oText.transform('s'+this.fZoom); - var oBB = oText.getBBox(); - var dy = iHeight/2*fTotalZoom + oBB.height/2; - oText.remove(); - oText = this.oPaper.text(xPos, yPos +dy +2, oNode.label); - oText.attr(oNode.text_attr); - //oText.transform('s'+this.fZoom); - oNode.aElements.push(oText); - oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9})); - oText.toFront(); - break; - - case 'icon': - if(Raphael.svg) - { - // the colorShift plugin works only in SVG - oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * fTotalZoom/2, yPos - iHeight * fTotalZoom/2, iWidth*fTotalZoom, iHeight*fTotalZoom).colorShift('#fff', 1)); - } - oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * fTotalZoom/2, yPos - iHeight * fTotalZoom/2, iWidth*fTotalZoom, iHeight*fTotalZoom).attr(oNode.icon_attr)); - - var idx = 0; - for(var i in oNode.context_icons) - { - var sgn = 2*(idx % 2) -1; // Suite: -1, 1, -1, 1, -1, 1, -1, etc. - var coef = Math.floor((1+idx)/2) * sgn; // Suite: 0, 1, -1, 2, -2, 3, -3, etc. - var alpha = coef*Math.PI/4 - Math.PI/2; - var x = xPos + Math.cos(alpha) * 1.25*iWidth * fTotalZoom / 2; - var y = yPos + Math.sin(alpha) * 1.25*iWidth * fTotalZoom / 2; - var l = iWidth/3 * fTotalZoom; - oNode.aElements.push(this.oPaper.image(oNode.context_icons[i], x - l/2, y - l/2, l , l).attr(oNode.icon_attr)); - idx++; - } - var oText = this.oPaper.text( xPos, yPos, oNode.label); - oNode.text_attr['font-size'] = iFontSize * fTotalZoom; - oText.attr(oNode.text_attr); - //oText.transform('S'+fTotalZoom); - var oBB = oText.getBBox(); - var dy = iHeight/2*fTotalZoom + oBB.height/2; - oText.remove(); - oText = this.oPaper.text( xPos, yPos + dy, oNode.label); - oText.attr(oNode.text_attr); - //oText.transform('S'+fTotalZoom); - oNode.aElements.push(oText); - oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}).toBack()); - break; - } - if (oNode.source) - { - oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*fTotalZoom / 2).attr({stroke: '#c33', 'stroke-width': 3*fTotalZoom }).toBack()); - } - if (oNode.sink) - { - oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*fTotalZoom / 2).attr({stroke: '#33c', 'stroke-width': 3*fTotalZoom }).toBack()); - } - - var me = this; - for(k in oNode.aElements) - { - var sNodeId = oNode.id; - $(oNode.aElements[k].node).attr({'data-type': oNode.shape, 'data-id': oNode.id} ).attr('class', 'popupMenuTarget'); - oNode.aElements[k].drag( - function(dx, dy, x, y, event) { - clearTimeout($(this.node).data('openTimeoutId')); - me._move(sNodeId, dx, dy, x, y, event); + align: 'center', + 'vertical-align': 'middle', + source_url: null, + sources: {}, + excluded: {}, + export_as_pdf: null, + page_format: { label: 'Page Format:', values: { A3: 'A3', A4: 'A4', Letter: 'Letter' }, 'default': 'A4'}, + page_orientation: { label: 'Page Orientation:', values: { P: 'Portait', L: 'Landscape' }, 'default': 'L' }, + labels: { + export_pdf_title: 'PDF Export Options', + cancel: 'Cancel', 'export': 'Export', + title: 'Document Title', + include_list: 'Include the list of objects', + comments: 'Comments', + grouping_threshold: 'Grouping Threshold', + additional_context_info: 'Additional Context Info', + refresh: 'Refresh', + check_all: 'Check All', + uncheck_all: 'Uncheck All', + none_selected: 'None', + nb_selected: '# selected', + zoom: 'Zoom', + loading: 'Loading...' }, - function(x, y, event) { - me._drag_start(sNodeId, x, y, event); - }, - function (event) { - me._drag_end(sNodeId, event); - } - ); - } - }, - _move: function(sNodeId, dx, dy, x, y, event) - { - var fTotalZoom = this.fZoom * this.fSliderZoom; - var origDx = dx / fTotalZoom; - var origDy = dy / fTotalZoom; - - var oNode = this._find_node(sNodeId); - oNode.x = oNode.xOrig + origDx; - oNode.y = oNode.yOrig + origDy; - - for(k in oNode.aElements) - { - oNode.aElements[k].transform('t'+(oNode.tx + dx)+', '+(oNode.ty + dy)); - - for(j in this.aEdges) + export_as_document: null, + drill_down: null, + grouping_threshold: 10, + excluded_classes: [], + attachment_obj_class: null, + attachment_obj_key: null, + additional_contexts: [], + context_key: '' + }, + css_classes: { - var oEdge = this.aEdges[j]; - if ((oEdge.source_node_id == sNodeId) || (oEdge.sink_node_id == sNodeId)) - { - var sPath = this._get_edge_path(oEdge); - oEdge.aElements[0].attr({path: sPath}); - } - } - } - }, - _drag_start: function(sNodeId, x, y, event) - { - var oNode = this._find_node(sNodeId); - oNode.xOrig = oNode.x; - oNode.yOrig = oNode.y; - - }, - _drag_end: function(sNodeId, event) - { - var fTotalZoom = this.fZoom * this.fSliderZoom; - var oNode = this._find_node(sNodeId); - oNode.tx += (oNode.x - oNode.xOrig) * fTotalZoom; - oNode.ty += (oNode.y - oNode.yOrig) * fTotalZoom; - oNode.xOrig = oNode.x; - oNode.yOrig = oNode.y; - this._updateBBox(); - }, - _updateBBox: function() - { - this.options.xmin = 9999; - this.options.xmax = -9999; - this.options.ymin = 9999; - this.options.ymax = -9999; - for(var k in this.aNodes) + has_focus: 'ibo-has-focus' + }, + // the constructor + _create: function() { - this.options.xmin = Math.min(this.aNodes[k].x + this.aNodes[k].tx - this.aNodes[k].width/2, this.options.xmin); - this.options.xmax = Math.max(this.aNodes[k].x + this.aNodes[k].tx + this.aNodes[k].width/2, this.options.xmax); - this.options.ymin = Math.min(this.aNodes[k].y + this.aNodes[k].ty - this.aNodes[k].width/2, this.options.ymin); - this.options.ymax = Math.max(this.aNodes[k].y + this.aNodes[k].ty + this.aNodes[k].width/2, this.options.ymax); - } - }, - _get_edge_path: function(oEdge) - { - var fTotalZoom = this.fZoom * this.fSliderZoom; - var oStart = this._find_node(oEdge.source_node_id); - var oEnd = this._find_node(oEdge.sink_node_id); - var iArrowSize = 5; - - if ((oStart == null) || (oEnd == null)) return ''; - - var xStart = Math.round(oStart.x * fTotalZoom + this.xOffset); - var yStart = Math.round(oStart.y * fTotalZoom + this.yOffset); - var xEnd = Math.round(oEnd.x * fTotalZoom + this.xOffset); - var yEnd = Math.round(oEnd.y * fTotalZoom + this.yOffset); + var me = this; + this.aNodes = []; + this.aEdges = []; + this.fZoom = 1.0; + this.xOffset = 0; + this.yOffset = 0; + this.xPan = 0; + this.yPan = 0; + this.iTextHeight = 12; + this.fSliderZoom = 1.0; + this.bInUpdateSliderZoom = false; + this.bRedrawNeeded = false; - var sPath = Raphael.format('M{0},{1}L{2},{3}', xStart, yStart, xEnd, yEnd); - var vx = (xEnd - xStart); - var vy = (yEnd - yStart); - var l = Math.sqrt(vx*vx+vy*vy); - vx = vx / l; - vy = vy / l; - var ux = -vy; - var uy = vx; - var lPos = Math.max(l/2, l - 40*fTotalZoom); - var xArrow = xStart + vx * lPos; - var yArrow = yStart + vy * lPos; - sPath += Raphael.format('M{0},{1}l{2},{3}M{4},{5}l{6},{7}', xArrow, yArrow, fTotalZoom * iArrowSize *(-vx + ux), fTotalZoom * iArrowSize *(-vy + uy), xArrow, yArrow, fTotalZoom * iArrowSize *(-vx - ux), fTotalZoom * iArrowSize *(-vy - uy)); - return sPath; - }, - _draw_edge: function(oEdge) - { - var fTotalZoom = this.fZoom * this.fSliderZoom; - var fStrokeSize = Math.max(1, 2 * fTotalZoom); - var sPath = this._get_edge_path(oEdge); - var oAttr = $.extend(oEdge.attr); - oAttr['stroke-linecap'] = 'round'; - oAttr['stroke-width'] = fStrokeSize; - oEdge.aElements.push(this.oPaper.path(sPath).attr(oAttr).toBack()); - }, - _find_node: function(sId) - { - for(var k in this.aNodes) - { - if (this.aNodes[k].id == sId) return this.aNodes[k]; - } - return null; - }, - auto_scale: function() - { - var fMaxZoom = 1.5; - - iMargin = 10; - xmin = this.options.xmin - iMargin; - xmax = this.options.xmax + iMargin; - ymin = this.options.ymin - iMargin; - ymax = this.options.ymax + iMargin; - var xScale = this.element.width() / (xmax - xmin); - var yScale = this.element.height() / (ymax - ymin + this.iTextHeight); + this.oPaper = Raphael(this.element.get(0), this.element.width(), screen.availHeight * 2 / 3); - this.fZoom = Math.min(xScale, yScale, fMaxZoom); - switch(this.options.align) - { - case 'left': - this.xOffset = -xmin * this.fZoom; - break; - - case 'right': - this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom); - break; - - case 'center': - this.xOffset = -xmin * this.fZoom + (this.element.width() - (xmax - xmin) * this.fZoom) / 2; - break; - } - switch(this.options['vertical-align']) - { - case 'top': - this.yOffset = -ymin * this.fZoom; - break; - - case 'bottom': - this.yOffset = this.element.height() - (ymax + this.iTextHeight) * this.fZoom; - break; - - case 'middle': - this.yOffset = -ymin * this.fZoom + (this.element.height() - (ymax - ymin + this.iTextHeight) * this.fZoom) / 2; - break; - } - }, - add_node: function(oNode) - { - oNode.aElements = []; - oNode.tx = 0; - oNode.ty = 0; - this.aNodes.push(oNode); - }, - add_edge: function(oEdge) - { - oEdge.aElements = []; - this.aEdges.push(oEdge); - }, - show_group: function(sGroupId) - { - this._close_all_tooltips(); - // Activate the 3rd tab - this.element.closest('[data-role="ibo-tab-container"]').tab_container("GetTabsWidget").option("active", 2); - // Scroll into view the group - if ($('#'+sGroupId).length > 0) - { - $('#'+sGroupId)[0].scrollIntoView(); - } - }, - _create_toolkit_menu: function() - { - var sPopupMenuId = 'tk_graph'+this.element.attr('id'); - var sHtml = '
'; - var sId = this.element.attr('id'); - sHtml += '
'; - if (this.options.additional_contexts.length > 0) - { - sHtml += '
' - } - sHtml += ''; - sHtml += '
'; - sHtml += '
'; - sHtml += '
'; - sHtml += '
'; - sHtml += '
'; - sHtml += '
'; - sHtml += '
'; - sHtml += '
'; + this.element + .addClass('panel-resized') + .addClass('itop-simple-graph') + .addClass('graph'); - - this.element.before(sHtml); - $('#'+sPopupMenuId+'>ul').popupmenu(); - - - var me = this; - $('#'+sPopupMenuId+'_pdf').on('click', function() { me.export_as_pdf(); }); - $('#'+sPopupMenuId+'_attachment').on('click', function() { me.export_as_attachment(); }); - $('#'+sId+'_zoom').slider({ min: 0, max: 5, value: 1, step: 0.25, change: function() { me._on_zoom_change( $(this).slider('value')); } }); - $('#'+sId+'_zoom_plus').on('click', function() { $('#'+sId+'_zoom').slider('value', 0.25 + $('#'+sId+'_zoom').slider('value')); return false; }); - $('#'+sId+'_zoom_minus').on('click', function() { $('#'+sId+'_zoom').slider('value', $('#'+sId+'_zoom').slider('value') - 0.25); return false; }); - $('#'+sId+'_contexts').multiselect({header: true, checkAllText: this.options.labels.check_all, uncheckAllText: this.options.labels.uncheck_all, noneSelectedText: this.options.labels.none_selected, selectedText: this.options.labels.nb_selected, selectedList: 1}); - $('#'+sId+'_refresh_btn').button().on('click', function() { me.reload(); }); - }, - _build_context_menus: function() - { - var sId = this.element.attr('id'); - var me = this; - - $.contextMenu({ - selector: '#'+sId+' .popupMenuTarget', - build: function(trigger, e) { - // this callback is executed every time the menu is to be shown - // its results are destroyed every time the menu is hidden - // e is the original contextmenu event, containing e.pageX and e.pageY (amongst other data) - var sType = trigger.attr('data-type'); - var sNodeId = trigger.attr('data-id'); - var oNode = me._find_node(sNodeId); - clearTimeout(trigger.data('openTimeoutId')); - - /* - var sObjName = trigger.attr('data-class'); - var sIndex = trigger.attr('data-index'); - var originalEvent = e; - var bHasItems = false; - */ - var oResult = {callback: null, items: {}}; - switch(sType) - { - case 'group': - var sGroupIndex = oNode.group_index; - if( $('#relation_group_'+sGroupIndex).length > 0) - { - oResult = { - callback: function(key, options) { - var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value - me.show_group('relation_group_'+sGroupIndex); - }, - items: { 'show': {name: me.options.drill_down.label } } - }; - } - break; - - case 'icon': - var sObjClass = oNode.obj_class; - var sObjKey = oNode.obj_key; - oResult = { - callback: function(key, options) { - var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value - var sURL = me.options.drill_down.url.replace('%1$s', sObjClass).replace('%2$s', sObjKey); - window.location.href = sURL; - }, - items: { 'details': {name: me.options.drill_down.label } } - }; - break; - - default: - oResult = false; // No context menu - } - return oResult; - } - }); - - }, - export_as_pdf: function() - { - this._export_dlg(this.options.labels.export_pdf_title, this.options.export_as_pdf.url, 'download_pdf'); - }, - _export_dlg: function(sTitle, sSubmitUrl, sOperation) - { - var sId = this.element.attr('id'); - var me = this; - var oPositions = {}; - for(k in this.aNodes) - { - oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y }; - } - var sHtmlForm = '
'; - sHtmlForm += ''; - sHtmlForm += ''; - $('#'+sId+'_contexts').multiselect('getChecked').each(function() { - sHtmlForm += ''; - }); - - sHtmlForm += ''; - for(k in this.options.excluded_classes) - { - sHtmlForm += ''; - } - for(var k1 in this.options.sources) - { - for(var k2 in this.options.sources[k1]) - { - sHtmlForm += ''; - } - } - for(var k1 in this.options.excluded) - { - for(var k2 in this.options.excluded[k1]) - { - sHtmlForm += ''; - } - } - if (sOperation == 'attachment') - { - sHtmlForm += ''; - sHtmlForm += ''; - } - sHtmlForm += ''; - sHtmlForm += ''; - sHtmlForm += ''; - sHtmlForm += ''; - sHtmlForm += ''; - sHtmlForm += ''; - sHtmlForm += '
'+this.options.page_format.label+'
'+this.options.page_orientation.label+'
'+this.options.labels.title+'
'+this.options.labels.comments+'
'; - sHtmlForm += ''; - - $('body').append(sHtmlForm); - $('#graph_'+this.element.attr('id')+'_export_dlg input[name="positions"]').val(JSON.stringify(oPositions)); - var me = this; - if (sOperation == 'attachment') - { - $('#GraphExportDlg'+this.element.attr('id')+' form').on('submit', function() { return me._on_export_as_attachment(); }); - } - $('#GraphExportDlg'+this.element.attr('id')).dialog({ - width: 'auto', - modal: true, - title: sTitle, - close: function() { $(this).remove(); }, - buttons: [ - {text: this.options.labels['cancel'], click: function() { $(this).dialog('close');} }, - {text: this.options.labels['export'], click: function() { $('#graph_'+me.element.attr('id')+'_export_dlg').submit(); $(this).dialog('close');} }, - ] - }); - }, - _on_zoom_change: function(sliderValue) - { - if(!this.bInUpdateSliderZoom) - { - var Z0 = this.fSliderZoom; - var X = this.xOffset - this.element.width()/2; - var Y = this.yOffset - this.element.height()/2; - - this.fSliderZoom = Math.pow(2 , (sliderValue - 1)); - - var Z1 = this.fSliderZoom = Math.pow(2 , (sliderValue - 1)); - var dx = X * (1 - Z1/Z0); - var dy = Y * (1 - Z1/Z0); - this.xPan += dx; - this.yPan += dy; - this._close_all_tooltips(); - this.oPaper.setViewBox(this.xPan, this.yPan, this.element.width(), this.element.height(), false); - this.draw(); - } - }, - _on_mousewheel: function(event, delta, deltaX, deltaY) - { - if(this.element.hasClass(this.css_classes.has_focus)) - { - var fStep = 0.25*delta; - var sId = this.element.attr('id'); - $('#'+sId+'_zoom').slider('value', fStep + $('#'+sId+'_zoom').slider('value')); - } - }, - _on_resize: function() - { - this.auto_scale(); - this._close_all_tooltips(); - this.draw(); - }, - _on_tabs_activate: function(ui) - { - if (ui.newPanel[0] === $('#'+this.sTabId)[0]) - { - if (this.bRedrawNeeded) - { - this._updateBBox(); - this.auto_scale(); - this.oPaper.setSize(this.element.width(), this.element.height()); - this._reset_pan_and_zoom(); - this.draw(); - bRedrawNeeded = false; - } - } - }, - load: function(oData) - { - var me = this; - var sId = this.element.attr('id'); - this.aNodes = []; - this.aEdges = []; - for(k in oData.nodes) - { - this.add_node(oData.nodes[k]); - } - for(k in oData.edges) - { - this.add_edge(oData.edges[k]); - } - if (oData.groups) - { - this.refresh_groups(oData.groups); - } - if (oData.lists) - { - if (this.options.excluded_classes.length > 0) { - var newList = {}; - $.each(oData.lists, function (index, listId) { - if (me.options.excluded_classes.indexOf(index) < 0) { - newList[index] = listId; - } + this._create_toolkit_menu(); + this._build_context_menus(); + this.sTabId = null; + var jTabPanel = this.element.closest('.ui-tabs-panel'); + if (jTabPanel.length > 0) { + // We are inside a tab, find out which one and hook its activation + this.sTabId = jTabPanel.attr('id'); + var jTabs = this.element.closest('.ibo-tab-container'); + jTabs.on("tabsactivate", function (event, ui) { + me._on_tabs_activate(ui); }); - me.refresh_lists(newList); - } else { - me.refresh_lists(oData.lists); } - } - if (me.element.is(':visible')) { - me._updateBBox(); - me.auto_scale(); - me._reset_pan_and_zoom(); - me.draw(); - } else { - me.bRedrawNeeded = true; - } - }, - refresh_groups: function(aGroups) - { - if(this.element.parents('.ibo-tab-container').attr('data-status') === 'loaded'){ - if ($('#impacted_groups').length > 0) + $(window).bind('resized', function () { + var that = me; + window.setTimeout(function () { + that._on_resize(); + }, 50); + }); + $('#dh_flash').bind('toggle_complete', function () { + var that = me; + window.setTimeout(function () { + that._on_resize(); + }, 50); + }); + this.element.on('mousewheel', function (event, delta, deltaX, deltaY) { + return me._on_mousewheel(event, delta, deltaX, deltaY); + }); + $(document).on('click', function (e) { + if ($(e.target).closest(me.element).length === 0) { + me.element.removeClass(me.css_classes.has_focus); + } else { + me.element.addClass(me.css_classes.has_focus); + } + }); + if (this.options.source_url != null) { + this.load_from_url(this.options.source_url); + } + }, + // called when created, and later when changing options + _refresh: function() + { + this.draw(); + }, + // events bound via _bind are removed automatically + // revert other modifications here + _destroy: function() + { + var sId = this.element.attr('id'); + this.element + .removeClass('itop-simple-graph') + .removeClass('graph'); + + $('#tk_graph'+sId).remove(); + $('#graph_'+sId+'_export_as_pdf').remove(); + + }, + // _setOptions is called with a hash of all options that are changing + _setOptions: function() + { + this._superApply(arguments); + }, + // _setOption is called for each individual option that is changing + _setOption: function( key, value ) + { + this._superApply(arguments); + }, + draw: function() + { + this._updateBBox(); + this.auto_scale(); + this.oPaper.clear(); + this._reset + this.oPaper.setViewBox(this.xPan, this.yPan, this.element.width(), this.element.height(), false); + for(var k in this.aNodes) { - // The "Groups" tab is present, refresh it - if (aGroups.length == 0) + this.aNodes[k].aElements = []; + this._draw_node(this.aNodes[k]); + } + for(var k in this.aEdges) + { + this.aEdges[k].aElements = []; + this._draw_edge(this.aEdges[k]); + } + var me = this; + this.oBackground = this.oPaper + .rect(-10000, -10000, 20000, 20000) + .attr({fill: '#fff', opacity: 0, cursor: 'move'}) + .toBack() + .drag(function(dx, dy, x, y, event) { me._on_background_move(dx, dy, x, y, event); }, function(x, y, event) { me._on_background_drag_start(x, y, event); }, function (event) { me._on_background_drag_end(event); }); + this._make_tooltips(); + }, + _draw_node: function(oNode) + { + var iWidth = oNode.width; + var iHeight = 32; + var iFontSize = 10; + var fTotalZoom = this.fZoom * this.fSliderZoom; + var xPos = Math.round(oNode.x * fTotalZoom + this.xOffset); + var yPos = Math.round(oNode.y * fTotalZoom + this.yOffset); + oNode.tx = 0; + oNode.ty = 0; + switch(oNode.shape) + { + case 'disc': + oScaledAttr = {}; + for(k in oNode.disc_attr) + { + value = oNode.disc_attr[k] + switch(k) + { + // Scalable attributes + case 'stroke-width': + value = value * fTotalZoom; + break; + } + oScaledAttr[k] = value; + } + oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*fTotalZoom / 2).attr(oScaledAttr)); + var oText = this.oPaper.text(xPos, yPos, oNode.label); + oNode.text_attr['font-size'] = iFontSize * fTotalZoom; + oText.attr(oNode.text_attr); + //oText.transform('s'+this.fZoom); + oNode.aElements.push(oText); + break; + + case 'group': + oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*fTotalZoom / 2).attr({fill: '#fff', 'stroke-width':0})); + oScaledAttr = {}; + for(k in oNode.disc_attr) + { + value = oNode.disc_attr[k] + switch(k) + { + // Scalable attributes + case 'stroke-width': + value = value * fTotalZoom; + break; + } + oScaledAttr[k] = value; + } + oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*fTotalZoom / 2).attr(oScaledAttr)); + var xIcon = xPos - 18 * fTotalZoom; + var yIcon = yPos - 18 * fTotalZoom; + oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon, yIcon, 16*fTotalZoom, 16*fTotalZoom).attr(oNode.icon_attr)); + oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 18*fTotalZoom, yIcon, 16*fTotalZoom, 16*fTotalZoom).attr(oNode.icon_attr)); + oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 9*fTotalZoom, yIcon + 18*fTotalZoom, 16*fTotalZoom, 16*fTotalZoom).attr(oNode.icon_attr)); + var oText = this.oPaper.text(xPos, yPos +2, oNode.label); + oNode.text_attr['font-size'] = iFontSize * fTotalZoom; + oText.attr(oNode.text_attr); + //oText.transform('s'+this.fZoom); + var oBB = oText.getBBox(); + var dy = iHeight/2*fTotalZoom + oBB.height/2; + oText.remove(); + oText = this.oPaper.text(xPos, yPos +dy +2, oNode.label); + oText.attr(oNode.text_attr); + //oText.transform('s'+this.fZoom); + oNode.aElements.push(oText); + oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9})); + oText.toFront(); + break; + + case 'icon': + if(Raphael.svg) + { + // the colorShift plugin works only in SVG + oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * fTotalZoom/2, yPos - iHeight * fTotalZoom/2, iWidth*fTotalZoom, iHeight*fTotalZoom).colorShift('#fff', 1)); + } + oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * fTotalZoom/2, yPos - iHeight * fTotalZoom/2, iWidth*fTotalZoom, iHeight*fTotalZoom).attr(oNode.icon_attr)); + + var idx = 0; + for(var i in oNode.context_icons) + { + var sgn = 2*(idx % 2) -1; // Suite: -1, 1, -1, 1, -1, 1, -1, etc. + var coef = Math.floor((1+idx)/2) * sgn; // Suite: 0, 1, -1, 2, -2, 3, -3, etc. + var alpha = coef*Math.PI/4 - Math.PI/2; + var x = xPos + Math.cos(alpha) * 1.25*iWidth * fTotalZoom / 2; + var y = yPos + Math.sin(alpha) * 1.25*iWidth * fTotalZoom / 2; + var l = iWidth/3 * fTotalZoom; + oNode.aElements.push(this.oPaper.image(oNode.context_icons[i], x - l/2, y - l/2, l , l).attr(oNode.icon_attr)); + idx++; + } + var oText = this.oPaper.text( xPos, yPos, oNode.label); + oNode.text_attr['font-size'] = iFontSize * fTotalZoom; + oText.attr(oNode.text_attr); + //oText.transform('S'+fTotalZoom); + var oBB = oText.getBBox(); + var dy = iHeight/2*fTotalZoom + oBB.height/2; + oText.remove(); + oText = this.oPaper.text( xPos, yPos + dy, oNode.label); + oText.attr(oNode.text_attr); + //oText.transform('S'+fTotalZoom); + oNode.aElements.push(oText); + oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}).toBack()); + break; + } + if (oNode.source) + { + oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*fTotalZoom / 2).attr({stroke: '#c33', 'stroke-width': 3*fTotalZoom }).toBack()); + } + if (oNode.sink) + { + oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*fTotalZoom / 2).attr({stroke: '#33c', 'stroke-width': 3*fTotalZoom }).toBack()); + } + + var me = this; + for(k in oNode.aElements) + { + var sNodeId = oNode.id; + $(oNode.aElements[k].node).attr({'data-type': oNode.shape, 'data-id': oNode.id} ).attr('class', 'popupMenuTarget'); + oNode.aElements[k].drag( + function(dx, dy, x, y, event) { + clearTimeout($(this.node).data('openTimeoutId')); + me._move(sNodeId, dx, dy, x, y, event); + }, + function(x, y, event) { + me._drag_start(sNodeId, x, y, event); + }, + function (event) { + me._drag_end(sNodeId, event); + } + ); + } + }, + _move: function(sNodeId, dx, dy, x, y, event) + { + var fTotalZoom = this.fZoom * this.fSliderZoom; + var origDx = dx / fTotalZoom; + var origDy = dy / fTotalZoom; + + var oNode = this._find_node(sNodeId); + oNode.x = oNode.xOrig + origDx; + oNode.y = oNode.yOrig + origDy; + + for(k in oNode.aElements) + { + oNode.aElements[k].transform('t'+(oNode.tx + dx)+', '+(oNode.ty + dy)); + + for(j in this.aEdges) { - this.element.closest('[data-role="ibo-tab-container"]').tab_container("GetTabsWidget").disable(2); - $('#impacted_groups').html(''); + var oEdge = this.aEdges[j]; + if ((oEdge.source_node_id == sNodeId) || (oEdge.sink_node_id == sNodeId)) + { + var sPath = this._get_edge_path(oEdge); + oEdge.aElements[0].attr({path: sPath}); + } + } + } + }, + _drag_start: function(sNodeId, x, y, event) + { + var oNode = this._find_node(sNodeId); + oNode.xOrig = oNode.x; + oNode.yOrig = oNode.y; + + }, + _drag_end: function(sNodeId, event) + { + var fTotalZoom = this.fZoom * this.fSliderZoom; + var oNode = this._find_node(sNodeId); + oNode.tx += (oNode.x - oNode.xOrig) * fTotalZoom; + oNode.ty += (oNode.y - oNode.yOrig) * fTotalZoom; + oNode.xOrig = oNode.x; + oNode.yOrig = oNode.y; + this._updateBBox(); + }, + _updateBBox: function() + { + this.options.xmin = 9999; + this.options.xmax = -9999; + this.options.ymin = 9999; + this.options.ymax = -9999; + for(var k in this.aNodes) + { + this.options.xmin = Math.min(this.aNodes[k].x + this.aNodes[k].tx - this.aNodes[k].width/2, this.options.xmin); + this.options.xmax = Math.max(this.aNodes[k].x + this.aNodes[k].tx + this.aNodes[k].width/2, this.options.xmax); + this.options.ymin = Math.min(this.aNodes[k].y + this.aNodes[k].ty - this.aNodes[k].width/2, this.options.ymin); + this.options.ymax = Math.max(this.aNodes[k].y + this.aNodes[k].ty + this.aNodes[k].width/2, this.options.ymax); + } + }, + _get_edge_path: function(oEdge) + { + var fTotalZoom = this.fZoom * this.fSliderZoom; + var oStart = this._find_node(oEdge.source_node_id); + var oEnd = this._find_node(oEdge.sink_node_id); + var iArrowSize = 5; + + if ((oStart == null) || (oEnd == null)) return ''; + + var xStart = Math.round(oStart.x * fTotalZoom + this.xOffset); + var yStart = Math.round(oStart.y * fTotalZoom + this.yOffset); + var xEnd = Math.round(oEnd.x * fTotalZoom + this.xOffset); + var yEnd = Math.round(oEnd.y * fTotalZoom + this.yOffset); + + var sPath = Raphael.format('M{0},{1}L{2},{3}', xStart, yStart, xEnd, yEnd); + var vx = (xEnd - xStart); + var vy = (yEnd - yStart); + var l = Math.sqrt(vx*vx+vy*vy); + vx = vx / l; + vy = vy / l; + var ux = -vy; + var uy = vx; + var lPos = Math.max(l/2, l - 40*fTotalZoom); + var xArrow = xStart + vx * lPos; + var yArrow = yStart + vy * lPos; + sPath += Raphael.format('M{0},{1}l{2},{3}M{4},{5}l{6},{7}', xArrow, yArrow, fTotalZoom * iArrowSize *(-vx + ux), fTotalZoom * iArrowSize *(-vy + uy), xArrow, yArrow, fTotalZoom * iArrowSize *(-vx - ux), fTotalZoom * iArrowSize *(-vy - uy)); + return sPath; + }, + _draw_edge: function(oEdge) + { + var fTotalZoom = this.fZoom * this.fSliderZoom; + var fStrokeSize = Math.max(1, 2 * fTotalZoom); + var sPath = this._get_edge_path(oEdge); + var oAttr = $.extend(oEdge.attr); + oAttr['stroke-linecap'] = 'round'; + oAttr['stroke-width'] = fStrokeSize; + oEdge.aElements.push(this.oPaper.path(sPath).attr(oAttr).toBack()); + }, + _find_node: function(sId) + { + for(var k in this.aNodes) + { + if (this.aNodes[k].id == sId) return this.aNodes[k]; + } + return null; + }, + auto_scale: function() + { + var fMaxZoom = 1.5; + + iMargin = 10; + xmin = this.options.xmin - iMargin; + xmax = this.options.xmax + iMargin; + ymin = this.options.ymin - iMargin; + ymax = this.options.ymax + iMargin; + var xScale = this.element.width() / (xmax - xmin); + var yScale = this.element.height() / (ymax - ymin + this.iTextHeight); + + this.fZoom = Math.min(xScale, yScale, fMaxZoom); + switch(this.options.align) + { + case 'left': + this.xOffset = -xmin * this.fZoom; + break; + + case 'right': + this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom); + break; + + case 'center': + this.xOffset = -xmin * this.fZoom + (this.element.width() - (xmax - xmin) * this.fZoom) / 2; + break; + } + switch(this.options['vertical-align']) + { + case 'top': + this.yOffset = -ymin * this.fZoom; + break; + + case 'bottom': + this.yOffset = this.element.height() - (ymax + this.iTextHeight) * this.fZoom; + break; + + case 'middle': + this.yOffset = -ymin * this.fZoom + (this.element.height() - (ymax - ymin + this.iTextHeight) * this.fZoom) / 2; + break; + } + }, + add_node: function(oNode) + { + oNode.aElements = []; + oNode.tx = 0; + oNode.ty = 0; + this.aNodes.push(oNode); + }, + add_edge: function(oEdge) + { + oEdge.aElements = []; + this.aEdges.push(oEdge); + }, + show_group: function(sGroupId) + { + this._close_all_tooltips(); + // Activate the 3rd tab + this.element.closest('[data-role="ibo-tab-container"]').tab_container("GetTabsWidget").option("active", 2); + // Scroll into view the group + if ($('#'+sGroupId).length > 0) + { + $('#'+sGroupId)[0].scrollIntoView(); + } + }, + _create_toolkit_menu: function() + { + var sPopupMenuId = 'tk_graph'+this.element.attr('id'); + var sHtml = '
'; + var sId = this.element.attr('id'); + sHtml += '
'; + if (this.options.additional_contexts.length > 0) + { + sHtml += '
' + } + sHtml += ''; + sHtml += '
'; + sHtml += '
'; + sHtml += '
'; + sHtml += '
'; + sHtml += '
'; + sHtml += '
'; + sHtml += '
'; + sHtml += '
'; + + + this.element.before(sHtml); + $('#'+sPopupMenuId+'>ul').popupmenu(); + + + var me = this; + $('#'+sPopupMenuId+'_pdf').on('click', function() { me.export_as_pdf(); }); + $('#'+sPopupMenuId+'_attachment').on('click', function() { me.export_as_attachment(); }); + $('#'+sId+'_zoom').slider({ min: 0, max: 5, value: 1, step: 0.25, change: function() { me._on_zoom_change( $(this).slider('value')); } }); + $('#'+sId+'_zoom_plus').on('click', function() { $('#'+sId+'_zoom').slider('value', 0.25 + $('#'+sId+'_zoom').slider('value')); return false; }); + $('#'+sId+'_zoom_minus').on('click', function() { $('#'+sId+'_zoom').slider('value', $('#'+sId+'_zoom').slider('value') - 0.25); return false; }); + $('#'+sId+'_contexts').multiselect({header: true, checkAllText: this.options.labels.check_all, uncheckAllText: this.options.labels.uncheck_all, noneSelectedText: this.options.labels.none_selected, selectedText: this.options.labels.nb_selected, selectedList: 1}); + $('#'+sId+'_refresh_btn').button().on('click', function() { me.reload(); }); + }, + _build_context_menus: function() + { + var sId = this.element.attr('id'); + var me = this; + + $.contextMenu({ + selector: '#'+sId+' .popupMenuTarget', + build: function(trigger, e) { + // this callback is executed every time the menu is to be shown + // its results are destroyed every time the menu is hidden + // e is the original contextmenu event, containing e.pageX and e.pageY (amongst other data) + var sType = trigger.attr('data-type'); + var sNodeId = trigger.attr('data-id'); + var oNode = me._find_node(sNodeId); + clearTimeout(trigger.data('openTimeoutId')); + + /* + var sObjName = trigger.attr('data-class'); + var sIndex = trigger.attr('data-index'); + var originalEvent = e; + var bHasItems = false; + */ + var oResult = {callback: null, items: {}}; + switch(sType) + { + case 'group': + var sGroupIndex = oNode.group_index; + if( $('#relation_group_'+sGroupIndex).length > 0) + { + oResult = { + callback: function(key, options) { + var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value + me.show_group('relation_group_'+sGroupIndex); + }, + items: { 'show': {name: me.options.drill_down.label } } + }; + } + break; + + case 'icon': + var sObjClass = oNode.obj_class; + var sObjKey = oNode.obj_key; + oResult = { + callback: function(key, options) { + var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value + var sURL = me.options.drill_down.url.replace('%1$s', sObjClass).replace('%2$s', sObjKey); + window.location.href = sURL; + }, + items: { 'details': {name: me.options.drill_down.label } } + }; + break; + + default: + oResult = false; // No context menu + } + return oResult; + } + }); + + }, + export_as_pdf: function() + { + this._export_dlg(this.options.labels.export_pdf_title, this.options.export_as_pdf.url, 'download_pdf'); + }, + _export_dlg: function(sTitle, sSubmitUrl, sOperation) + { + var sId = this.element.attr('id'); + var me = this; + var oPositions = {}; + for(k in this.aNodes) + { + oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y }; + } + var sHtmlForm = '
'; + sHtmlForm += ''; + sHtmlForm += ''; + $('#'+sId+'_contexts').multiselect('getChecked').each(function() { + sHtmlForm += ''; + }); + + sHtmlForm += ''; + for(k in this.options.excluded_classes) + { + sHtmlForm += ''; + } + for(var k1 in this.options.sources) + { + for(var k2 in this.options.sources[k1]) + { + sHtmlForm += ''; + } + } + for(var k1 in this.options.excluded) + { + for(var k2 in this.options.excluded[k1]) + { + sHtmlForm += ''; + } + } + if (sOperation == 'attachment') + { + sHtmlForm += ''; + sHtmlForm += ''; + } + sHtmlForm += '
'; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += '
'+this.options.page_format.label+'
'+this.options.page_orientation.label+'
'+this.options.labels.title+'
'+this.options.labels.comments+'
'; + sHtmlForm += ''; + + $('body').append(sHtmlForm); + $('#graph_'+this.element.attr('id')+'_export_dlg input[name="positions"]').val(JSON.stringify(oPositions)); + var me = this; + if (sOperation == 'attachment') + { + $('#GraphExportDlg'+this.element.attr('id')+' form').on('submit', function() { return me._on_export_as_attachment(); }); + } + $('#GraphExportDlg'+this.element.attr('id')).dialog({ + width: 'auto', + modal: true, + title: sTitle, + close: function() { $(this).remove(); }, + buttons: [ + {text: this.options.labels['cancel'], click: function() { $(this).dialog('close');} }, + {text: this.options.labels['export'], click: function() { $('#graph_'+me.element.attr('id')+'_export_dlg').submit(); $(this).dialog('close');} }, + ] + }); + }, + _on_zoom_change: function(sliderValue) + { + if(!this.bInUpdateSliderZoom) + { + var Z0 = this.fSliderZoom; + var X = this.xOffset - this.element.width()/2; + var Y = this.yOffset - this.element.height()/2; + + this.fSliderZoom = Math.pow(2 , (sliderValue - 1)); + + var Z1 = this.fSliderZoom = Math.pow(2 , (sliderValue - 1)); + var dx = X * (1 - Z1/Z0); + var dy = Y * (1 - Z1/Z0); + this.xPan += dx; + this.yPan += dy; + this._close_all_tooltips(); + this.oPaper.setViewBox(this.xPan, this.yPan, this.element.width(), this.element.height(), false); + this.draw(); + } + }, + _on_mousewheel: function(event, delta, deltaX, deltaY) + { + if(this.element.hasClass(this.css_classes.has_focus)) + { + var fStep = 0.25*delta; + var sId = this.element.attr('id'); + $('#'+sId+'_zoom').slider('value', fStep + $('#'+sId+'_zoom').slider('value')); + } + }, + _on_resize: function() + { + this.auto_scale(); + this._close_all_tooltips(); + this.draw(); + }, + _on_tabs_activate: function(ui) + { + if (ui.newPanel[0] === $('#'+this.sTabId)[0]) + { + if (this.bRedrawNeeded) + { + this._updateBBox(); + this.auto_scale(); + this.oPaper.setSize(this.element.width(), this.element.height()); + this._reset_pan_and_zoom(); + this.draw(); + bRedrawNeeded = false; + } + } + }, + load: function(oData) + { + var me = this; + var sId = this.element.attr('id'); + this.aNodes = []; + this.aEdges = []; + for(k in oData.nodes) + { + this.add_node(oData.nodes[k]); + } + for(k in oData.edges) + { + this.add_edge(oData.edges[k]); + } + if (oData.groups) + { + this.refresh_groups(oData.groups); + } + if (oData.lists) + { + if (this.options.excluded_classes.length > 0) { + var newList = {}; + $.each(oData.lists, function (index, listId) { + if (me.options.excluded_classes.indexOf(index) < 0) { + newList[index] = listId; + } + }); + me.refresh_lists(newList); + if ($('#alert_filtered_list').length > 0) { + $('#alert_filtered_list').removeClass('ibo-is-hidden'); + } + } else { + me.refresh_lists(oData.lists); + if ($('#alert_filtered_list').length > 0) { + $('#alert_filtered_list').addClass('ibo-is-hidden'); + } + } + } + if (me.element.is(':visible')) { + me._updateBBox(); + me.auto_scale(); + me._reset_pan_and_zoom(); + me.draw(); + } else { + me.bRedrawNeeded = true; + } + }, + refresh_groups: function(aGroups) + { + if(this.element.parents('.ibo-tab-container').attr('data-status') === 'loaded'){ + if ($('#impacted_groups').length > 0) + { + // The "Groups" tab is present, refresh it + if (aGroups.length == 0) + { + this.element.closest('[data-role="ibo-tab-container"]').tab_container("GetTabsWidget").disable(2); + $('#impacted_groups').html(''); + } + else + { + this.element.closest('[data-role="ibo-tab-container"]').tab_container("GetTabsWidget").enable(2); + $('#impacted_groups').block({message:this.options.labels.loading}); + var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'; + $.post(sUrl, { operation: 'relation_groups', groups: aGroups }, function(data) { + $('#impacted_groups').unblock(); + $('#impacted_groups').html(data); + }); + } + } + } + else{ + setTimeout(this.refresh_groups(aGroups), 800); + } + }, + refresh_lists: function(aLists) + { + if ($('#impacted_objects_lists').length > 0) + { + // The "Lists" tab is present, refresh it + if (aLists.length == 0) + { + $('#impacted_objects_lists').html(''); } else { - this.element.closest('[data-role="ibo-tab-container"]').tab_container("GetTabsWidget").enable(2); - $('#impacted_groups').block({message:this.options.labels.loading}); + $('#impacted_objects_lists').block({message:this.options.labels.loading}); var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'; - $.post(sUrl, { operation: 'relation_groups', groups: aGroups }, function(data) { - $('#impacted_groups').unblock(); - $('#impacted_groups').html(data); + $.post(sUrl, { operation: 'relation_lists', lists: aLists }, function(data) { + $('#impacted_objects_lists').unblock(); + $('#impacted_objects_lists').html(data); }); } } - } - else{ - setTimeout(this.refresh_groups(aGroups), 800); - } - }, - refresh_lists: function(aLists) - { - if ($('#impacted_objects_lists').length > 0) + }, + _reset_pan_and_zoom: function() { - // The "Lists" tab is present, refresh it - if (aLists.length == 0) - { - $('#impacted_objects_lists').html(''); - } - else - { - $('#impacted_objects_lists').block({message:this.options.labels.loading}); - var sUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'; - $.post(sUrl, { operation: 'relation_lists', lists: aLists }, function(data) { - $('#impacted_objects_lists').unblock(); - $('#impacted_objects_lists').html(data); - }); - } - } - }, - _reset_pan_and_zoom: function() - { - this.xPan = 0; - this.yPan = 0; - var sId = this.element.attr('id'); - this.bInUpdateSliderZoom = true; - $('#'+sId+'_zoom').slider('value', 1); - this.fSliderZoom = 1.0; - this.bInUpdateSliderZoom = false; - this.oPaper.setViewBox(this.xPan, this.yPan, this.element.width(), this.element.height(), false); - }, - load_from_url: function(sUrl) - { - this.options.load_from_url = sUrl; - var me = this; - var sId = this.element.attr('id'); - this.options.grouping_threshold = $('#'+sId+'_grouping_threshold').val(); - if (this.options.grouping_threshold < 2) + this.xPan = 0; + this.yPan = 0; + var sId = this.element.attr('id'); + this.bInUpdateSliderZoom = true; + $('#'+sId+'_zoom').slider('value', 1); + this.fSliderZoom = 1.0; + this.bInUpdateSliderZoom = false; + this.oPaper.setViewBox(this.xPan, this.yPan, this.element.width(), this.element.height(), false); + }, + load_from_url: function(sUrl) { - this.options.grouping_threshold = 2; - $('#'+sId+'_grouping_threshold').val(this.options.grouping_threshold); - } - var aContexts = []; - $('#'+sId+'_contexts').multiselect('getChecked').each(function() { aContexts[$(this).val()] = me.options.additional_contexts[$(this).val()].oql; }); + this.options.load_from_url = sUrl; + var me = this; + var sId = this.element.attr('id'); + this.options.grouping_threshold = $('#'+sId+'_grouping_threshold').val(); + if (this.options.grouping_threshold < 2) + { + this.options.grouping_threshold = 2; + $('#'+sId+'_grouping_threshold').val(this.options.grouping_threshold); + } + var aContexts = []; + $('#'+sId+'_contexts').multiselect('getChecked').each(function() { aContexts[$(this).val()] = me.options.additional_contexts[$(this).val()].oql; }); - this._close_all_tooltips(); - this.element.block({message:this.options.labels.loading}); - - $('#'+sId+'_refresh_btn').button('disable'); - $.post(sUrl, {excluded_classes: this.options.excluded_classes, g: this.options.grouping_threshold, sources: this.options.sources, excluded: this.options.excluded, contexts: aContexts, context_key: this.options.context_key }, function(data) { - me.load(data); - me.element.unblock(); - $('#'+sId+'_refresh_btn').button('enable'); - }, 'json'); - }, - export_as_attachment: function() - { - this._export_dlg(this.options.labels.export_as_attachment_title, this.options.export_as_attachment.url, 'attachment'); - }, - _on_export_as_attachment: function() - { - var oParams = {}; - var oPositions = {}; - var jForm = $('#GraphExportDlg'+this.element.attr('id')+' form'); - for(k in this.aNodes) + this._close_all_tooltips(); + this.element.block({message:this.options.labels.loading}); + + $('#'+sId+'_refresh_btn').button('disable'); + $.post(sUrl, {excluded_classes: this.options.excluded_classes, g: this.options.grouping_threshold, sources: this.options.sources, excluded: this.options.excluded, contexts: aContexts, context_key: this.options.context_key }, function(data) { + me.load(data); + me.element.unblock(); + $('#'+sId+'_refresh_btn').button('enable'); + }, 'json'); + }, + export_as_attachment: function() { - oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y }; - } - oParams.positions = JSON.stringify(oPositions); - oParams.sources = this.options.sources; - oParams.excluded_classes = this.options.excluded_classes; - oParams.title = jForm.find(':input[name="title"]').val(); - oParams.comments = jForm.find(':input[name="comments"]').val(); - oParams.include_list = jForm.find(':input[name="include_list"]:checked').length; - oParams.o = jForm.find(':input[name="o"]').val(); - oParams.p = jForm.find(':input[name="p"]').val(); - oParams.obj_class = this.options.export_as_attachment.obj_class; - oParams.obj_key = this.options.export_as_attachment.obj_key; - oParams.contexts = []; - var me = this; - $('#'+this.element.attr('id')+'_contexts').multiselect('getChecked').each(function() { - oParams.contexts[$(this).val()] = me.options.additional_contexts[$(this).val()].oql; - }); - oParams.context_key = this.options.context_key; - var sUrl = jForm.attr('action'); - var sTitle = oParams.title; - var jPanel = $('#attachments').closest('.ui-tabs-panel'); - var jTab = null; - var sTabText = null; - if (jPanel.length > 0) + this._export_dlg(this.options.labels.export_as_attachment_title, this.options.export_as_attachment.url, 'attachment'); + }, + _on_export_as_attachment: function() { - var sTabId = jPanel.attr('id'); - jTab = $('li[aria-controls='+sTabId+']'); - sTabText = jTab.find('span').html(); - jTab.find('span').html(sTabText+' '); - } - $.post(sUrl, oParams, function(data) { - var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.document.php?operation=download_document&class=Attachment&field=contents&id='+data.att_id; - var sIcon = GetAbsoluteUrlModulesRoot()+'itop-attachments/icons/icons8-pdf.svg'; - if (jTab != null) + var oParams = {}; + var oPositions = {}; + var jForm = $('#GraphExportDlg'+this.element.attr('id')+' form'); + for(k in this.aNodes) { - var re = /^([^(]+)\(([0-9]+)\)(.*)$/; - var aParts = re.exec(sTabText); - if (aParts == null) - { - // First attachment - $('#attachments').html(''); - jTab.find('span').html(sTabText +' (1)'); - } - else - { - $('#attachments').append(''); - var iPrevCount = parseInt(aParts[2], 10); - jTab.find('span').html(aParts[1]+'('+(1 + iPrevCount)+')'+aParts[3]); - } + oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y }; } - }, 'json'); - return false; - }, - reload: function() - { - this.load_from_url(this.options.load_from_url); - }, - _make_tooltips: function() - { - var me = this; - let aTooltipGroups = []; - $( ".popupMenuTarget" ).each(function(){ - var sDataId = $(this).attr('data-id'); - var sTooltipContent = me._get_tooltip_content(sDataId); - $(this).attr('data-tooltip-content', sTooltipContent) - .attr('data-tooltip-html-enabled', 'true') - .attr('data-tooltip-interaction-enabled', 'true') - .attr('data-tooltip-append-to', 'body') - .attr('data-tooltip-hide-delay', '1500'); - CombodoTooltip.InitTooltipFromMarkup($(this)); - if(aTooltipGroups.indexOf(sDataId) < 0) { - aTooltipGroups.push(sDataId); + oParams.positions = JSON.stringify(oPositions); + oParams.sources = this.options.sources; + oParams.excluded_classes = this.options.excluded_classes; + oParams.title = jForm.find(':input[name="title"]').val(); + oParams.comments = jForm.find(':input[name="comments"]').val(); + oParams.include_list = jForm.find(':input[name="include_list"]:checked').length; + oParams.o = jForm.find(':input[name="o"]').val(); + oParams.p = jForm.find(':input[name="p"]').val(); + oParams.obj_class = this.options.export_as_attachment.obj_class; + oParams.obj_key = this.options.export_as_attachment.obj_key; + oParams.contexts = []; + var me = this; + $('#'+this.element.attr('id')+'_contexts').multiselect('getChecked').each(function() { + oParams.contexts[$(this).val()] = me.options.additional_contexts[$(this).val()].oql; + }); + oParams.context_key = this.options.context_key; + var sUrl = jForm.attr('action'); + var sTitle = oParams.title; + var jPanel = $('#attachments').closest('.ui-tabs-panel'); + var jTab = null; + var sTabText = null; + if (jPanel.length > 0) + { + var sTabId = jPanel.attr('id'); + jTab = $('li[aria-controls='+sTabId+']'); + sTabText = jTab.find('span').html(); + jTab.find('span').html(sTabText+' '); } - }); - for(let sTooltipGroupKey in aTooltipGroups) { - CombodoTooltip.InitSingletonFromSelector('.itop-simple-graph [data-id="' + aTooltipGroups[sTooltipGroupKey] + '"]'); - } - }, - _get_tooltip_content: function(sNodeId) - { - var oNode = this._find_node(sNodeId); - if (oNode !== null) + $.post(sUrl, oParams, function(data) { + var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.document.php?operation=download_document&class=Attachment&field=contents&id='+data.att_id; + var sIcon = GetAbsoluteUrlModulesRoot()+'itop-attachments/icons/icons8-pdf.svg'; + if (jTab != null) + { + var re = /^([^(]+)\(([0-9]+)\)(.*)$/; + var aParts = re.exec(sTabText); + if (aParts == null) + { + // First attachment + $('#attachments').html(''); + jTab.find('span').html(sTabText +' (1)'); + } + else + { + $('#attachments').append(''); + var iPrevCount = parseInt(aParts[2], 10); + jTab.find('span').html(aParts[1]+'('+(1 + iPrevCount)+')'+aParts[3]); + } + } + }, 'json'); + return false; + }, + reload: function() { - return oNode.tooltip; - } - return '

Node Id:'+sNodeId+'

'; - }, - _close_all_tooltips: function() - { - //obsolete - }, - _on_background_drag_start: function(x, y, event) - { - this.bDragging = true; - this.xDrag = 0; - this.yDrag = 0; - //this._close_all_tooltips(); - }, - _on_background_move: function(dx, dy, x, y, event) - { - if (this.bDragging) + this.load_from_url(this.options.load_from_url); + }, + _make_tooltips: function() { - this.xDrag = dx; - this.yDrag = dy; - this.oPaper.setViewBox(this.xPan - this.xDrag, this.yPan - this.yDrag, this.element.width(), this.element.height(), false); - } - }, - _on_background_drag_end: function(event) - { - if (this.bDragging) + var me = this; + let aTooltipGroups = []; + $( ".popupMenuTarget" ).each(function(){ + var sDataId = $(this).attr('data-id'); + var sTooltipContent = me._get_tooltip_content(sDataId); + $(this).attr('data-tooltip-content', sTooltipContent) + .attr('data-tooltip-html-enabled', 'true') + .attr('data-tooltip-interaction-enabled', 'true') + .attr('data-tooltip-append-to', 'body') + .attr('data-tooltip-hide-delay', '1500'); + CombodoTooltip.InitTooltipFromMarkup($(this)); + if(aTooltipGroups.indexOf(sDataId) < 0) { + aTooltipGroups.push(sDataId); + } + }); + for(let sTooltipGroupKey in aTooltipGroups) { + CombodoTooltip.InitSingletonFromSelector('.itop-simple-graph [data-id="' + aTooltipGroups[sTooltipGroupKey] + '"]'); + } + }, + _get_tooltip_content: function(sNodeId) { - this.xPan -= this.xDrag; - this.yPan -= this.yDrag; + var oNode = this._find_node(sNodeId); + if (oNode !== null) + { + return oNode.tooltip; + } + return '

Node Id:'+sNodeId+'

'; + }, + _close_all_tooltips: function() + { + //obsolete + }, + _on_background_drag_start: function(x, y, event) + { + this.bDragging = true; this.xDrag = 0; this.yDrag = 0; - this.bDragging = false; - } - }, - }); + //this._close_all_tooltips(); + }, + _on_background_move: function(dx, dy, x, y, event) + { + if (this.bDragging) + { + this.xDrag = dx; + this.yDrag = dy; + this.oPaper.setViewBox(this.xPan - this.xDrag, this.yPan - this.yDrag, this.element.width(), this.element.height(), false); + } + }, + _on_background_drag_end: function(event) + { + if (this.bDragging) + { + this.xPan -= this.xDrag; + this.yPan -= this.yDrag; + this.xDrag = 0; + this.yDrag = 0; + this.bDragging = false; + } + }, + }); }); diff --git a/pages/UI.php b/pages/UI.php index af9854a8c..77847b8a7 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -6,6 +6,7 @@ use Combodo\iTop\Application\Helper\Session; use Combodo\iTop\Application\TwigBase\Twig\TwigHelper; +use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Form\Form; @@ -18,6 +19,7 @@ use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; +use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory; /** * Displays a popup welcome message, once per session at maximum @@ -266,21 +268,12 @@ function DisplayMultipleSelectionForm(WebPage $oP, DBSearch $oFilter, string $sN function DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj) { $oP->SetCurrentTab('UI:RelationshipList'); - $oP->add("
"); - $sOldRelation = $sRelation; - if (($sRelation == 'impacts') && ($sDirection == 'up')) - { - $sOldRelation = 'depends on'; - } - $oP->add("
"); - $oP->add("
"); - - /* - * Content is rendered asynchronously via pages/ajax.render.php?operation=relation_lists - */ - - $oP->add("
"); - $oP->add("
"); + $oImpactedObject = UIContentBlockUIBlockFactory::MakeStandard("impacted_objects", ['ibo-is-visible']); + $oP->AddSubBlock($oImpactedObject); + $oImpactedObject->AddSubBlock(AlertUIBlockFactory::MakeForWarning(Dict::S("Relation:impacts/FilteredData"), '', "alert_filtered_list")->SetIsHidden(true)); + $oImpactedObjectList = UIContentBlockUIBlockFactory::MakeStandard("impacted_objects_lists", ['ibo-is-visible']); + $oImpactedObject->AddSubBlock($oImpactedObjectList); + $oImpactedObjectList->AddSubBlock(UIContentBlockUIBlockFactory::MakeStandard("impacted_objects_lists_placeholder", ['ibo-is-visible'])); } function DisplayNavigatorGroupTab($oP)