Find your people. Pick a challenge. Ship something real. The CreatorCon Hackathon is coming to the Community Pavilion for one epic night. Every skill level, every role welcome. Join us on May 5th and learn more here.

HTML structure in Jelly Scripting

HameetK
Tera Contributor

Hi 
I am working on a requirement where i want to show/hide a UI formatter from on a UI macro based on the reference field on the form.

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <style>
        tr:nth-child(even) {
            background-color: #DCDCDC;
        }

        td,
        th {
            padding: 5px;
            border: 1px solid black;
            border-collapse: collapse;
        }

        table {
            margin-left: auto;
            margin-right: auto;
            border: 1px solid black;
            border-collapse: collapse;
        }
    </style>
	<script>

        function saveCostPlanData() {
            var data = {};

            // Get all inputs for carry_over (this gives us all years)
            var carryInputs = document.querySelectorAll("input[id^='carry_over_']");

            carryInputs.forEach(function(input) {
                // Extract year from id → carry_over_FY25 → FY25
                // var year = input.id.replace("carry_over_", "").toLowerCase();
                var year = input.id.replace("carry_over_", "");

                data[year] = {
                    carry_over: input.value || "",
                    supplemental: document.getElementById("supplemental_" + year)?.value || "",
                    release: document.getElementById("release_" + year)?.value || ""
                };
            });

            g_form.setValue('u_cost_plan_changes', JSON.stringify(data));

        }
    </script>

    <g2:evaluate var="jvar_savedData" jelly="true">
        var data = '';
        var gr = new GlideRecord('project_change_request');
        gr.addQuery('sys_id', RP.getParameterValue('sys_id'));
        gr.query();

        if (gr.next()) {
        data = gr.getValue('u_cost_plan_changes');
        }
        data;

    </g2:evaluate>

    <div id='cost_plan_baseline'>
        <div class="section-title" style="width:80%"> <b>Baseline Cost Plan</b></div>
        <table style="width:80%">
            <g2:evaluate>
                var baselineConfig = {
                dmn_demand: 'dmn_demand_baseline',
                pm_project: 'pm_project_baseline'
                };
                var totals1 = {};
                var allResourceTypes1 = {};
                var query = '';
                var className = current.parent.sys_class_name;
                var category = current.category.getDisplayValue();
                query ='^ORbaseline_nameLIKE' + current.parent.getDisplayValue() + ' ' + category;
                var baselineGR=new GlideRecord(baselineConfig[className]);
                baselineGR.addEncodedQuery(' baseline_name=Baseline on Approved State-' + current.parent.getDisplayValue() + query);
                baselineGR.addQuery(className, current.parent.sys_id);
                baselineGR.orderByDesc('sys_created_on');
                baselineGR.setLimit(1);
                baselineGR.query();
                if (baselineGR.next()) {
                var costPlanBaselineGR=new GlideRecord('cost_plan_baseline');
                costPlanBaselineGR.addQuery('parent', baselineGR.getValue('sys_id'));
                costPlanBaselineGR.query();
                while (costPlanBaselineGR.next()) {
                var costPlanCategory=costPlanBaselineGR.resource_type.getDisplayValue();
                allResourceTypes1[costPlanCategory]=true;
                var costBreakdownGR=new GlideRecord('cost_plan_breakdown_baseline');
                costBreakdownGR.addQuery('parent', costPlanBaselineGR.getValue('sys_id'));
                costBreakdownGR.query();
                while (costBreakdownGR.next()) {
                var fiscalPeriod=costBreakdownGR.getDisplayValue('fiscal_period') || '' ;
                var fiscalYear=fiscalPeriod.split(':')[0].trim();
                var cost=parseFloat(costBreakdownGR.getValue('cost_default_currency')) || 0;
                if (!totals1[fiscalYear]) {
                totals1[fiscalYear]={ Total: 0 };
                }
                if (!totals1[fiscalYear][costPlanCategory]) {
                totals1[fiscalYear][costPlanCategory]=0;
                }
                totals1[fiscalYear][costPlanCategory] +=cost;
                totals1[fiscalYear].Total +=cost;
                }
                }
                }

                for (var fyKey1 in totals1) {
                totals1[fyKey1].Total=parseFloat(totals1[fyKey1].Total.toFixed(2));
                for (var rt1 in allResourceTypes1) {
                if (totals1[fyKey1][rt1]===undefined) {
                totals1[fyKey1][rt1]=0;
                }
                totals1[fyKey1][rt1]=parseFloat(totals1[fyKey1][rt1].toFixed(2));
                }
                }

                var orderedTotals1 = {};
                var orderedRT1 = Object.keys(allResourceTypes1).sort();
                var sortedFY1 = Object.keys(totals1).sort(function(a, b) {
                return parseInt(a.replace('FY','')) - parseInt(b.replace('FY',''));
                });

                for (var m in sortedFY1) {
                var fyName1 = sortedFY1[m];
                orderedTotals1[fyName1] = { Total: totals1[fyName1].Total };

                for (var idx1 in orderedRT1) {
                var resTypeName1=orderedRT1[idx1];
                orderedTotals1[fyName1][resTypeName1]=totals1[fyName1][resTypeName1];
                }
                }
             
            </g2:evaluate>
            <j2:forEach items="$[Object.keys(orderedTotals1)]" var="jvar_fy_key2" varStatus="jvar_index1">
                <g2:evaluate jelly="true">
                    var fyData2 = orderedTotals1[jelly.jvar_fy_key2];
                </g2:evaluate>
                <j2:if test="$[jvar_index1 == 0]">
                    <tr>
                        <th>Year</th>
                        <j2:forEach items="$[Object.keys(fyData2)]" var="jvar_key3">
                            <th>$[jvar_key3]</th>
                        </j2:forEach>
                    </tr>
                </j2:if>
                <tr>
                    <td>$[jvar_fy_key2]</td>
                    <j2:forEach items="$[Object.keys(fyData2)]" var="jvar_key4">
                        <g2:evaluate jelly="true">
                            var fyData3 = fyData2[jelly.jvar_key4];
                        </g2:evaluate>
                        <td>$[fyData3]</td>
                    </j2:forEach>
                </tr>
            </j2:forEach>
        </table>
		
		<div class="section-title" style="width:80%"> <b>Draft Cost Plan</b></div>
        <table style="width:80%">

            <g2:evaluate jelly="true">
                var allResourceTypes = {};
                var costPlanMap = {};
                var costPlanGR = new GlideRecord('cost_plan');
                costPlanGR.addQuery('top_task', current.getValue('parent'));
                costPlanGR.query();
                while (costPlanGR.next()) {
                var costPlanId = costPlanGR.getUniqueValue();
                var costPlanType = (costPlanGR.getDisplayValue('resource_type') || '').trim();
                costPlanMap[costPlanId] = costPlanType;
                }
                var totals = {};

                var costBreakdownGR = new GlideRecord('cost_plan_breakdown');
                costBreakdownGR.addQuery('task', current.getValue('parent'));
                costBreakdownGR.addNotNullQuery('cost_plan');
                costBreakdownGR.query();
                while (costBreakdownGR.next()) {
                var parentCostPlan = costBreakdownGR.getValue('cost_plan');
                if (!costPlanMap[parentCostPlan]) continue;
                var mappedResourceType = costPlanMap[parentCostPlan];
                allResourceTypes[mappedResourceType] = true;
                var fp = costBreakdownGR.getDisplayValue('fiscal_period') || '';
                var fy = fp.split(':')[0].trim();
                var cost = parseFloat(costBreakdownGR.getValue('cost_default_currency')) || 0;
                if (!totals[fy]) {
                totals[fy] = { Total: 0 };
                }
                if (!totals[fy][mappedResourceType]) {
                totals[fy][mappedResourceType] = 0;
                }
                totals[fy][mappedResourceType] += cost;
                totals[fy].Total += cost;
                }
                for (var fyKey in totals) {
                totals[fyKey].Total = parseFloat(totals[fyKey].Total.toFixed(2));
                for (var rt in allResourceTypes) {
                if (totals[fyKey][rt] === undefined) {
                totals[fyKey][rt] = 0;
                }
                totals[fyKey][rt] = parseFloat(totals[fyKey][rt].toFixed(2));
                }
                }
                var orderedTotals = {};
                var orderedRT = Object.keys(allResourceTypes).sort();
                var sortedFY = Object.keys(totals).sort(function(a, b) {
                return parseInt(a.replace('FY','')) - parseInt(b.replace('FY',''));
                });

                for (var n in sortedFY) {
                var fyName = sortedFY[n];
                orderedTotals[fyName] = { Total: totals[fyName].Total };

                for (var idx in orderedRT) {
                var resTypeName=orderedRT[idx];
                orderedTotals[fyName][resTypeName]=totals[fyName][resTypeName];
                }
                }
            </g2:evaluate>

            <j2:forEach items="$[Object.keys(orderedTotals)]" var="jvar_fy_key" varStatus="jvar_index">
                <g2:evaluate jelly="true">
                    var fyData = orderedTotals[jelly.jvar_fy_key];
                </g2:evaluate>
                <j2:if test="$[jvar_index == 0]">
                    <tr>
                        <th>Year</th>
                        <j2:forEach items="$[Object.keys(fyData)]" var="jvar_key1">
                            <th>$[jvar_key1]</th>
                        </j2:forEach>
                    </tr>
                </j2:if>
                <tr>
                    <td>$[jvar_fy_key]</td>
                    <j2:forEach items="$[Object.keys(fyData)]" var="jvar_key">
                        <g2:evaluate jelly="true">
                            var fyData1 = fyData[jelly.jvar_key];
                        </g2:evaluate>
                        <td>$[fyData1]</td>
                    </j2:forEach>
                </tr>
            </j2:forEach>
        </table>

        <div class="section-title" style="width:80%"> <b>Cost plan change classification</b></div>
        <table style="width:80%">
            <tr>
                <th>Year</th>
                <th>Baselined Cost plan</th>
                <th>Draft cost plan</th>
                <th>Carry over/Forward</th>
                <th>Supplemental</th>
                <th>Release</th>
                <th>New cost plan</th>
                <th>Change to be Claaified</th>
            </tr>

            <j2:forEach items="$[Object.keys(orderedTotals)]" var="jvar_fy_key1">

                <g2:evaluate jelly="true">
                    var year = jelly.jvar_fy_key1;
                    var fyData1 = orderedTotals[year];
                    var fyData4 = orderedTotals1[year];
                    var data = jelly.jvar_savedData;

                    var data1 = JSON.parse(data);

                    var carry = '';
                    var supp = '';
                    var rel = '';

                    if (data1[year]) {
                    carry = data1[year].carry_over || '';
                    supp = data1[year].supplemental || '';
                    rel = data1[year].release || '';
                    }

                </g2:evaluate>

                <tr>
                    <td>$[jvar_fy_key1]</td>
                    <td>$[fyData4.Total]</td>
                    <td>$[fyData1.Total]</td>

                    <td>
                        <input type="number" id="carry_over_$[jvar_fy_key1]" name="carry_over_$[jvar_fy_key1]" value="$[carry]" />
                    </td>

                    <td>
                        <input type="number" id="supplemental_$[jvar_fy_key1]" name="supplemental_$[jvar_fy_key1]" value="$[supp]" />
                    </td>

                    <td>
                        <input type="number" id="release_$[jvar_fy_key1]" name="release_$[jvar_fy_key1]" value="$[rel]" />
                    </td>

                    <td>Germany</td>
                    <td>Germany</td>
                </tr>

            </j2:forEach>
        </table>
    </div>
