link to a class */ function MakeClassHLink($sClass, $sContext) { return "".MetaModel::GetName($sClass)." (".$sClass.")"; } /** * Helper for the global list and the details of a given class */ function DisplaySubclasses($oPage, $sClass, $sContext) { $aChildClasses = MetaModel::EnumChildClasses($sClass); if (count($aChildClasses) != 0) { $oPage->add("\n"); } } /** * Helper for the global list and the details of a given class */ function GetSubclasses($sClass, $sContext) { $sHtml = ''; try{ $aChildClasses = MetaModel::EnumChildClasses($sClass); if (count($aChildClasses) != 0) { $sHtml .= ""; } } catch(Exception $e){ } return $sHtml; } /** * Helper for the lifecycle details of a given class */ function DisplayLifecycle($oPage, $sClass) { if (!MetaModel::HasLifecycle($sClass)) { $oPage->p(Dict::S('UI:Schema:NoLifeCyle')); } else { $aStates = MetaModel::EnumStates($sClass); $aStimuli = MetaModel::EnumStimuli($sClass); $oPage->add("\n"); $oPage->add_ready_script( <<SetOnClickJsCode( <<SetOnClickJsCode( <<AddUiBlock($oOpenAllButton); $oPage->AddUiBlock($oCloseAllButton); $oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:Schema:LifeCycleTransitions'), 3)); $oPage->add("
    \n"); foreach ($aStates as $sStateCode => $aStateDef) { $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); $oPage->add("
  • $sStateLabel ($sStateCode) $sStateDescription\n"); $oPage->add("
      \n"); foreach (MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) { $sStimulusLabel = $aStimuli[$sStimulusCode]->GetLabel(); $sTargetState = $aTransitionDef['target_state']; $sTargetStateLabel = MetaModel::GetStateLabel($sClass, $sTargetState); if (count($aTransitionDef['actions']) > 0) { $aActionsDesc = array(); foreach ($aTransitionDef['actions'] as $actionHandler) { if (is_string($actionHandler)) { $aActionsDesc[] = $actionHandler; } else { $aParamsDesc = array(); foreach ($actionHandler['params'] as $aParamData) { $aParamsDesc[] = $aParamData['type'].':'.$aParamData['value']; } $aActionsDesc[] = $actionHandler['verb'].'('.implode(', ', $aParamsDesc).')'; } } $sActions = " (".implode(', ', $aActionsDesc).")"; } else { $sActions = ""; } $oPage->add("
    • $sStimulusLabel ($sStimulusCode) $sTargetStateLabel ($sTargetState) $sActions
    • \n"); } $oPage->add("
  • \n"); } $oPage->add("
\n"); $oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:Schema:LifeCyleAttributeOptions'), 3)); $oPage->add("
    \n"); foreach ($aStates as $sStateCode => $aStateDef) { $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); $oPage->add("
  • $sStateLabel ($sStateCode) $sStateDescription\n"); if (count($aStates[$sStateCode]['attribute_list']) > 0) { $oPage->add("
      \n"); foreach ($aStates[$sStateCode]['attribute_list'] as $sAttCode => $iOptions) { $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $sAttLabel = $oAttDef->GetLabel(); $aOptions = array(); if ($iOptions & OPT_ATT_HIDDEN) { $aOptions[] = Dict::S('UI:Schema:LifeCycleHiddenAttribute'); } if ($iOptions & OPT_ATT_READONLY) { $aOptions[] = Dict::S('UI:Schema:LifeCycleReadOnlyAttribute'); } if ($iOptions & OPT_ATT_MANDATORY) { $aOptions[] = Dict::S('UI:Schema:LifeCycleMandatoryAttribute'); } if ($iOptions & OPT_ATT_MUSTCHANGE) { $aOptions[] = Dict::S('UI:Schema:LifeCycleAttributeMustChange'); } if ($iOptions & OPT_ATT_MUSTPROMPT) { $aOptions[] = Dict::S('UI:Schema:LifeCycleAttributeMustPrompt'); } if (count($aOptions)) { $sOptions = implode(', ', $aOptions); } else { $sOptions = ""; } $oPage->add("
    • $sAttLabel $sOptions
    • \n"); } $oPage->add("
  • \n"); } else { $oPage->p("".Dict::S('UI:Schema:LifeCycleEmptyList').""); } } $oPage->add("
\n"); $oPage->add_ready_script('$("#LifeCycleList").treeview({collapsed: true,});'); $oPage->add_ready_script('$("#LifeCycleAttrOptList").treeview({collapsed: true,});'); } } /** * Helper for the trigger */ function DisplayTriggers($oPage, $sClass) { $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObject WHERE target_class IN ('$sClassList')")); cmdbAbstractObject::DisplaySet($oPage, $oSet, array('block_id' => 'triggers')); } /** * Display the list of classes from the business model */ function DisplayClassesList($oPage, $oLayout, $sContext) { $sSelectedClass = utils::ReadParam('class', '', false, 'class'); $oLayout->AddSideHtml("
"); $oListSearch = new Select("ibo-datamodel-viewer--class-search"); $oListSearch->SetName('aa'); // Get all the "root" classes for display $aRootClasses = array(); $aClassLabelAndCodeAsJSON = []; $aClassLabelAsJSON = array(); $aClassCodeAsJSON = array(); $oOptionSearch = SelectOptionUIBlockFactory::MakeForSelectOption('', "select option", true); $oListSearch->AddOption($oOptionSearch->SetDisabled(true)); foreach (MetaModel::GetClasses() as $sClassName) { if (MetaModel::IsRootClass($sClassName)) { $aRootClasses[$sClassName] = MetaModel::GetName($sClassName); } elseif (MetaModel::IsStandaloneClass($sClassName)) { $aRootClasses[$sClassName] = MetaModel::GetName($sClassName); } $sLabelClassName = MetaModel::GetName($sClassName); $oOptionSearch = SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, "$sLabelClassName ($sClassName)", $sClassName === $sSelectedClass); $oListSearch->AddOption($oOptionSearch); //Fetch classes names for autocomplete purpose // - Encode as JSON to escape quotes and other characters array_push ($aClassLabelAndCodeAsJSON, ["value"=>$sClassName,"label"=>"$sLabelClassName ($sClassName)"]); array_push ($aClassLabelAsJSON, ["value"=>$sClassName,"label"=>"$sLabelClassName"]); array_push ($aClassCodeAsJSON, ["value"=>$sClassName,"label"=>"$sClassName"]); } usort($aClassLabelAndCodeAsJSON, "Label_sort"); $oLayout->AddSideBlock($oListSearch); $oPage->add_ready_script( << li').show(); e.parents().show(); }); } else{ $('#ibo-datamodel-viewer--classes-list--list').find("li").each(function(){ $(this).show(); }); } }; $('#ibo-datamodel-viewer--class-search').selectize({ sortField: 'text', onChange: function(value){ var preUrl = "?operation=details_class&class="; var sufUrl = "&c[menu]=DataModelMenu"; window.location = preUrl + value + sufUrl; }, onType: DatamodelViewerFilterList, maxOptions: 7, }); DatamodelViewerFilterList('$sSelectedClass'); JS ); $oLayout->AddSideHtml("
    \n"); $oPage->add_ready_script( << $sDisplayName) { if (MetaModel::IsRootClass($sClassName)) { $oLayout->AddSideHtml("
  • ".MakeClassHLink($sClassName, $sContext)."\n"); $oLayout->AddSideHtml(GetSubclasses($sClassName, $sContext)); $oLayout->AddSideHtml("
  • \n"); } elseif (MetaModel::IsStandaloneClass($sClassName)) { $oLayout->AddSideHtml("
  • ".MakeClassHLink($sClassName, $sContext)."
  • \n"); } } $oLayout->AddSideHtml("
\n"); $oPage->add_ready_script('$("#ibo-datamodel-viewer--classes-list--list").treeview();'); } function Label_sort($building_a, $building_b) { return strnatcmp ($building_a["label"], $building_b["label"]); } /** * Helper for the list of classes related to the given class in a graphical way */ function DisplayRelatedClassesGraph($oPage, $sClass) { try { $bOnTheLeft = true; $bSkipLinkingClasses = false; // 1) Fetching referencing classes data // $aData = array(); $aOrigins = array('_' => true); $aRefs = MetaModel::EnumReferencingClasses($sClass, $bSkipLinkingClasses); $sSelfReference = "false"; if (count($aRefs) != 0) { foreach ($aRefs as $sRemoteClass => $aRemoteKeys) { foreach ($aRemoteKeys as $sExtKeyAttCode => $oExtKeyAttDef) { if ($sRemoteClass != $sClass) { // ref_prefix to avoid collision between attributes labels that refer to this class and local attributes label that references other classes $aAttribute = array('label' => 'ref_'.$sExtKeyAttCode); // Test if a distant attribut exists and if it uses a link class if (!($oExtKeyAttDef->GetMirrorLinkAttribute() == null ? false : $oExtKeyAttDef->GetMirrorLinkAttribute() instanceof AttributeLinkedSetIndirect)) { $aAttribute['related'] = $sRemoteClass; $aAttribute['related_icon'] = MetaModel::GetClassIcon($aAttribute['related'], false); $aAttribute['related_position'] = $bOnTheLeft ? -1 : 1; $aAttribute['relation_type'] = 0; $bOnTheLeft = !$bOnTheLeft; // Toggle the side $sOrigin = MetaModel::GetAttributeOrigin($sRemoteClass, $sExtKeyAttCode); $aAttribute['origin'] = $sOrigin; $aOrigins[$sOrigin] = true; $aData[$sExtKeyAttCode.$sRemoteClass] = $aAttribute; } } } } } // 2) Fetching referenced classes data // $aDataRef = array( array( 'label' => $sClass, 'icon' => MetaModel::GetClassIcon($sClass, false), 'origin_index' => 0, 'alphabetical_index' => 0, 'origin' => '_', ), ); $bOnTheLeft = true; $aOriginsRef = array('_' => true); foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { $aAttribute = array('label' => $sAttCode); if ($oAttDef->IsLinkSet()) { if ($oAttDef->IsIndirect()) { $sRemoteAttDef = $oAttDef->GetExtKeyToRemote(); $aAttribute['related'] = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sRemoteAttDef)->GetTargetClass(); $aAttribute['related_icon'] = MetaModel::GetClassIcon($aAttribute['related'], false); $aAttribute['related_position'] = $bOnTheLeft ? 1 : -1; $aAttribute['relation_type'] = 0; // $aAttribute['tooltip_data']['class'] = $oAttDef->GetLinkedClass(); $aAttribute['tooltip_data']['to_remote'] = $sRemoteAttDef; $aAttribute['tooltip_data']['to_me'] = $oAttDef->GetExtKeyToMe(); $bOnTheLeft = !$bOnTheLeft; // Toggle the side } else { $aAttribute['related'] = $oAttDef->GetLinkedClass(); $aAttribute['related_icon'] = MetaModel::GetClassIcon($aAttribute['related'], false); $aAttribute['related_position'] = $bOnTheLeft ? 1 : -1; $aAttribute['relation_type'] = 1; $bOnTheLeft = !$bOnTheLeft; // Toggle the side } } else { if ($oAttDef->IsHierarchicalKey()) { $aAttribute['related'] = $sClass; $aAttribute['related_icon'] = MetaModel::GetClassIcon($aAttribute['related'], false); $aAttribute['related_position'] = $bOnTheLeft ? 1 : -1; $aAttribute['relation_type'] = 2; $bOnTheLeft = !$bOnTheLeft; // Toggle the side $sSelfReference = "true"; } else { if ($oAttDef->IsExternalKey()) { $aAttribute['related'] = $oAttDef->GetTargetClass(); $aAttribute['related_icon'] = MetaModel::GetClassIcon($aAttribute['related'], false); $aAttribute['related_position'] = $bOnTheLeft ? 1 : -1; $aAttribute['relation_type'] = 3; $bOnTheLeft = !$bOnTheLeft; // Toggle the side } } } if ($oAttDef->IsLinkSet() || $oAttDef->IsHierarchicalKey() || $oAttDef->IsExternalKey()) { $sOrigin = MetaModel::GetAttributeOrigin($sClass, $sAttCode); $aAttribute['origin'] = $sOrigin; $aOriginsRef[$sOrigin] = true; $aDataRef[$sAttCode] = $aAttribute; } } //sort referencing data $aOrigins = array_keys($aOrigins); $idx = 0; $bOnTheLeft = true; foreach ($aData as $sAttCode => $aAttribute) { $is_also_referenced = false; foreach ($aDataRef as $sAttCodeRef => $aAttributeRef) { if (!empty($aDataRef[$sAttCodeRef]['related']) && ($aData[$sAttCode]['related'] == $aDataRef[$sAttCodeRef]['related'])) { $is_also_referenced = true; } } if (!$is_also_referenced) { $aData[$sAttCode]['related_position'] = ($bOnTheLeft) ? -1 : 1; $bOnTheLeft = !$bOnTheLeft; $aData[$sAttCode]['origin_index'] = ($aData[$sAttCode]['related_position'] == -1) ? ++$idx : $idx; } else { unset($aData[$sAttCode]); } } ksort($aData); $idx = 0; $aFinalDataReferencing = array(); foreach ($aData as $sAttCode => $aAttribute) { $aData[$sAttCode]['alphabetical_index'] = $aAttribute['related_position'] == 1 ? ++$idx : $idx; $aFinalDataReferencing[] = $aData[$sAttCode]; } $sDataReferencing = json_encode($aFinalDataReferencing); $sOriginsReferencing = json_encode(array_keys($aOrigins)); //sort referenced data $idx = 1; foreach ($aDataRef as $sAttCode => $aAttribute) { $aDataRef[$sAttCode]['origin_index'] = $idx++; } $idx = 1; $aFinalData = array(); foreach ($aDataRef as $sAttCode => $aAttribute) { $aDataRef[$sAttCode]['alphabetical_index'] = $idx++; $aFinalData[] = $aDataRef[$sAttCode]; } $sData = json_encode($aFinalData); // 3) Processing data and building graph // // - Add graphs dependencies WebResourcesHelper::EnableC3JSToWebPage($oPage); // - Add markup $oPage->add( << EOF ); // - Add scripts $oPage->add_ready_script( << 1) { schema.append("path") .attr("d", "M"+(margins.left + relatedCellWidth + gap + cellWidth*0.75)+" "+ (margins.top + cellHeight + cellHeight*(datareflen+1.5)) +" l 0 "+ cellHeight*-datareflen+"") .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2); } if(dataref.length > 0) { schema.append("path") .attr("d", "M"+(margins.left + relatedCellWidth + gap + cellWidth*0.25)+" "+ (margins.top + cellHeight + cellHeight*(datareflen+1.5)) +" l 0 "+ cellHeight*-datareflen+"") .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2); } //loop + arrow to show that a class has a hierarchical attribute if(isSelfReferencing == true) { schema.append("path") .attr("d", "M"+(margins.left + relatedCellWidth + gap + cellWidth/1.9)+" "+ (margins.top + cellHeight*(datareflen+1.5))+" a 20 20 0 1 0 20 0 m-10 0l-5 3 m5 -3 l-5 -3") .attr("id", "selfreferencing") .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2) .attr("transform", "rotate(95, "+ (margins.left + relatedCellWidth + gap + cellWidth/1.9) + ", " + ((margins.top + cellHeight*(datareflen+1.5))+10)+")"); } // 2) Classes linked to horns (classes referencing us) // var fieldref = schema.selectAll("g") .data(dataref, function(d) { return d.label + d.related } ) .enter().append("g") .attr("transform", function(d, i) { return "translate(" + (margins.left + relatedCellWidth + gap + cellWidth/2) + "," + (margins.top + d.origin_index*cellHeight*2) + ")"; }); fieldref.filter(function(d) { return (d.related != null); }).append("a") .attr("xlink:href",function(d){ return refClassLinkpre + d.related + refClassLinksuf}) .append("rect") .attr("x", -relatedCellWidth/2) .attr("width", relatedCellWidth) .attr("height", cellHeight) .attr("fill", "#fff") .attr("stroke", "#000") .attr("stroke-width", 1) .attr("transform", function(d, i) { return "translate("+ d.related_position*(relatedCellWidth/2+cellWidth/2+gap) +", 0)"; }); fieldref.filter(function(d) { return (d.related != null); }).append("a") .attr("xlink:href",function(d){ return refClassLinkpre + d.related + refClassLinksuf }) .append("text") .attr("x", 0) .attr("y", cellHeight / 2) .attr("dy", ".35em") .text(function(d) { return d.related ? d.related : ''; }) .attr("transform", function(d, i) { return "translate("+ (d.related_position*(relatedCellWidth/2+cellWidth/2+gap)) +", 0)"; }); fieldref.filter(function(d) { return (d.related != null); }).append("path") .attr("d", "M"+(cellWidth/2 - cellWidth*0.25)+" "+cellHeight/2+" h"+(gap-2 + cellWidth*0.25)) .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2) .attr("transform", function(d, i) { return (d.related_position < 0) ? "rotate(180, 0, "+(cellHeight/2)+")" : ""}); fieldref.filter(function(d) { return (d.related != null); }).append("path") .attr("d", "M"+cellWidth/1.9*-1+" "+cellHeight/2+" m-10 0l-5 3 m5 -3 l-5 -3") .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2) .attr("transform", function(d, i) { return (d.related_position < 0) ? "rotate(360, 0, "+(cellHeight/2)+")" : "rotate(180, 0, "+(cellHeight/2)+")"}); fieldref.filter(function(d) { return (d.related != null); }).append("svg:image") .attr("x", -relatedCellWidth/2) .attr("width", cellHeight) .attr("height", cellHeight) .attr("xlink:href", function(d, i) { return d.related_icon }) .attr("transform", function(d, i) { return "translate("+ (d.related_position*(relatedCellWidth/2+cellWidth/2+gap) - 12)+", -" + cellHeight/2+" )"; }); // 3) Main class rectangle and attributes rectangles // var field = schema.selectAll("g") .data(data, function(d) { return d.label } ) .enter().append("g") .attr("transform", function(d, i) { return "translate(" + (margins.left + relatedCellWidth + gap + cellWidth/2) + "," + (margins.top + (datareflen+1.5)*cellHeight + d.origin_index*cellHeight) + ")"; }); field.append("rect") .attr("x", -cellWidth/2) .attr("width", cellWidth) .attr("class", function(d, i){return (d.relation_type == 2 ? "selfattr" : "extattr");}) .attr("height", cellHeight) .attr("fill", "#fff") .attr("stroke", "#000") .attr("stroke-width", 1); field.append("text") .attr("x", 0) .attr("y", cellHeight / 2) .attr("dy", ".35em") .text(function(d) { return d.label; }); field.append("text") .attr("x", function(d){ return 7*d.label.length/2 }) .attr("y", cellHeight / 2) .attr("dy", ".35em") .attr("class", function(d, i){return (d.relation_type == 2 ? "selfattrtxt" : "");}) .text(function(d) { return ((d.relation_type == 2) ? '\uf01e' : '' ); }); // 4) Classes that our main class is refering to // field.filter(function(d) { return (d.related != null) && (d.relation_type != 2); }).append("a") .attr("xlink:href",function(d){ return refClassLinkpre + d.related + refClassLinksuf}) .append("rect") .attr("x", -relatedCellWidth/2) .attr("width", relatedCellWidth) .attr("height", cellHeight) .attr("fill", "#fff") .attr("stroke", "#000") .attr("stroke-width", 1) .attr("transform", function(d, i) { return "translate("+ d.related_position*(relatedCellWidth/2+cellWidth/2+gap) +", 0)"; }); field.filter(function(d) { return (d.related != null) && (d.relation_type != 2); }).append("a") .attr("xlink:href",function(d){ return refClassLinkpre + d.related + refClassLinksuf}) .append("text") .attr("x", 0) .attr("y", cellHeight / 2) .attr("dy", ".35em") .text(function(d) { return d.related ? d.related : ''; }) .attr("transform", function(d, i) { return "translate("+ (d.related_position*(relatedCellWidth/2+cellWidth/2+gap)) +", 0)"; }); field.filter(function(d) { return (d.related != null) && (d.relation_type != 2); }).append("path") .attr("d", "M"+cellWidth/2+" "+cellHeight/2+" h"+(gap-2)) .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2) .attr("transform", function(d, i) { return (d.related_position < 0) ? "rotate(180, 0, "+(cellHeight/2)+")" : ""}); field.filter(function(d) { return (d.related != null) && (d.relation_type == 3 || d.relation_type == 0); }).append("path") .attr("d","M"+ (gap - 2 + cellWidth/2) +" "+cellHeight/2+" m-10 0l-5 3 m5 -3 l-5 -3") .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2) .attr("transform", function(d, i) { return (d.related_position < 0) ? "rotate(180, 0, "+(cellHeight/2)+")" : ""}); field.filter(function(d) { return (d.related != null) && (d.relation_type == 1 || d.relation_type == 0); }).append("path") .attr("d", "M"+cellWidth/1.9*-1+" "+cellHeight/2+" m-10 0l-5 3 m5 -3 l-5 -3") .attr("fill", "transparent") .attr("stroke", "black") .attr("stroke-linecap", "round") .attr("stroke-width", 2) .attr("transform", function(d, i) { return (d.related_position < 0) ? "rotate(360, 0, "+(cellHeight/2)+")" : "rotate(180, 0, "+(cellHeight/2)+")"}); field.filter(function(d) { return (d.related != null) && (d.relation_type == 0); }).append("circle") .attr("r", 5) .attr("cy", cellHeight/2) .on('mouseover',function(d){ divD3.transition() .duration(200) .style("opacity","1"); divD3.style("left", (d3.event.pageX - 7*d['tooltip_data']['class'].length/2) + "px"); divD3.style("top", (d3.event.pageY - 65) + "px"); divD3.html( '
' + d['tooltip_data']['class'] + '
' + ( (d.related_position < 0) ? d['tooltip_data']['to_remote'] : d['tooltip_data']['to_me'] ) + '
' + ( (d.related_position < 0) ? d['tooltip_data']['to_me'] : d['tooltip_data']['to_remote'] ) + '
'); }) .on('mouseout',function(d){ divD3.transition() .duration(500) .style("opacity","0"); }) .attr("transform", function(d, i) { return "translate("+ d.related_position*(cellWidth+gap+2)/2 +", 0)"; }); field.filter(function(d) { return (d.related != null) && (d.relation_type != 2); }).append("svg:image") .attr("x", -relatedCellWidth/2) .attr("width", cellHeight) .attr("height", cellHeight) .attr("xlink:href", function(d, i) { return d.related_icon }) .attr("transform", function(d, i) { return "translate("+ (d.related_position*(relatedCellWidth/2+cellWidth/2+gap) - 12)+", -" + cellHeight/2+" )"; }); field.append("rect") .attr("x", -cellWidth/2 - 5) .attr("width", 5) .attr("height", cellHeight) .attr("fill", function(d) { return aColors(aOrigins.indexOf(d.origin)); } ) .attr("stroke-width", 0) .attr("class","liseret"); field.filter(function(d) { return (d.icon != null); }).append("svg:image") .attr("x", -cellWidth/2) .attr("width", 36) .attr("height", 36) .attr("xlink:href", function(d, i) { return d.icon }) .attr("transform", "translate(-12, -24)"); // When the schema is visible for the first time, initialize SVG viewbox based on content height/width let oSvgElement = document.getElementsByClassName("dataModelSchema")[0]; if(window.IntersectionObserver) { const oDatamodelSchemaIntersectObs = new IntersectionObserver(function(aEntries, oDatamodelSchemaIntersectObs){ aEntries.forEach(oEntry => { let bIsVisible = oEntry.isIntersecting; if(bIsVisible) { let oSvgBB = oSvgElement.getBBox(); let aSvgViewbox = [oSvgBB.x, oSvgBB.y , oSvgBB.width, oSvgBB.height]; oSvgElement.setAttribute("viewBox", aSvgViewbox.join(" ")); oDatamodelSchemaIntersectObs.unobserve(oSvgElement); } }); }, { root: $('#dataModelGraph')[0], threshold: [1] // Must be completely visible }); oDatamodelSchemaIntersectObs.observe(oSvgElement); } JS ); } catch (Exception $e) { $oPage->p(''.Dict::Format('UI:RunQuery:Error', $e->getMessage()).''); } } /** * Display the details of a given class of objects * * @param \iTopWebPage $oPage * @param string $sClass * @param string $sContext * * @throws \CoreException */ function DisplayClassDetails($oPage, $sClass, $sContext) { $aParentClasses = array(); foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) { $aParentClasses[] = MakeClassHLink($sParentClass, $sContext); } if (count($aParentClasses) > 0) { $sParents = implode(' ', $aParentClasses).' '.MetaModel::GetName($sClass).'('.$sClass.')'; } else { $sParents = ''; } $sClassHierarchy = ("[".Dict::S('UI:Schema:AllClasses')."] $sParents"); $oPanel = PanelUIBlockFactory::MakeForClass($sClass, MetaModel::GetName($sClass).' ('.$sClass.')') ->SetIcon(MetaModel::GetClassIcon($sClass, false)); $sClassDescritpion = MetaModel::GetClassDescription($sClass); $oEnhancedPanelSubtitle = $oPanel->GetSubTitleBlock(); $sEnhancedPanelSubtitle = $sClassHierarchy.($sClassDescritpion == "" ? "" : ' - '.$sClassDescritpion); if (MetaModel::IsAbstract($sClass)) { $sEnhancedPanelSubtitle .= ' - '; } $oEnhancedPanelSubtitle->AddHtml($sEnhancedPanelSubtitle); $oPage->AddUiBlock($oPanel); $oPage->AddTabContainer('details', '', $oPanel); $oPage->SetCurrentTabContainer('details'); // List the attributes of the object $aForwardChangeTracking = MetaModel::GetTrackForwardExternalKeys($sClass); $aDetails = array(); $aOrigins = array(); foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { if ($oAttDef->IsExternalKey()) { $sValue = Dict::Format('UI:Schema:ExternalKey_To', MakeClassHLink($oAttDef->GetTargetClass(), $sContext)); if (array_key_exists($sAttCode, $aForwardChangeTracking)) { $oLinkSet = $aForwardChangeTracking[$sAttCode]; $sRemoteClass = $oLinkSet->GetHostClass(); $sValue = $sValue."*"; } } elseif ($oAttDef->IsLinkSet()) { $sValue = MakeClassHLink($oAttDef->GetLinkedClass(), $sContext); } else { $sValue = $oAttDef->GetDescription(); } $sType = get_class($oAttDef); $sTypeDict = $oAttDef->GetType(); $sTypeDesc = $oAttDef->GetTypeDesc(); $sOrigin = MetaModel::GetAttributeOrigin($sClass, $sAttCode); $aOrigins[$sOrigin] = true; $sAllowedValues = ""; $sMoreInfo = ""; $sDefaultNullValue = ''; if (call_user_func(array(get_class($oAttDef), 'IsBasedOnDBColumns'))) { $aMoreInfo = array(); if ($oAttDef->IsNullAllowed()) { $aMoreInfo[] = Dict::S('UI:Schema:NullAllowed'); $sDefaultNullValue = (!is_null($oAttDef->GetNullValue()) ? $oAttDef->GetNullValue() : null); if (!is_null($sDefaultNullValue) && !is_string($sDefaultNullValue)) { $sDefaultNullValue = json_encode($sDefaultNullValue); } $sDefaultNullValue = (!is_null($sDefaultNullValue) ? Dict::Format('UI:Schema:DefaultNullValue', $sDefaultNullValue) : ''); } else { $aMoreInfo[] = Dict::S('UI:Schema:NullNotAllowed'); } if ($oAttDef->GetDefaultValue()) { $sDefaultValue = $oAttDef->GetDefaultValue(); if (!is_string($sDefaultValue)) { $sDefaultValue = json_encode($sDefaultValue); } $aMoreInfo[] = Dict::Format("UI:Schema:Default_Description", $sDefaultValue); } $sMoreInfo .= implode(', ', $aMoreInfo); } $sAttrCode = $oAttDef->GetCode(); $sIsEnumValues = 'false'; $sAllowedValuesEscpd = '""'; if ($oAttDef instanceof AttributeEnum) { // Display localized values for the enum (which depend on the localization provided by the class) $aLocalizedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array()); $aDescription = array(); foreach ($aLocalizedValues as $val => $sDisplay) { $aDescription[] = $sDisplay." (".$val.")"; } $sAllowedValues = implode(', ', $aDescription); $sIsEnumValues = 'true'; } elseif (is_object($oAllowedValuesDef = $oAttDef->GetValuesDef())) { $sAllowedValues = str_replace("Filter: ", "", $oAllowedValuesDef->GetValuesDescription()); $sAllowedValuesEscpd = utils::HtmlEntities($sAllowedValues); $sFilterURL = urlencode($sAllowedValues); $sAllowedValues = ' '.Dict::S('UI:Schema:Attribute/Filter').""; } else { $sAllowedValues = ''; } $sAttrValueEscpd = utils::HtmlEntities($sValue); $sAttrTypeDescEscpd = utils::HtmlEntities($sTypeDesc); $sAttrOriginEscpd = utils::HtmlEntities($sOrigin); $sDefaultNullValueEscpd = utils::HtmlEntities($sDefaultNullValue); $aDetails[] = array( 'code' => ''.$oAttDef->GetLabel().' ('.$oAttDef->GetCode().')', 'type' => ''.$sTypeDict.' ('.$sType.')', 'origincolor' => '
', 'origin' => "$sOrigin", 'values' => $sAllowedValues, 'moreinfo' => ''.$sMoreInfo.'', ); } $oPage->SetCurrentTab('UI:Schema:Attributes'); $aConfig = array( 'origincolor' => array('label' => "", 'description' => ""), 'code' => array('label' => Dict::S('UI:Schema:AttributeCode'), 'description' => Dict::S('UI:Schema:AttributeCode+')), 'type' => array('label' => Dict::S('UI:Schema:Type'), 'description' => Dict::S('UI:Schema:Type+')), 'values' => array('label' => Dict::S('UI:Schema:AllowedValues'), 'description' => Dict::S('UI:Schema:AllowedValues+')), 'moreinfo' => array('label' => Dict::S('UI:Schema:MoreInfo'), 'description' => Dict::S('UI:Schema:MoreInfo+')), 'origin' => array('label' => Dict::S('UI:Schema:Origin'), 'description' => Dict::S('UI:Schema:Origin+')), ); $oTablePanel = PanelUIBlockFactory::MakeForClass($sClass, ''); $oTablePanel->AddCSSClass('ibo-datatable-panel'); $oAttributesTable = DataTableUIBlockFactory::MakeForStaticData('', $aConfig, $aDetails, 'ibo-datamodel-viewer--attributes-table', [], "", array('pageLength' => -1)); $oTablePanel->AddSubBlock($oAttributesTable); $oPage->AddUiBlock($oTablePanel); $sOrigins = json_encode(array_keys($aOrigins)); //color calculation in order to keep 1 color for 1 extended class. Colors are interpolated and will be used for // graph scheme color too $oPage->add_ready_script( <<< EOF var aOrigins = $sOrigins; var aColors = d3.scale.linear().domain([1,aOrigins.length]) .interpolate(d3.interpolateHcl) .range([d3.rgb("#007AFF"), d3.rgb('#FFF500')]); $.each(aOrigins,function(idx, origin){ $('.originColor'+origin).css('background-color',aColors(aOrigins.indexOf(origin))); }); Array.prototype.forEach.call($(".listResults").find('td:nth-child(1),th:nth-child(1)'), function(e){ $(e).removeClass("header").addClass("ibo-datamodel-viewer--origin-cell"); }); EOF ); $oPage->SetCurrentTab('UI:Schema:RelatedClasses'); DisplayRelatedClassesGraph($oPage, $sClass); if (MetaModel::HasChildrenClasses($sClass)) { $oPage->SetCurrentTab('UI:Schema:ChildClasses'); $oPage->add("
    "); $oPage->add("
  • ".$sClass."\n"); DisplaySubclasses($oPage, $sClass, $sContext); $oPage->add("
  • \n"); $oPage->add("
\n"); $oPage->add_ready_script('$("#ClassHierarchy").treeview({collapsed: false,});'); } $oPage->SetCurrentTab('UI:Schema:LifeCycle'); DisplayLifecycle($oPage, $sClass); $oPage->SetCurrentTab('UI:Schema:Triggers'); DisplayTriggers($oPage, $sClass); $oPage->SetCurrentTab(); $oPage->SetCurrentTabContainer(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MAIN BLOCK // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Display the menu on the left $oAppContext = new ApplicationContext(); $sContext = $oAppContext->GetForLink(); if (!empty($sContext)) { $sContext = '&'.$sContext; } $operation = utils::ReadParam('operation', ''); $oLayout = new PageContentWithSideContent(); $oLayout->AddCSSClass('ibo-datamodel-viewer--side-pane'); $oPage = new iTopWebPage(Dict::S('UI:Schema:Title')); $oPage->SetContentLayout($oLayout); $oPage->no_cache(); $oPage->SetBreadCrumbEntry('ui-tool-datamodel', Dict::S('Menu:DataModelMenu'), Dict::S('Menu:DataModelMenu+'), '', 'fas fa-book', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); $oTitle = TitleUIBlockFactory::MakeForPage(Dict::S('UI:Schema:Title')); $oPage->AddUiBlock($oTitle); $oLayout->AddSideHtml("
"); DisplayClassesList($oPage, $oLayout, $sContext); $oLayout->AddSideHtml("
"); $oPage->add("
"); $oPage->add("
"); switch ($operation) { case 'details_class': $sClass = utils::ReadParam('class', '', false, 'class'); //if we want to see class details & class is given then display it, otherwise act default (just show the class list) if ($sClass != '') { $oPage->set_title(Dict::Format('UI:Schema:TitleForClass', $sClass)); DisplayClassDetails($oPage, $sClass, $sContext); break; } default: } $oPage->add("
"); $oPage->add("
"); $oPage->output();