</j:jelly>

I have designed the HTML Structure as below :

<div id='cost_plan_baseline'>
    <div class="section-title" style="width:80%"> 
        <b>Baseline Cost Plan</b>
    </div>
    <table style="width:80%">
        <!-- table content -->
    </table>

    <div class="section-title" style="width:80%"> 
        <b>Draft Cost Plan</b>
    </div>
    <table style="width:80%">
        <!-- table content -->
    </table>

    <div class="section-title" style="width:80%"> 
        <b>Cost plan change classification</b>
    </div>
    <table style="width:80%">
        <!-- table content -->
    </table>

</div>  <!-- THIS CLOSES THE cost_plan_baseline DIV -->

However , when it is being rendered by the browser the <div id='cost_plan_baseline> tag is being closed after the first <div> as shown in the screenshot below:

HameetK_0-1776789306230.png

What is the possible solution for this?

@Naveen20 Can you please help?

1 REPLY 1

Naveen20
ServiceNow Employee

Try this possible fix

1. Move all `<g2:evaluate>` computation blocks out of the tables, above the outer div:
<g2:evaluate>
// all baseline + draft calculations here
</g2:evaluate>

<div id="cost_plan_baseline">
<table>
<tbody>
<j2:forEach ...>
<tr>...</tr>
</j2:forEach>
</tbody>
</table>
</div>
2. Wrap rows in an explicit `<tbody>` — gives the parser a valid container.
3. Replace per-row `<g2:evaluate jelly="true">` with inline `$[...]` expressions, or precompute a flat row array above the div.

Show/hide
Client script on the reference field:

function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) return;
var el = document.getElementById('cost_plan_baseline');
if (el) el.style.display = newValue ? 'block' : 'none';
}