This is just another attempt to unpack Siebel Audit Trail CLOB value through PL/SQL. This time it is with a "classic" select statement.

It is definitely not the most performant solution but useful if you want to quickly look through a handful of S_AUDIT_ITEM records without a need to open Siebel views or install SQL packages.

Might give you an idea of how to leverage a recursive SQL for parsing purposes.

with user_select as (
    -- free form S_AUDIT_ITEM select should include at least ROW_ID, AUDIT_LOG, BUSCOMP_NAME, TBL_NAME columns
    select *
    from siebel.S_AUDIT_ITEM
    where AUDIT_LOG is not null and rownum <= 5
    order by row_id desc
),
-- split audit_log value into tokens
tokens (ROW_ID, LVL, AUDIT_LOG, BUSCOMP_NAME, TBL_NAME, REMAIN, TOKEN_LEN, TOKEN, NEW_POS) as (
    -- maybe it is worth fetching first token here?
    select ROW_ID, 0 LVL, AUDIT_LOG, BUSCOMP_NAME, TBL_NAME,
        AUDIT_LOG REMAIN,
        0 TOKEN_LEN,
        null TOKEN,
        1 NEW_POS
    from user_select
    union all
    select i.ROW_ID, p.LVL + 1 LVL, i.AUDIT_LOG, i.BUSCOMP_NAME, i.TBL_NAME,
        SUBSTR(p.AUDIT_LOG, p.NEW_POS) REMAIN,  -- remaining AUDIT_LOG value after cutting processed tokens (for debugging)
        TO_NUMBER(REGEXP_SUBSTR(i.AUDIT_LOG, '(\d*)\*', p.NEW_POS, 1, '', 1)) TOKEN_LEN, -- length of the following token (for debugging)
         -- token lenght comes before * and then a comes a token of that lenght
        REGEXP_SUBSTR(p.AUDIT_LOG, '\*(.{' || REGEXP_SUBSTR(i.AUDIT_LOG, '(\d*)\*', p.NEW_POS, 1, '', 1) || '})', p.NEW_POS, 1, '', 1) TOKEN,
        -- position at which starts next token length (current position + token lenght + lenght of token lenght + 1(* sign))
        p.NEW_POS + TO_NUMBER(REGEXP_SUBSTR(i.AUDIT_LOG, '(\d*)\*', p.NEW_POS, 1, '', 1)) + length(REGEXP_SUBSTR(i.AUDIT_LOG, '(\d*)\*', p.NEW_POS, 1, '', 1)) + 1 NEW_POS
    from user_select i
        join tokens p on p.ROW_ID = i.ROW_ID and p.NEW_POS < length(i.AUDIT_LOG) -- recursive join until we reach the end of AUDIT_LOG value
),
-- group tokens into C,N,O,J,L,K arrays and number rows
blocks (ROW_ID, LVL, AUDIT_LOG, BUSCOMP_NAME, TBL_NAME, TOKEN, TYPE, ROWCNT, NUM) as (
    select ROW_ID, LVL, AUDIT_LOG, BUSCOMP_NAME, TBL_NAME, TOKEN, 
        TO_CHAR(SUBSTR(TOKEN, 1, 1)) TYPE, -- first token indicates the meaning of following tokens (C, N, O, J, L, K)
        TO_NUMBER(SUBSTR(TOKEN, 2)) ROWCNT, -- and a count of following tokens
        0 NUM -- mark first(technical) token to filter it out later
    from tokens
    where lvl = 1
    union all
    select t.ROW_ID, t.LVL, t.AUDIT_LOG, t.BUSCOMP_NAME, t.TBL_NAME, t.TOKEN, 
        CASE WHEN p.ROWCNT = p.NUM  -- fetch next technical token after reached the end of token array
            THEN TO_CHAR(SUBSTR(t.TOKEN, 1, 1))
            ELSE p.TYPE
        END TYPE,
        CASE WHEN p.ROWCNT = p.NUM
            THEN TO_NUMBER(SUBSTR(t.TOKEN, 2))
            ELSE p.ROWCNT
        END ROWCNT,
        CASE WHEN p.ROWCNT = p.NUM -- also reset a row number
            THEN 0
            ELSE p.NUM + 1
        END NUM
    from tokens t
        join blocks p on t.row_id = p.row_id and t.lvl = p.lvl + 1
),
-- combine arrays into records and map columns into fields
recs as (
    -- J(oin) entries are recorded with field names
    select j.*, to_char(j.token) FIELD_NAME, o.token OLD_VAL, n.token NEW_VAL
    from blocks j
        join blocks n on n.row_id = j.row_id and n.num = j.num and n.type = 'L'
        join blocks o on o.row_id = j.row_id and o.num = j.num and o.type = 'K'
    where j.num > 0 and j.type = 'J'
    union all
    -- C(olumn) entries need to be mapped to field names through [Administration - Audit Trail]
    select c.*, NVL(a.field_name, to_char(c.token)) FIELD_NAME, o.token OLD_VAL, n.token NEW_VAL
    from blocks c
        join blocks n on n.row_id = c.row_id and n.num = c.num and n.type = 'N'
        join blocks o on o.row_id = c.row_id and o.num = c.num and o.type = 'O'
        left join (
            select b.buscomp_name, f.tbl_name, f.col_name, f.field_name,
                ROW_NUMBER() OVER (PARTITION BY b.buscomp_name, f.tbl_name, f.col_name ORDER BY f.created desc) row_num
            from siebel.S_AUDIT_BUSCOMP b
                join siebel.S_AUDIT_FIELD f on f.audit_bc_id = b.row_id
        ) a on a.col_name = to_char(c.token) and a.buscomp_name = c.buscomp_name and a.tbl_name = c.tbl_name and row_num = 1 -- fetching only last created field entry
    where c.num > 0 and c.type = 'C'
)
select ROW_ID, NUM, FIELD_NAME, NEW_VAL, OLD_VAL 
from recs 
    -- here you can join original S_AUDIT_LOG records by ROW_ID if you need more info
order by ROW_ID desc, TYPE desc, NUM asc;

And it is also on GitHub.

Great news, a freshly baked version of the About View bookmarklet has arrived.

Been working on it slowly for quite a long time. Started this version from scratch to get rid of EJS and experiment more with plain ES6 JS. Hope it deserves a separate post.

New features in version 2.0:

  • First of all, the source code is now much more friendly, so don't hesitate to make your hands dirty and tune it to your needs.
  • It now has settings that are stored in localStorage:
    • Advanced properties - turn it off when overwhelmed with unnecessary OUI properties
    • Options to customize how applets and controls are listed
    • Event handlers for links, whitespace, bookmark icon clicks
    • Focus feature to highlight the deepest expanded element
  • Applets arranged hierarchically from primary BC at the top to indented child BC and popup applets at the bottom
  • Hierarchy of custom PR, PM, and PW classes for each element
  • Current workspace name and version. Keep in mind it is not refreshed when inspected through my quick inspect tool until you refresh a page.
  • Plenty of new attributes including a screen name, current page recordset, search spec (when available) and others.
  • Also fixed several defects here and there.

As I mentioned it is relatively fresh, so stay tuned for further upgrades and I'll greatly appreciate all feedback.

Recent version:

  • About View 2.0 source code
  • (() => {
    
        if ("undefined" == typeof SiebelApp) {
            alert("It works only in Siebel OUI session!");
            return;
        }
    
        const Id = "XapuksAboutView2";
    
        // read options localStorage
        let options = {};
        if (localStorage.getItem(Id)) {
            options = JSON.parse(localStorage.getItem(Id));
        } else {
            resetOptions();
        }
    
        let $d = $(`.ui-dialog.${Id}`);
    
        // event handling
        const handlers = {
            "None": (e) => {
                return true;
            },
            "Expand / Special": (e) => {
                id = $(e.target).attr("data-ul");
                if (id) {
                    $d.find(`ul.ul_show:not(#${id}):not(:has(#${id})):not(.keep_open)`).removeClass("ul_show").addClass("ul_hide");
                    $d.find("#" + id).toggleClass(['ul_show', 'ul_hide']);
                    e.stopPropagation();
                    return false;
                } else {
                    e.stopPropagation();
                    return true;
                }
            },
            "Expand active context": (e) => {
                const a = SiebelApp.S_App.GetActiveView().GetActiveApplet();
                if (a) {
                    $d.find(`ul#${a.GetFullId()}, ul#${a.GetFullId()}_controls, ul#${a.GetActiveControl()?.GetInputName()}`).removeClass("ul_hide").addClass("ul_show");
                }
                e?.stopPropagation();
                return false;
            },
            "Copy active applet": (e) => {
                const a = SiebelApp.S_App.GetActiveView().GetActiveApplet();
                if (a) {
                    handlers["Copy value"]({
                        target: $d.find(`a[data-ul=${a.GetFullId()}]`)[0]
                    });
                }
            },
            "Collapse item": (e) => {
                $d.find(`ul.ul_show:not(:has(.ul_show:not(.keep_open))):not(.keep_open)`).removeClass("ul_show").addClass("ul_hide");
                e.stopPropagation();
                return false;
            },
            "Collapse all": (e) => {
                $d.find(`ul.ul_show:not(.keep_open)`).removeClass("ul_show").addClass("ul_hide");
                e.stopPropagation();
                return false;
            },
            "Close dialog": (e) => {
                $d.dialog("close");
                e.stopPropagation();
                return false;
            },
            "Options": (e) => {
                rOptions();
                e.stopPropagation();
                return false;
            },
            "Copy value and close": (e) => {
                handlers["Copy value"](e);
                handlers["Close dialog"](e);
                e.stopPropagation?.call(this);
                return false;
            },
            "Copy value": (e) => {
                const scope = e.target;
                // replacing link with intput and select the value
                const val = $(scope).text();
                $(scope).hide().after("<input id='" + Id + "i'>");
                $d.find("#" + Id + "i").val(val).select();
                // attempt to copy value
                if (document.execCommand("copy", false, null)) {
                    // if copied, display a message for a second
                    $d.find("#" + Id + "i").attr("disabled", "disabled").css("color", "red").val("Copied!");
                    setTimeout(() => {
                        $d.find("#" + Id + "i").remove();
                        $(scope).show();
                    }, 700);
                } else {
                    // if failed to copy, leave input until blur, so it can be copied manually
                    $d.find("#" + Id + "i").blur(() => {
                        $(this).remove();
                        $d.find("a").show();
                    });
                }
                e.stopPropagation?.call(this);
                return false;
            },
            "Invoke applet method": (e) => {
                var $target = $(e.target);
                var applet = SiebelApp.S_App.GetActiveView().GetAppletMap()[$target.attr("data-applet")];
                const method = $target.text();
                applet.InvokeMethod(method);
                e.stopPropagation?.call(this);
                return false;
            },
            "Invoke control method": (e) => {
                var $target = $(e.target);
                var applet = SiebelApp.S_App.GetActiveView().GetAppletMap()[$target.attr("data-applet")];
                var control = applet.GetControls()[$target.attr("data-control")];
                const method = $target.text();
                SiebelApp.S_App.uiStatus.Busy();
                try {
                    applet.GetPModel().OnControlEvent(SiebelApp.Constants.get("PHYEVENT_INVOKE_CONTROL"), control.GetMethodName(), control.GetMethodPropSet(), {
                        async: true,
                        cb: () => SiebelApp.S_App.uiStatus.Free()
                    });
                } catch (e) {
                    console.error(e.toString());
                    SiebelApp.S_App.uiStatus.Free();
                }
                e.stopPropagation?.call(this);
                return false;
            },
            "Focus": (e) => {
                var $target = $(e.target);
                const $el = $($target.attr("data-focus"));
                $d.dialog('close');
                $el.focus();
                e.stopPropagation?.call(this);
                return false;
            },
            "Expand related": (e) => {
                var $target = $(e.target);
                var sel = $target.attr("data-selector");
                $d.find(`ul.ul_show:not(.keep_open)`).removeClass("ul_show").addClass("ul_hide");
                $d.find(sel).toggleClass(['ul_show', 'ul_hide']);
                e.stopPropagation?.call(this);
                return false;
            }
    
        };
    
        // handle double click
        if ($d.length) {
            var o = options["bmk_dbl"];
            handlers[o]();
            return;
        }
    
        // render the dialog
        let guid = 0;
        const css = [`<style>`, ...[
            `ul {margin-left:20px}`,
            `a.x_active {text-decoration:underline}`,
            `a.x_hidden {font-style:italic}`,
            `select {display:block; margin-bottom:15px}`,
            `.options {background-color:lightgray; padding:15px; margin:10px}`,
            `.ul_hide {display:none}`,
            `.ul_show {border-bottom: 1px solid; border-top: 1px solid; margin: 5px; padding: 5px; border-color: lightgray;}`,
            options["focus_feature"] == "true" ? `ul:has(.ul_show:not(.keep_open)) :is(label,a) {color:darkgray!important}` : ``,
            options["focus_feature"] == "true" ? `ul.ul_show:not(:has(.ul_show:not(.keep_open))) :is(label,a) {color:black!important}` : ``,
            `a[data-ul] {font-weight:bold}`,
            `a[data-ul]:before {content:"> "; opacity:0.5; font-style:normal}`,
            `a[data-handler]:before, a[data-focus]:before {content:"<["; opacity:0.5; font-style:normal}`,
            `a[data-handler]:after, a[data-focus]:after {content:"]>"; opacity:0.5; font-style:normal}`,
            `label {font-size:1rem; margin:0px; font-weight:bold;}`,
            `table {display:block; overflow-x:auto; whitespace: nowrap}`,
            `td {border:solid 1px}`,
            `.options select {width:250px}`,
        ].map((i) => i ? `.${Id} ${i}` : ``), `</style>`].join("\n");
    
        $d = $(`<div class="container" title="About View 2.0">${rApplication()}${css}</div>`).dialog({
            dragStop: () => $d.dialog({ height: 'auto' }),
            classes: { "ui-dialog": Id },
            modal: true,
            width: options["width"],
            close: () => $d.dialog('destroy').remove(),
            buttons: [
                {
                    text: 'Help',
                    click: () => window.open('http://xapuk.com/index.php?topic=145', '_blank')
                }, {
                    text: 'Feedback',
                    click: () => window.location.href = "mailto:[email protected]?subject=AboutView2"
                }, {
                    text: 'Settings',
                    click: rOptions,
                }, {
                    text: 'Reset Settings',
                    click: resetOptions
                }, {
                    text: 'Close (esc)',
                    click: () => $d.dialog('close')
                }
            ]
        });
    
        function dispatchEvent(e, cb) {
            var $target = $(e.target);
            if (cb.name?.indexOf("Special") > 0) {
                if ($target.attr("data-handler") == "applet method") {
                    return handlers["Invoke applet method"](e);
                } else if ($target.attr("data-handler") == "control method") {
                    return handlers["Invoke control method"](e);
                } else if ($target.attr("data-selector")) {
                    return handlers["Expand related"](e);
                }
            }
            if ($target.attr("data-focus")) {
                return handlers["Focus"](e);
            }
            return cb(e);
        }
    
        $d.contextmenu(handlers[options["ws_right"]]);
        $d.click(handlers[options["ws_click"]]);
        $d.find("a").off("click").click((e) => dispatchEvent(e, handlers[options["link_click"]]));
        $d.find("a").off("contextmenu").contextmenu((e) => dispatchEvent(e, handlers[options["link_right"]]));
        $(".ui-widget-overlay").click(handlers[options["out_click"]]);
        $(".ui-widget-overlay").contextmenu(handlers[options["out_right"]]);
    
        function resetOptions(e) {
            options = {
                "bmk_dbl": "Expand active context",
                "ws_click": "None",
                "ws_right": "Collapse item",
                "link_click": "Copy value",
                "link_right": "Expand / Special",
                "out_click": "Close dialog",
                "out_right": "Close dialog",
                "adv": "true",
                "width": "1000",
                "ctrl_list_by": "name",
                "applet_list": "applet / bc",
                "applet_list_by": "name",
                "focus_feature": "false"
            };
            localStorage.setItem(Id, JSON.stringify(options));
        }
    
        // render functions
        function rDropdown(caption, field, list) {
            const id = field;
            const value = options[field];
            return [`<li>`,
                `<label for="${id}">${caption}</label>`,
                `<select id="${id}">`,
                list.map((i) => `<option value="${i}" ${i == value ? 'selected' : ''}>${i}</option>`),
                `</select>`,
                `<li>`].join("");
        }
    
        function rOptions() {
            if ($d.find(".options").length) {
                $d.find(".options").remove();
            } else {
                let html = [
                    `<div class="options">`,
                    `<h4>SETTINGS</h4>`,
                    rDropdown(`Advanced properties`, `adv`, [`false`, `true`]),
                    rDropdown(`Dialog width`, `width`, [`600`, `800`, `1000`]),
                    rDropdown(`Show in main list`, `applet_list`, [`applet`, `applet / bc`, `applet / bc / rowid`]),
                    rDropdown(`List applets by`, `applet_list_by`, [`name`, `title`]),
                    rDropdown(`List controls by`, `ctrl_list_by`, [`name`, `caption`]),
                    rDropdown(`Link click`, `link_click`, [`Copy value`, `Copy value and close`, `Expand / Special`, `None`]),
                    rDropdown(`Link right click`, `link_right`, [`Copy value`, `Copy value and close`, `Expand / Special`, `None`]),
                    rDropdown(`Bookmarklet double click`, `bmk_dbl`, [`Expand active context`, `Copy active applet`]),
                    rDropdown(`Whitespace click`, `ws_click`, [`None`, `Close dialog`, `Options`, `Expand active context`, `Collapse item`, `Collapse all`]),
                    rDropdown(`Whitespace right click`, `ws_right`, [`None`, `Close dialog`, `Options`, `Expand active context`, `Collapse item`, `Collapse all`]),
                    rDropdown(`Outside click`, `out_click`, [`Close dialog`, `Expand active context`, `Collapse item`, `Collapse all`]),
                    rDropdown(`Outside right click`, `out_right`, [`Close dialog`, `Expand active context`, `Collapse item`, `Collapse all`]),
                    rDropdown(`Focus feature`, `focus_feature`, [`false`, `true`]),
                    `<\div>`
                ].join("\n");
                $d.append(html);
                $d.find("select").change((e) => {
                    const c = e?.target;
                    if (c) {
                        options[c.id] = c.value;
                        localStorage.setItem(Id, JSON.stringify(options));
                    }
                });
            }
        }
    
        function rPS(prop) {
            return rItem(`<a href='#'>${prop[0]}</a>`, prop[1]);
        }
    
        function rHierarchy(caption, value, advanced) {
            var a = [];
            while ("object" === typeof value && value?.constructor?.name?.length > 1) {
                a.push(value.constructor.name);
                value = value.constructor.superclass;
            }
            return rItem(caption, a, advanced);
        }
    
        function rItem(caption, value, advanced, attribs = {}) {
            if (value && (!Array.isArray(value) || value.length) || "boolean" === typeof value) {
                if (!advanced || options["adv"] === `true`) {
                    guid++;
                    let id = Id + guid;
                    let sAttr = Object.entries(attribs).map(([p, v]) => `${p}="${v}"`).join(" ");
                    return (Array.isArray(value) ? [
                        `<li>`,
                        `<label for="${id}_0">`, caption, `:</label> `,
                        value.map((e, i) => `<a href="#" id="${id}_${i}" ${sAttr}>${e}</a>`).join(" > "),
                        `</li>`
                    ] : [
                        `<li>`,
                        `<label for="${id}">`, caption, `:</label> `,
                        `<a href="#" id="${id}" ${sAttr}>`, escapeHtml(value), `</a>`,
                        `</li>`
                    ]).join("");
                }
            }
            return "";
        }
    
        function rControl(control) {
            const id = control.GetInputName();
            const applet = control.GetApplet();
            const pr = SiebelAppFacade.ComponentMgr.FindComponent(applet.GetName())?.GetPR();
            const bc = applet.GetBusComp();
            const ps = control.GetMethodPropSet();
            const up = control.GetPMPropSet();
            const con = SiebelApp.Constants;
            let sel = `#${applet.GetFullId()} [name=${control.GetInputName()}]`;
            if (con.get("SWE_CTRL_RTCEMBEDDED") === control.GetUIType()) {
                sel = `#${applet.GetFullId()} #cke_${control.GetInputName()}`;
            }
            const cls = control === applet.GetActiveControl() ? 'x_active' : $(sel).length == 0 || $(sel).is(":visible") ? '' : 'x_hidden'
            return [`<li>`,
                `<a href="#" data-ul="${id}_c" class="${cls}">`,
                options["ctrl_list_by"] == 'caption' && control.GetDisplayName() ? control.GetDisplayName() : control.GetName(),
                `</a>`,
                `<ul id="${id}_c" class="ul_hide">`,
                rItem(control.GetControlType() == con.get("SWE_PST_COL") ? "List column" : control.GetControlType() == con.get("SWE_PST_CNTRL") ? "Control" : control.GetControlType(), control.GetName()),
                rItem("Display name", control.GetDisplayName()),
                rItem("Field", control.GetFieldName(), false, { "data-handler": "field", "data-selector": `ul#${applet.GetFullId()}_bc,ul#${applet.GetFullId()}_bc_${applet.GetBusComp().GetFieldMap()[control.GetFieldName()]?.index}` }),
                rItem("Value", bc?.GetFieldValue(control.GetFieldName())),
                rItem("Message", control.GetControlMsg()),
                ...(control.GetMessageVariableMap() && Object.keys(control.GetMessageVariableMap()).length ?
                    [`<li><a href="#" data-ul="${id}_var">Message variables (${Object.keys(control.GetMessageVariableMap()).length}):</a></li>`,
                    `<ul id="${id}_var" class="ul_hide">`,
                    Object.entries(control.GetMessageVariableMap()).map(rPS).join("\n"),
                        `</ul>`] : []),
                rItem("Type", control.GetUIType()),
                rItem("LOV", control.GetLovType()),
                rItem("MVG", control.IsMultiValue()),
                rItem("Method", control.GetMethodName(), false, { "data-handler": "control method", "data-applet": applet.GetName(), "data-control": control.GetName() }),
                ...(options["adv"] === `true` && ps && ps.propArrayLen ?
                    [`<li><a href="#" data-ul="${id}_method">Method properties (${ps.propArrayLen}):</a></li>`,
                    `<ul id="${id}_method" class="ul_hide">`,
                    Object.entries(ps.propArray).map(rPS).join("\n"),
                        `</ul>`] : []),
                //rItem("Id", id),
                rItem("Id", options["adv"] === `true` && $(sel).is(":focusable") ? [id, `<a href="#" data-focus='${sel}'>Focus</a>`] : id),
                rItem("Immidiate post changes", control.GetPostChanges()),
                rItem("Display format", control.GetDisplayFormat()),
                rItem("HTML attribute", control.GetHTMLAttr(), true),
                rItem("Display mode", control.GetDispMode()),
                rItem("Popup", control.GetPopupType() && [control.GetPopupType(), control.GetPopupWidth(), control.GetPopupHeight()].join(" / ")),
                rHierarchy("Plugin wrapper", SiebelApp.S_App.PluginBuilder.GetPwByControl(pr, control), true),
                rItem("Length", control.GetFieldName() ? control.GetMaxSize() : ""),
                ...(options["adv"] === `true` && up && up.propArrayLen ?
                    [`<li><a href="#" data-ul="${id}_up">User properties (${up.propArrayLen}):</a></li>`,
                    `<ul id="${id}_up" class="ul_hide">`,
                    Object.entries(up.propArray).map(rPS).join("\n"),
                        `</ul>`] : []),
                rItem("Object", `SiebelApp.S_App.GetActiveView().GetAppletMap()["${applet.GetName()}"].GetControls()["${control.GetName()}"]`, true),
                $(sel).length > 0 ? rItem("Node", `$("${sel}")`, true) : ``,
                `</ul>`, `</li>`].join("");
        }
    
        function rApplet(applet) {
            const cm = Object.values(applet.GetControls());
            const mm = applet.GetCanInvokeArray();
            const id = applet.GetFullId();
            return [`<ul id="${id}" class="ul_hide">`,
            rItem("Applet", applet.GetName()),
            rItem("BusComp", applet.GetBusComp()?.GetName(), false, { "data-handler": "buscomp", "data-selector": `ul#${applet.GetFullId()}_bc` }),
            rItem("Title", SiebelApp.S_App.LookupStringCache(applet.GetTitle())),
            rItem("Mode", applet.GetMode()),
            rItem("Record counter", applet.GetPModel().GetStateUIMap().GetRowCounter, true),
            rHierarchy("PModel", applet.GetPModel(), true),
            rHierarchy("PRender", SiebelAppFacade.ComponentMgr.FindComponent(applet.GetName())?.GetPR(), true),
            rItem("Object", `SiebelApp.S_App.GetActiveView().GetAppletMap()["${applet.GetName()}"]`, true),
            rItem("Node", `$("#${applet.GetFullId()}")`, true),
            `<li><a href="#" data-ul="${id}_methods">Methods (${mm.length}):</a></li>`,
            `<ul id="${id}_methods" class="ul_hide">`,
            mm.map(m => [`<li>`, `<a href="#" data-handler="applet method" data-applet="${applet.GetName()}">`, m, `</a>`, `</li>`].join("")).join("\n"),
                `</ul>`,
            `<li><a href="#" data-ul="${id}_controls">Controls (${cm.length}):</a></li>`,
            `<ul id="${id}_controls" class="ul_show keep_open">`, //<ul id="${id}_controls" class="ul_show">
            ...cm.map(rControl),
                `</ul>`,
                `</ul>`].join("\n");
        }
    
        function rField(field, id) {
            const bc = field.GetBusComp();
            const name = SiebelApp.S_App.LookupStringCache(field.GetName());
            return [`<li>`,
                `<a href="#" data-ul="${id}">`, name, `</a>`,
                `<ul id="${id}" class="ul_hide">`,
                rItem("Field", name),
                rItem("Value", field.GetBusComp().GetFieldValue(name)),
                rItem("Type", field.GetDataType()),
                rItem("Length", field.GetLength()),
                rItem("Search spec", field.GetSearchSpec()),
                rItem("Calculated", !!field.IsCalc()),
                rItem("Bounded picklist", !!field.IsBoundedPick()),
                rItem("Read only", !!field.IsReadOnly()),
                rItem("Immediate post changes", !!field.IsPostChanges()),
                rItem("Object", `SiebelApp.S_App.GetBusObj().GetBusCompByName("${bc.GetName()}").GetFieldMap()["${name}"]`, true),
                `</ul>`, `</li>`].join("\n");
        }
    
        function rBC(a, id) {
            var bc = a.GetBusComp();
            const fields = Object.values(bc.GetFieldMap());
            return [`<ul id="${id}" class="ul_hide">`,
            rItem("BusComp", bc.GetName()),
            rItem("Commit pending", !!bc.commitPending, true),
            rItem("Can update", !!bc.canUpdate),
            rItem("Search spec", bc.GetSearchSpec()),
            rItem("Sort spec", bc.GetSortSpec()),
            rItem("Current row id", bc.GetIdValue()),
            rItem("Object", `SiebelApp.S_App.GetBusObj().GetBusComp("${a.GetBCId()}")`, true),
            `<li><label><a href="#" data-ul="${id}_rec">Records: ${Math.abs(bc.GetCurRowNum())} of ${bc.GetNumRows()}${bc.IsNumRowsKnown() ? '' : '+'}</a></label></li>`,
            `<ul id="${id}_rec" class="ul_hide">`,
                `<table>`,
                `<tr>`,
            ...Object.keys(bc.GetFieldMap()).map((i) => `<th>${i}</th>`),
                `</tr>`,
            ...bc.GetRecordSet().map((r, i) => [
                `<tr>`,
                ...Object.values(r).map(v => [
                    `<td><a href="#" ${bc.GetSelection() == i ? ` class="x_active"` : ``}>`,
                    v,
                    `</a></td>`
                ].join("")),
                `</tr>`
            ].join("")),
                `</table>`,
                `</ul>`,
            `<li><label><a href="#" data-ul="${id}_fields">Fields(${bc.GetFieldList()?.length}):</a></label></li>`,
            `<ul id="${id}_fields" class="ul_show keep_open">`,
            ...fields.map((field, i) => rField(field, id + "_" + field.index)),
                `</ul>`,
                `</ul>`].join("\n");
        }
    
        function rApplication() {
            const app = SiebelApp.S_App;
            const view = app.GetActiveView();
            const bo = app?.GetBusObj();
            const bm = bo?.GetBCArray();
            const scrPM = SiebelApp.S_App.NavCtrlMngr()?.GetscreenNavigationPM();
            let am = Object.values(view?.GetAppletMap());
            var ws = SiebelApp.S_App.GetWSInfo().split("_");
            var wsver = ws.pop();
    
            var amCache = {};
            Object.assign(amCache, view?.GetAppletMap());
    
            // Identifying a primary BC
            var paa = Object.values(SiebelApp.S_App.GetActiveView().GetAppletMap()).filter((a) => !a.GetParentApplet() && (!a.GetBusComp() || !a.GetBusComp().GetParentBusComp()));
            if (!paa.length) {
                alert("Failed to identify a primary BusComp!")
            }
    
            return [`<ul>`,
                rItem("Application", app.GetName()),
                rItem("Screen", scrPM?.Get("GetTabInfo")[scrPM?.Get("GetSelectedTabKey")]?.screenName),
                rItem("View", view.GetName()),
                rItem("Task", view.GetActiveTask()),
                rHierarchy("PModel", SiebelAppFacade.ComponentMgr.FindComponent(view.GetName())?.GetPM(), true),
                rHierarchy("PRender", SiebelAppFacade.ComponentMgr.FindComponent(view.GetName())?.GetPR(), true),
                rItem("BusObject", bo?.GetName()),
                rItem("Workspace", [ws.join("_"), wsver]),
                `<label>Applets (${am.length}) / BusComps (${bm.length}):</label>`,
                `<ul>`,
                hierBC(paa[0].GetBusComp(), 0, amCache),
                ...Object.values(amCache).map((a) => rAppletName(a, 0, amCache)),
                `</ul></ul>`].join("\n");
        }
    
        // prints applet name
        function rAppletName(a, l, amCache) {
            delete amCache[a.GetName()];
            return [`<li>`,
                `<ul>`.repeat(l),
                `<a href="#" data-ul="${a.GetFullId()}" class="${a === SiebelApp.S_App.GetActiveView().GetActiveApplet() ? 'x_active' : $(`#${a.GetFullId()}`).is(":visible") ? '' : 'x_hidden'}">`,
                options['applet_list_by'] == 'title' && SiebelApp.S_App.LookupStringCache(a.GetTitle()) ? SiebelApp.S_App.LookupStringCache(a.GetTitle()) : a.GetName(),
                `</a>`,
                a.GetBusComp() && options["applet_list"].indexOf("bc") > -1 ? ` / <a href="#" data-ul="${a.GetFullId()}_bc">${a.GetBusComp().GetName()}</a>` : ``,
                a.GetBusComp() && a.GetBusComp().GetIdValue() && options["applet_list"].indexOf("rowid") > -1 ? ` / <a href="#">${a.GetBusComp().GetIdValue()}</a>` : ``,
                rApplet(a),
                a.GetBusComp() && rBC(a, a.GetFullId() + "_bc"),
                `</ul>`.repeat(l),
                `</li>`].join("");
        }
    
        // prints applets based on bc or parent applet (rec)
        function hierApplet(bc, pa, l, amCache) {
            return Object.values(amCache).filter((a) => bc && a.GetBusComp() === bc || pa && a.GetParentApplet() === pa).map((a) => !(a.GetName() in amCache) ? "" : [
                rAppletName(a, l, amCache),
                hierApplet(null, a, l + 1, amCache) // look for child applets
            ].join("\n"));
        }
    
        // prints applets based on BC hierarchy (rec)
        function hierBC(bc, l, amCache) {
            return [
                hierApplet(bc, null, l, amCache)?.join("\n"),
                ...SiebelApp.S_App.GetActiveBusObj().GetBCArray().filter((e) => e.GetParentBusComp() === bc).map((b) => hierBC(b, l + 1, amCache))
            ].join("\n");
        }
    
        // utilities
        function escapeHtml(html) {
            return html.toString()
                .replace(/&/g, "&")
                .replace(/</g, "<")
                .replace(/>/g, ">")
                .replace(/"/g, """)
                .replace(/'/g, "'");
        }
    })() 
  • About View 2.0 bookmarklet code to copy and paste into bookmark URL.
  • javascript:(function()%7B%2F*%0A%20%20%20%20%2B1.%20%5BClick%5D%20option%20should%20be%20against%20Type%3A%20Button%2C%20not%20method%0A%20%20%20%20%2B2.%20%5BInvoke%5D%20option%20should%20be%20available%20against%20every%20control%20with%20InvokeMethod%0A%09%2B3.%20Reorginize%20the%20code%2C%20so%20it%20is%20granular%20and%20can%20be%20easily%20reconfigured%20%0A%094.%20Fetch%20Applet%20and%20view%20UPs%20through%20PM%3A%0A%09%09var%20appletComponent%20%3D%20SiebelAppFacade.ComponentMgr.FindComponent(%7B%0A%09%09%09id%3A%20%5BopenAppletPopupJSON.AppletName%5D%5B0%5D%0A%09%09%7D)%3B%0A%09%09var%20popupAppletPM%20%3D%20appletComponent.GetPM()%3B%0A%20%20%20%20%2B5.%20Display%20a%20hierarchy%20of%20PM%2FPR%20classes%20for%20View%2FApplet%2FControl%20%0A%20%20%20%20%09-%20use%20Siebel%20facade%20hierarchy%20code%20snippet%0A%20%20%20%20%09-%20on%20click%20of%20PM%2FPR%20run%20inspect%20to%20open%20a%20source%20file%20when%20in%20Chrome%20DevTools%0A%20%20%20%20%2B6.%20BusComp%20object%20selector%20(make%20object%20selector%20hidden%20and%20coppy%20on%20double%20click%3F)%0A%20%20%20%207.%20make%20focus%20button%20a%20link%20and%20put%20it%20outside%20the%20spoiler%0A%20%20%20%20%2B8.%20another%20spoiler%20at%20the%20bottom%20with%20Options%3A%0A%20%20%20%20%09-%20left%2Fright%20click%20%3D%20copy%20%2B%20close%20%2B%20copy%20and%20close%20%2B%20expand%0A%20%20%20%20%09-%20advanced%20checkbox%20-%20if%20N%20-%20hide%20all%20OUI%20related%20properties%0A%20%20%20%20%09-%20store%20options%20in%20localStorage%0A%20%20%20%20%09-%20checkbox%20-%20always%20expand%20active%20applet%0A%20%20%20%20%09-%20checkbox%20-%20always%20expand%20active%20control%20(available%20only%20if%20prev%20option%20%3D%20Y)%0A%20%20%20%20-9.%20applet%20%2F%20control%20DOM%20selectors%20to%20have%20Inspect%20option%20-%20on%20click%20run%20inspect()%20-%20not%20possible%20to%20call%20inspect%20from%20JS%20%0A%20%20%20%20%2B10.%20IP19%20compatible%0A%20%20%20%20-11.%20Renders%20hidden%20content%20when%20expanded%2C%20not%20in%20advance%20-%20more%20reliable%20and%20fast%0A%20%20%20%2012.%20for%20applets_div%20and%20controls%20container%20print%20out%20event%20listeners%20(event%2C%20selector%2C%20inspect(function))%20-%20code%20snippet%20%3D%20Node%20event%20listeners%0A%20%20%20%20%2B13.%20display%20applets%2FBCs%20in%20a%20hierarchycal%20order%20(use%20space%20or%20a%20glyph%20to%20indent)%0A%20%20%20%2014.%20Display%20a%20tree%20of%20all%20pm.Get()%20properties%20for%20view%20and%20applet%20-%20in%20our%20case%20it%20is%20where%20all%20portal%20config%20is%20stored%0A%20%20%20%2015.%20also%20display%20all%20SiebelApp.Constants%3F%20How%20to%20get%20a%20list%3F%0A%20%20%20%20%2B16.%20Get%20value%20through%20GetControlMsg()%20if%20GetFieldName()%20doesn't%20exist%20in%20the%20BC%20recordset%0A%20%20%20%2017.%20dblclick%20to%20run%20special%20methods%20(configurable)%20-%20focus%2C%20inspect%2C%20invoke%2C%20force%20show%2C%20etc%2C%20dblclick%20on%20whitespace%20gives%20you%20advanced%20look%0A%20%20%20%2018.%20print%20standalone%20applet%20in%20collapsed%20section%3F%20Through%20SiebelAppFacade.%0A%20%20%20%20-19.%20applet%2C%20application%20UPs%20and%20other%20staff%20comming%20from%20the%20server%20through%20SiebelApp.S_App.GetAppPropertySet()%20-%20nope%2C%20not%20applet%20UPs%0A%20%20%20%20%2B21.%20cater%20for%20empty%20applets%20with%20no%20records%2C%20export%20and%20import%20applets%0A%20%20%20%2022.%20expose%20RO%20attributte%20for%20all%20controls%20and%20have%20an%20option%20to%20update%20a%20field%20right%20away%0A%20%20%20%2023.%20commands%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20bookmarklet%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20double%20click%20%3D%20expand%20active%20applet%20and%20control%2Fcopy%20active%20applet%2Fcopy%20active%20control%20name%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20tripple%20click%20%3D%20reset%20all%20options%20to%20default%0A%20%20%20%20%20%20%20%20%20%20%20%20whitespace%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20click%20%3D%20show%20more%20options%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20right%20click%20%3D%20close%20window%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20double%20click%20%3D%20expand%20all%20or%20collapse%20all%20if%20expanded%0A%20%20%20%20%20%20%20%20%20%20%20%20links(%3Ca%3E)%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20left%20click%20%3D%20special%20command%20(expand%2C%20run%20method%2C%20inspect%2C%20etc)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20right%20click%20%3D%20copy%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-%20doule%20click%20%3D%20%0A%20%20%20%20%20%20%20%20%20%20%20%20other%20option%20example%20-%20right%20mb%20down%20while%20left%20mb%20is%20down%0A%20%20%20%20%2B24.%20need%20another%20way%20of%20fetching%20PR%20class%2C%20through%20FindComponent%3F%0A%20%20%20%20%20%20%20%20%20%20%20%20SiebelAppFacade.ComponentMgr.FindComponent(SiebelApp.S_App.GetActiveView().GetAppletMap()%5B'FaCS%20UX2%20NQSC%20Applicant%20Portal%20Application%20Left%20Pane%20Menu%20Items%20Applet'%5D.GetName()).GetPR()%3B%0A%20%20%20%20%2B25.%20Double%20click%20BM%20to%20expand%20current%20applet%20%2B%20carrent%20control%2C%20third%20click%20will%20expand%20all%0A%20%20%20%2026.%20wrap%20render%20functions%20into%20try%2Fcatch%20and%20return%20error%20text%20istead%20of%20html%0A%20%20%20%20%2B27.%20Print%20the%20entire%20RecordSet%20and%20hightlight%20an%20active%20record%20instead%20of%20just%20active%20record%0A%20%20%20%2028.%20option%20to%20print%20jquery%20selector%20%24(%22%23123%22)%20or%20node%20selector%20%24(%22%23123%22)%5B0%5D%0A%20%20%20%20%2B29.%20expose%20view%20and%20applet%20title%20as%20applet%20attribute%20and%20as%20a%20value%20for%20AppletTitle%20control%0A%20%20%20%2030.%20option%20to%20toggle%20collapse%20all%20sections%20when%20expanding%20a%20neighbour%0A%20%20%20%20%2B31.%20toggle%20to%20show%20control%2Fapplet%20display%20name%20(GetDisplayName)%20or%20physical%20name%20(GetName)%20in%20the%20list%0A%20%20%20%20%2B32.%20get%20record%20count%20from%20SiebelApp.S_App.GetActiveView().GetAppletMap()%5B'FaCS%20UX2%20NQSC%20Application%20List%20Applet'%5D.GetPModel().GetStateUIMap().GetRowCounter%0A%20%20%20%20%2B34.%20escape%20field%20and%20message%20values%0A%09%2B34.%20invokable%20methods%2C%20swe%20properties%20of%20invokable%20control%0A%09%2B34.%20buscomp%20selector%20should%20be%20by%20id%0A%20%20%20%20%2B35.%20option%20to%20autoclose%20after%20copy%20%0A%20%20%20%20%2B36.%20Option%20to%20display%20a%20current%20rowid%20next%20to%20the%20applet%20%2F%20bc%0A%20%20%20%20%2B37.%20control%20UP%0A%20%20%20%20%2B38.%20link%20from%20the%20control%20to%20the%20field%20or%20just%20expand%20another%20level%20(call%20of%20the%20same%20renderer%20function)%20on%20right%20click%0A%20%20%20%20%2B39.%20fetch%20screen%20name%20using%20SiebelApp.S_App.NavCtrlMngr().GetscreenNavigationPM().Get(%22GetTabInfo%22)%2C%20active%20screen%20%3D%20SiebelApp.S_App.NavCtrlMngr().GetscreenNavigationPM().Get(%22GetSelectedTabKey%22)%0A%20%20%20%20%2B40.%20expandable%20items%20should%20have%20an%20indication%20like%20%22(%3E)%20(%5E)%20(%2B)%20collapsed%20item%22%20%22(V)%20(-)%20expanded%20item%22%0A%20%20%20%20%2B41.%20%5BReset%20options%5D%20button%20intead%20of%20event%20action%0A%20%20%20%20%2B42.%20Expand%20option%20should%20do%20applets%20and%20controls%0A%20%20%20%20%2B43.%20Keep%20Applet%20Controls%20list%20opened%20by%20default%0A%20%20%20%20%2B44.%20Special%20event%3A%0A%20%20%20%20%20%20%20%20-%20special%20attrib%20data-handler%20%3D%20%5Bfield%2C%20buscomp%2C%20applet%20method%2C%20control%20method%2C%20focus%5D%0A%20%20%20%20%20%20%20%20-%20special%20highlight%20that%20supports%20special%20event%20-%20%5Bvalue%5D%20(value)%20%3Cvalue%3E%0A%20%20%20%20%20%20%20%20-%20should%20be%20configurable%20for%20each%20instance%20or%20once%20for%20all%20SE%3F%0A%20%20%20%20%20%20%20%20-%20event%3F%20-%20%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20dbl%20click%20also%20triggers%20single%20click%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20right%20click%20already%20has%20an%20event%20handler%20(expand)%0A%20%20%20%20%20%20%20%20%2B%20(field%2C%20buscomp)%20SE%20on%20control%20field%20is%20to%20open%20a%20field%0A%20%20%20%20%20%20%20%20%2B%20(field)%20SE%20on%20applet%20buscomp%20is%20to%20open%20a%20buscomp%20-%20check%20how%20it%20works%20when%20buscomp%20is%20hidden%0A%20%20%20%20%20%20%20%20%2B%20(applet)%20SE%20on%20available%20method%20is%20to%20call%20it%0A%20%20%20%20%20%20%20%20%2B%20(applet%2C%20control)%20SE%20on%20control%20method%20is%20to%20call%20it%20-%20for%20all%20controls%20with%20InvokeMethod%0A%20%20%20%20%20%20%20%20%2B%20(applet%2C%20control)%20SE%20on%20control%20or%20control%20node%20selector%20or%20separate%20link%20%3Cfocus%3E%20is%20to%20focus%20on%20it%20-%20only%20works%20on%20visible%20elements%0A%20%20%20%20%2B45.%20right%20click%20on%20regular%20links%20(values)%20triggers%20right%20click%20on%20whitespace%0A%20%20%20%20%2B46.%20proper%20indent%20for%20expanded%20section%20is%20to%20wrap%20it%20into%20ul%20insted%20of%20indent%20-%20noticable%20on%20grandchild%20applets%0A%20%20%20%20%2B47.%20translate%20control%20type%20through%20the%20constants%20-%20check%20the%20full%20list%20in%20pwinfra%20and%20other%20PW%20%0A%20%20%20%20%2B48.%20Fancy%20CSS%20to%20fade%20everything%20otside%20the%20deepest%20expanded%20section%20to%20put%20atention%20to%20the%20expanded%20one%0A%20%20%20%20%20%20%20%20-%20not%20possible%20without%20js%20since%20(%3Anot%20and%20%3Ahas)%20are%20not%20supported%0A%20%20%20%20%20%20%20%20-%20append%20a%20special%20class%20%0A%20%20%20%20%2B49.%20fix%20all%20event%20handlers%20to%20be%20configurable%0A%20%20%20%2050.%20code%20review%0A%20%20%20%20%20%20%20%20-%20reorganise%20as%20class%3F%0A%20%20%20%20%2B51.%20revisit%20advanced%2Fdefauklt%20options%20(a%3Arecord%20counter%2C%20c%3Auser%20props%2C%20method%20props%2C%20focus%2C%20b%3Acommit%20pending%2C%20)%0A%20%20%20%20%2B52.%20value%20link%20right%20click%20should%20have%20a%20default%20handler%20(open%20menu%3F)%0A%20%20%20%20%2B53.%20option%20for%20focus%20border%20feature%0A%20%20%20%20%2B54.%20whitespace%20right%20click%20to%20collapse%20last%20expanded%20item%0A%20%20%20%2055.%20Check%20if%20there%20something%20else%20to%20expose%0A%20%20%20%20%2B58.%20list%20applet%20column%20visibility%3F%0A%20%20%20%20%2B59.%20view%20PR%2FPM%20doesn't%20work%20in%20fins%0A%20%20%20%20%2B61.%20rtf%20field%20visibility%2Fselector%0A%09%2B62.%20Show%20control%20length%20only%20if%20there%20a%20field%0A%20%20%20%20%0AVersion%203.0%20features%3A%0A%20%20%20%201.%20New%20concept%20-%20separate%20renderer%20and%20ref-data%3A%20%0A%20%20%20%20%20%20%20%20-%20ref-data%20example%3A%20%7B%0A%20%20%20%20%20%20%20%20%09%22Applet%22%3A%7B%0A%20%20%20%20%20%20%20%20%09%09%22Name%22%3A%22GetName%22%2C%0A%20%20%20%20%20%20%20%20%09%09%22PModel%22%3A%22GetPModel%22%0A%20%20%20%20%20%20%20%20%09%7D%2C%0A%20%20%20%20%20%20%20%20%09%22Control%22%3A%7B%0A%20%20%20%20%20%20%20%20%09%09...%0A%20%20%20%20%20%20%20%20%09%7D%0A%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20-%20store%20the%20structure%20in%20localStorage%0A%20%20%20%20%20%20%20%20-%20on%20label%20rightclick%20-%20exclude%20the%20prop%20from%20the%20tree%2C%20so%20user%20won't%20see%20it%20anymore%0A%20%20%20%20%20%20%20%20-%20under%20options%20-%20have%20a%20list%20of%20properties%20included%20and%20excluded%0A%20%20%20%2060.%20Make%20label%20also%20a%20link%20and%20copy%20function%20path%20on%20click%3F%0A%20%20%20%2056.%20Container%20page%20name%20expandable%20with%20a%20list%20of%20page%20items%20under%20it%20SiebelApp.S_App.GetChildren()%5B3%5D%0A%20%20%20%2057.%20Application%20UP%20%3D%20SiebelApp.S_App.GetPMPropSet()%0A%20%20%20%2033.%20print%20real%20event%20listeners%20since%20chrome%20leads%20to%20the%20jquery%20only%3F%0A%0A*%2F%0A%0A(()%3D%3E%7B%0A%0A%09if%20(%22undefined%22%20%3D%3D%20typeof%20SiebelApp)%7B%0A%09%09alert(%22It%20works%20only%20in%20Siebel%20OUI%20session!%22)%3B%0A%09%09return%3B%0A%09%7D%0A%09%0A%09const%20Id%20%3D%20%22XapuksAboutView2%22%3B%0A%0A%09%2F%2F%20read%20options%20localStorage%0A%09let%20options%20%3D%20%7B%7D%3B%0A%09if%20(localStorage.getItem(Id))%20%7B%0A%09%09options%20%3D%20JSON.parse(localStorage.getItem(Id))%3B%0A%09%7D%20else%20%7B%0A%09%09resetOptions()%3B%0A%09%7D%0A%0A%09let%20%24d%20%3D%20%24(%60.ui-dialog.%24%7BId%7D%60)%3B%09%0A%0A%20%20%20%20%2F%2F%20event%20handling%0A%09const%20handlers%20%3D%20%7B%0A%09%09%22None%22%3A(e)%3D%3E%7B%0A%09%09%09return%20true%3B%0A%09%09%7D%2C%0A%09%09%22Expand%20%2F%20Special%22%3A(e)%3D%3E%7B%0A%09%09%09id%20%3D%20%24(e.target).attr(%22data-ul%22)%3B%0A%09%09%09if%20(id)%20%7B%0A%09%09%09%09%24d.find(%60ul.ul_show%3Anot(%23%24%7Bid%7D)%3Anot(%3Ahas(%23%24%7Bid%7D))%3Anot(.keep_open)%60).removeClass(%22ul_show%22).addClass(%22ul_hide%22)%3B%0A%09%09%09%09%24d.find(%22%23%22%20%2B%20id).toggleClass(%5B'ul_show'%2C'ul_hide'%5D)%3B%0A%09%09%09%20%20%20%20e.stopPropagation()%3B%0A%09%09%09%09return%20false%3B%0A%09%09%09%7D%20else%20%7B%0A%09%09%09%09e.stopPropagation()%3B%0A%09%09%09%09return%20true%3B%0A%09%09%09%7D%0A%09%09%7D%2C%0A%09%09%22Expand%20active%20context%22%3A(e)%3D%3E%7B%0A%09%09%09const%20a%20%3D%20SiebelApp.S_App.GetActiveView().GetActiveApplet()%3B%0A%09%09%09if%20(a)%20%7B%0A%09%09%09%09%24d.find(%60ul%23%24%7Ba.GetFullId()%7D%2C%20ul%23%24%7Ba.GetFullId()%7D_controls%2C%20ul%23%24%7Ba.GetActiveControl()%3F.GetInputName()%7D%60).removeClass(%22ul_hide%22).addClass(%22ul_show%22)%3B%0A%09%09%09%7D%0A%09%09%09e%3F.stopPropagation()%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Copy%20active%20applet%22%3A(e)%3D%3E%7B%0A%09%09%09const%20a%20%3D%20SiebelApp.S_App.GetActiveView().GetActiveApplet()%3B%0A%09%09%09if%20(a)%20%7B%0A%09%09%09%09handlers%5B%22Copy%20value%22%5D(%7B%0A%09%09%09%09%09target%3A%24d.find(%60a%5Bdata-ul%3D%24%7Ba.GetFullId()%7D%5D%60)%5B0%5D%0A%09%09%09%09%7D)%3B%0A%09%09%09%7D%0A%09%09%7D%2C%0A%09%09%22Collapse%20item%22%3A(e)%3D%3E%7B%0A%09%09%09%24d.find(%60ul.ul_show%3Anot(%3Ahas(.ul_show%3Anot(.keep_open)))%3Anot(.keep_open)%60).removeClass(%22ul_show%22).addClass(%22ul_hide%22)%3B%0A%09%09%09e.stopPropagation()%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Collapse%20all%22%3A(e)%3D%3E%7B%0A%09%09%09%24d.find(%60ul.ul_show%3Anot(.keep_open)%60).removeClass(%22ul_show%22).addClass(%22ul_hide%22)%3B%0A%09%09%09e.stopPropagation()%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Close%20dialog%22%3A(e)%3D%3E%7B%0A%09%09%09%24d.dialog(%22close%22)%3B%0A%09%09%09e.stopPropagation()%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Options%22%3A(e)%3D%3E%7B%0A%09%09%09rOptions()%3B%0A%09%09%09e.stopPropagation()%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Copy%20value%20and%20close%22%3A(e)%3D%3E%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20handlers%5B%22Copy%20value%22%5D(e)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20handlers%5B%22Close%20dialog%22%5D(e)%3B%0A%09%09%09e.stopPropagation%3F.call(this)%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Copy%20value%22%3A(e)%3D%3E%7B%0A%09%09%09const%20scope%20%3D%20e.target%3B%0A%09%09%09%2F%2F%20replacing%20link%20with%20intput%20and%20select%20the%20value%0A%09%09%09const%20val%20%3D%20%24(scope).text()%3B%0A%09%09%09%24(scope).hide().after(%22%3Cinput%20id%3D'%22%20%2B%20Id%20%2B%20%22i'%3E%22)%3B%0A%09%09%09%24d.find(%22%23%22%20%2B%20Id%20%2B%20%22i%22).val(val).select()%3B%0A%09%09%09%2F%2F%20attempt%20to%20copy%20value%0A%09%09%09if%20(document.execCommand(%22copy%22%2C%20false%2C%20null))%7B%0A%09%09%09%09%2F%2F%20if%20copied%2C%20display%20a%20message%20for%20a%20second%0A%09%09%09%09%24d.find(%22%23%22%20%2B%20Id%20%2B%20%22i%22).attr(%22disabled%22%2C%20%22disabled%22).css(%22color%22%2C%22red%22).val(%22Copied!%22)%3B%0A%09%09%09%09setTimeout(()%3D%3E%7B%0A%09%09%09%09%09%24d.find(%22%23%22%20%2B%20Id%20%2B%20%22i%22).remove()%3B%0A%09%09%09%09%09%24(scope).show()%3B%0A%09%09%09%09%7D%2C%20700)%3B%0A%09%09%09%7D%20else%20%7B%0A%09%09%09%09%2F%2F%20if%20failed%20to%20copy%2C%20leave%20input%20until%20blur%2C%20so%20it%20can%20be%20copied%20manually%0A%09%09%09%09%24d.find(%22%23%22%20%2B%20Id%20%2B%20%22i%22).blur(()%3D%3E%7B%0A%09%09%09%09%09%24(this).remove()%3B%0A%09%09%09%09%09%24d.find(%22a%22).show()%3B%0A%09%09%09%09%7D)%3B%20%20%20%20%09%0A%09%09%09%7D%0A%09%09%09e.stopPropagation%3F.call(this)%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Invoke%20applet%20method%22%3A%20(e)%20%3D%3E%20%7B%0A%09%09%09var%20%24target%20%3D%20%24(e.target)%3B%0A%09%09%09var%20applet%20%3D%20SiebelApp.S_App.GetActiveView().GetAppletMap()%5B%24target.attr(%22data-applet%22)%5D%3B%0A%09%09%09const%20method%20%3D%20%24target.text()%3B%0A%09%09%09applet.InvokeMethod(method)%3B%0A%09%09%09e.stopPropagation%3F.call(this)%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Invoke%20control%20method%22%3A%20(e)%20%3D%3E%20%7B%0A%09%09%09var%20%24target%20%3D%20%24(e.target)%3B%0A%09%09%09var%20applet%20%3D%20SiebelApp.S_App.GetActiveView().GetAppletMap()%5B%24target.attr(%22data-applet%22)%5D%3B%0A%09%09%09var%20control%20%3D%20applet.GetControls()%5B%24target.attr(%22data-control%22)%5D%3B%0A%09%09%09const%20method%20%3D%20%24target.text()%3B%0A%09%09%09SiebelApp.S_App.uiStatus.Busy()%3B%0A%09%09%09try%20%7B%0A%09%09%09applet.GetPModel().OnControlEvent(SiebelApp.Constants.get(%22PHYEVENT_INVOKE_CONTROL%22)%2C%20control.GetMethodName()%2C%20control.GetMethodPropSet()%2C%20%7B%0A%09%09%09%09async%3A%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cb%3A%20()%3D%3ESiebelApp.S_App.uiStatus.Free()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%3B%0A%09%09%09%7D%20catch(e)%20%7B%0A%09%09%09%09console.error(e.toString())%3B%0A%09%09%09%09SiebelApp.S_App.uiStatus.Free()%3B%0A%09%09%09%7D%0A%09%09%09e.stopPropagation%3F.call(this)%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Focus%22%3A%20(e)%20%3D%3E%20%7B%0A%09%09%09var%20%24target%20%3D%20%24(e.target)%3B%0A%09%09%09const%20%24el%20%3D%20%24(%24target.attr(%22data-focus%22))%3B%0A%09%09%09%24d.dialog('close')%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%24el.focus()%3B%0A%09%09%09e.stopPropagation%3F.call(this)%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%2C%0A%09%09%22Expand%20related%22%3A%20(e)%20%3D%3E%20%7B%0A%09%09%09var%20%24target%20%3D%20%24(e.target)%3B%0A%09%09%09var%20sel%20%3D%20%24target.attr(%22data-selector%22)%3B%0A%09%09%09%24d.find(%60ul.ul_show%3Anot(.keep_open)%60).removeClass(%22ul_show%22).addClass(%22ul_hide%22)%3B%0A%09%09%09%24d.find(sel).toggleClass(%5B'ul_show'%2C'ul_hide'%5D)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20e.stopPropagation%3F.call(this)%3B%0A%20%20%20%20%09%20%20%20%20return%20false%3B%0A%09%09%7D%0A%0A%09%7D%3B%0A%0A%20%20%20%20%2F%2F%20handle%20double%20click%0A%09if%20(%24d.length)%20%7B%0A%20%20%20%20%20%20%20%20var%20o%20%3D%20options%5B%22bmk_dbl%22%5D%3B%0A%20%20%20%20%20%20%20%20handlers%5Bo%5D()%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%09%2F%2F%20render%20the%20dialog%0A%09let%20guid%20%3D%200%3B%0A%09const%20css%20%3D%20%5B%60%3Cstyle%3E%60%2C%20...%5B%0A%09%09%60ul%20%7Bmargin-left%3A20px%7D%60%2C%0A%09%09%60a.x_active%20%7Btext-decoration%3Aunderline%7D%60%2C%0A%09%09%60a.x_hidden%20%7Bfont-style%3Aitalic%7D%60%2C%0A%09%09%60select%20%7Bdisplay%3Ablock%3B%20margin-bottom%3A15px%7D%60%2C%0A%09%09%60.options%20%7Bbackground-color%3Alightgray%3B%20padding%3A15px%3B%20margin%3A10px%7D%60%2C%0A%09%09%60.ul_hide%20%7Bdisplay%3Anone%7D%60%2C%0A%09%09%60.ul_show%20%7Bborder-bottom%3A%201px%20solid%3B%20border-top%3A%201px%20solid%3B%20margin%3A%205px%3B%20padding%3A%205px%3B%20border-color%3A%20lightgray%3B%7D%60%2C%0A%09%09options%5B%22focus_feature%22%5D%20%3D%3D%20%22true%22%20%3F%20%60ul%3Ahas(.ul_show%3Anot(.keep_open))%20%3Ais(label%2Ca)%20%7Bcolor%3Adarkgray!important%7D%60%3A%60%60%2C%0A%20%20%20%09%09options%5B%22focus_feature%22%5D%20%3D%3D%20%22true%22%20%3F%20%60ul.ul_show%3Anot(%3Ahas(.ul_show%3Anot(.keep_open)))%20%3Ais(label%2Ca)%20%7Bcolor%3Ablack!important%7D%60%3A%60%60%2C%0A%09%09%60a%5Bdata-ul%5D%20%7Bfont-weight%3Abold%7D%60%2C%0A%09%09%60a%5Bdata-ul%5D%3Abefore%20%7Bcontent%3A%22%3E%20%22%3B%20opacity%3A0.5%3B%20font-style%3Anormal%7D%60%2C%0A%09%09%60a%5Bdata-handler%5D%3Abefore%2C%20a%5Bdata-focus%5D%3Abefore%20%7Bcontent%3A%22%3C%5B%22%3B%20opacity%3A0.5%3B%20font-style%3Anormal%7D%60%2C%0A%09%09%60a%5Bdata-handler%5D%3Aafter%2C%20a%5Bdata-focus%5D%3Aafter%20%7Bcontent%3A%22%5D%3E%22%3B%20opacity%3A0.5%3B%20font-style%3Anormal%7D%60%2C%0A%09%09%60label%20%7Bfont-size%3A1rem%3B%20margin%3A0px%3B%20font-weight%3Abold%3B%7D%60%2C%0A%09%09%60table%20%7Bdisplay%3Ablock%3B%20overflow-x%3Aauto%3B%20whitespace%3A%20nowrap%7D%60%2C%0A%09%09%60td%20%7Bborder%3Asolid%201px%7D%60%2C%0A%09%09%60.options%20select%20%7Bwidth%3A250px%7D%60%2C%0A%09%5D.map((i)%3D%3Ei%20%3F%20%60.%24%7BId%7D%20%24%7Bi%7D%60%3A%60%60)%2C%60%3C%2Fstyle%3E%60%5D.join(%22%5Cn%22)%3B%0A%0A%09%24d%20%3D%20%24(%60%3Cdiv%20class%3D%22container%22%20title%3D%22About%20View%202.0%22%3E%24%7BrApplication()%7D%24%7Bcss%7D%3C%2Fdiv%3E%60).dialog(%7B%0A%09%09dragStop%3A%20()%20%3D%3E%20%24d.dialog(%7Bheight%3A'auto'%7D)%2C%20%0A%09%09classes%3A%20%7B%22ui-dialog%22%3A%20Id%7D%2C%0A%09%09modal%3A%20true%2C%0A%09%09width%3A%20options%5B%22width%22%5D%2C%0A%09%09close%3A%20()%20%3D%3E%20%24d.dialog('destroy').remove()%2C%0A%09%09buttons%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%20%20%20text%3A'Help'%2C%0A%09%09%09%20%20%20click%3A%20()%20%3D%3E%20window.open('http%3A%2F%2Fxapuk.com%2Findex.php%3Ftopic%3D145'%2C'_blank')%0A%09%09%09%7D%2C%7B%0A%09%09%09%20%20%20text%3A'Feedback'%2C%0A%09%09%09%20%20%20click%3A%20()%20%3D%3E%20window.location.href%3D%22mailto%3Avbabkin%40gmail.com%3Fsubject%3DAboutView2%22%0A%09%09%09%7D%2C%7B%0A%09%09%09%20%20%20text%3A'Settings'%2C%0A%09%09%09%20%20%20click%3A%20rOptions%2C%0A%09%09%09%7D%2C%7B%0A%09%09%09%20%20%20text%3A'Reset%20Settings'%2C%0A%09%09%09%20%20%20click%3A%20resetOptions%0A%09%09%09%7D%2C%7B%0A%09%09%09%20%20%20text%3A'Close%20(esc)'%2C%0A%09%09%09%20%20%20click%3A%20()%20%3D%3E%20%24d.dialog('close')%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D)%3B%0A%09%0A%09function%20dispatchEvent(e%2C%20cb)%20%7B%0A%09%09var%20%24target%20%3D%20%24(e.target)%3B%0A%09%09if%20(cb.name%3F.indexOf(%22Special%22)%20%3E%200)%20%7B%0A%09%09%09if%20(%24target.attr(%22data-handler%22)%20%3D%3D%20%22applet%20method%22)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20handlers%5B%22Invoke%20applet%20method%22%5D(e)%3B%0A%09%09%09%7D%20else%20if%20(%24target.attr(%22data-handler%22)%20%3D%3D%20%22control%20method%22)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20handlers%5B%22Invoke%20control%20method%22%5D(e)%3B%0A%09%09%09%7D%20else%20if%20(%24target.attr(%22data-selector%22))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20handlers%5B%22Expand%20related%22%5D(e)%3B%0A%09%09%09%7D%0A%09%09%7D%0A%09%09if%20(%24target.attr(%22data-focus%22))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20handlers%5B%22Focus%22%5D(e)%3B%0A%09%09%7D%0A%20%20%20%20%20%20%20%20return%20cb(e)%3B%0A%09%7D%0A%0A%20%20%20%20%24d.contextmenu(handlers%5Boptions%5B%22ws_right%22%5D%5D)%3B%0A%20%20%20%20%24d.click(handlers%5Boptions%5B%22ws_click%22%5D%5D)%3B%0A%20%20%20%20%24d.find(%22a%22).off(%22click%22).click((e)%3D%3EdispatchEvent(e%2C%20handlers%5Boptions%5B%22link_click%22%5D%5D))%3B%0A%20%20%20%20%24d.find(%22a%22).off(%22contextmenu%22).contextmenu((e)%3D%3EdispatchEvent(e%2C%20handlers%5Boptions%5B%22link_right%22%5D%5D))%3B%0A%20%20%20%20%24(%22.ui-widget-overlay%22).click(handlers%5Boptions%5B%22out_click%22%5D%5D)%3B%0A%20%20%20%20%24(%22.ui-widget-overlay%22).contextmenu(handlers%5Boptions%5B%22out_right%22%5D%5D)%3B%0A%0A%20%20%20%20function%20resetOptions(e)%20%7B%0A%09%09options%20%3D%20%7B%0A%09%09%09%22bmk_dbl%22%3A%20%22Expand%20active%20context%22%2C%0A%09%09%09%22ws_click%22%3A%20%22None%22%2C%0A%09%09%09%22ws_right%22%3A%20%22Collapse%20item%22%2C%0A%09%09%09%22link_click%22%3A%20%22Copy%20value%22%2C%0A%09%09%09%22link_right%22%3A%20%22Expand%20%2F%20Special%22%2C%0A%09%09%09%22out_click%22%3A%20%22Close%20dialog%22%2C%0A%09%09%09%22out_right%22%3A%20%22Close%20dialog%22%2C%0A%09%09%09%22adv%22%3A%20%22true%22%2C%0A%09%09%09%22width%22%3A%20%221000%22%2C%0A%09%09%09%22ctrl_list_by%22%3A%20%22name%22%2C%0A%09%09%09%22applet_list%22%3A%20%22applet%20%2F%20bc%22%2C%0A%09%09%09%22applet_list_by%22%3A%20%22name%22%2C%0A%09%09%09%22focus_feature%22%3A%22false%22%0A%09%09%7D%3B%0A%09%09localStorage.setItem(Id%2C%20JSON.stringify(options))%3B%0A%09%7D%0A%0A%20%20%20%20%2F%2F%20render%20functions%0A%20%20%20%20function%20rDropdown(caption%2C%20field%2C%20list)%20%7B%0A%20%20%20%20%09const%20id%20%3D%20field%3B%0A%20%20%20%20%09const%20value%20%3D%20options%5Bfield%5D%3B%0A%20%20%20%20%20%20%20%20return%20%5B%60%3Cli%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Clabel%20for%3D%22%24%7Bid%7D%22%3E%24%7Bcaption%7D%3C%2Flabel%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cselect%20id%3D%22%24%7Bid%7D%22%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20list.map((i)%3D%3E%60%3Coption%20value%3D%22%24%7Bi%7D%22%20%24%7Bi%3D%3Dvalue%3F'selected'%3A''%7D%3E%24%7Bi%7D%3C%2Foption%3E%60)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%2Fselect%3E%60%2C%0A%20%20%20%20%20%20%20%20%60%3Cli%3E%60%5D.join(%22%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rOptions()%20%7B%0A%20%20%20%20%20%20%20%20if%20(%24d.find(%22.options%22).length)%20%7B%0A%20%20%20%20%20%20%20%20%09%24d.find(%22.options%22).remove()%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%09let%20html%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cdiv%20class%3D%22options%22%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3Ch4%3ESETTINGS%3C%2Fh4%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Advanced%20properties%60%2C%20%60adv%60%2C%20%5B%60false%60%2C%20%60true%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Dialog%20width%60%2C%20%60width%60%2C%20%5B%60600%60%2C%20%60800%60%2C%20%601000%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Show%20in%20main%20list%60%2C%20%60applet_list%60%2C%20%5B%60applet%60%2C%20%60applet%20%2F%20bc%60%2C%20%60applet%20%2F%20bc%20%2F%20rowid%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60List%20applets%20by%60%2C%20%60applet_list_by%60%2C%20%5B%60name%60%2C%20%60title%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60List%20controls%20by%60%2C%20%60ctrl_list_by%60%2C%20%5B%60name%60%2C%20%60caption%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Link%20click%60%2C%20%60link_click%60%2C%20%5B%60Copy%20value%60%2C%20%60Copy%20value%20and%20close%60%2C%20%60Expand%20%2F%20Special%60%2C%20%60None%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Link%20right%20click%60%2C%20%60link_right%60%2C%20%5B%60Copy%20value%60%2C%20%60Copy%20value%20and%20close%60%2C%20%60Expand%20%2F%20Special%60%2C%20%60None%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Bookmarklet%20double%20click%60%2C%20%60bmk_dbl%60%2C%20%5B%60Expand%20active%20context%60%2C%20%60Copy%20active%20applet%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Whitespace%20click%60%2C%20%60ws_click%60%2C%20%5B%60None%60%2C%20%60Close%20dialog%60%2C%20%60Options%60%2C%20%60Expand%20active%20context%60%2C%20%60Collapse%20item%60%2C%20%60Collapse%20all%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Whitespace%20right%20click%60%2C%20%60ws_right%60%2C%20%5B%60None%60%2C%20%60Close%20dialog%60%2C%20%60Options%60%2C%20%60Expand%20active%20context%60%2C%20%60Collapse%20item%60%2C%20%60Collapse%20all%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Outside%20click%60%2C%20%60out_click%60%2C%20%5B%60Close%20dialog%60%2C%20%60Expand%20active%20context%60%2C%20%60Collapse%20item%60%2C%20%60Collapse%20all%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Outside%20right%20click%60%2C%20%60out_right%60%2C%20%5B%60Close%20dialog%60%2C%20%60Expand%20active%20context%60%2C%20%60Collapse%20item%60%2C%20%60Collapse%20all%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rDropdown(%60Focus%20feature%60%2C%20%60focus_feature%60%2C%20%5B%60false%60%2C%20%60true%60%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%5Cdiv%3E%60%0A%20%20%20%20%20%20%20%20%09%5D.join(%22%5Cn%22)%3B%0A%20%20%20%20%20%20%20%20%09%24d.append(html)%3B%0A%20%20%20%20%20%20%20%20%09%24d.find(%22select%22).change((e)%3D%3E%7B%0A%09%09%09%09const%20c%20%3D%20e%3F.target%3B%0A%09%09%09%09if%20(c)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20options%5Bc.id%5D%20%3D%20c.value%3B%0A%09%09%09%09%20%20%20%20localStorage.setItem(Id%2C%20JSON.stringify(options))%3B%0A%09%09%09%09%7D%0A%09%09%09%7D)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rPS(prop)%20%7B%0A%20%20%20%20%09return%20rItem(%60%3Ca%20href%3D'%23'%3E%24%7Bprop%5B0%5D%7D%3C%2Fa%3E%60%2C%20prop%5B1%5D)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rHierarchy(caption%2C%20value%2C%20advanced)%20%7B%0A%20%20%20%20%09var%20a%20%3D%20%5B%5D%3B%0A%20%20%20%20%20%20%20%20while%20(%22object%22%20%3D%3D%3D%20typeof%20value%20%26%26%20value%3F.constructor%3F.name%3F.length%20%3E%201)%20%7B%0A%20%20%20%20%20%20%20%20%09a.push(value.constructor.name)%3B%0A%20%20%20%20%20%20%20%20%09value%20%3D%20value.constructor.superclass%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%09return%20rItem(caption%2C%20a%2C%20advanced)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rItem(caption%2C%20value%2C%20advanced%2C%20attribs%3D%7B%7D)%20%7B%0A%20%20%20%20%20%20%20%20if%20(value%20%26%26%20(!Array.isArray(value)%20%7C%7C%20value.length)%20%7C%7C%20%22boolean%22%20%3D%3D%3D%20typeof%20value)%20%7B%0A%20%20%20%20%20%20%20%20%09if%20(!advanced%20%7C%7C%20options%5B%22adv%22%5D%20%3D%3D%3D%20%60true%60)%20%7B%0A%09%09%09%09guid%2B%2B%3B%0A%09%09%09%09let%20id%20%3D%20Id%20%2B%20guid%3B%0A%09%09%09%09let%20sAttr%20%3D%20Object.entries(attribs).map((%5Bp%2Cv%5D)%3D%3E%60%24%7Bp%7D%3D%22%24%7Bv%7D%22%60).join(%22%20%22)%3B%0A%09%09%09%09return%20(Array.isArray(value)%20%3F%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cli%3E%60%2C%0A%09%09%09%09%09%60%3Clabel%20for%3D%22%24%7Bid%7D_0%22%3E%60%2Ccaption%2C%60%3A%3C%2Flabel%3E%20%60%2C%0A%09%09%09%09%09value.map((e%2C%20i)%20%3D%3E%60%3Ca%20href%3D%22%23%22%20id%3D%22%24%7Bid%7D_%24%7Bi%7D%22%20%24%7BsAttr%7D%3E%24%7Be%7D%3C%2Fa%3E%60).join(%22%20%3E%20%22)%2C%0A%09%09%09%09%09%60%3C%2Fli%3E%60%0A%09%09%09%09%5D%20%3A%20%5B%0A%09%09%09%09%09%60%3Cli%3E%60%2C%0A%09%09%09%09%09%60%3Clabel%20for%3D%22%24%7Bid%7D%22%3E%60%2Ccaption%2C%60%3A%3C%2Flabel%3E%20%60%2C%0A%09%09%09%09%09%60%3Ca%20href%3D%22%23%22%20id%3D%22%24%7Bid%7D%22%20%24%7BsAttr%7D%3E%60%2CescapeHtml(value)%2C%60%3C%2Fa%3E%60%2C%0A%09%09%09%09%09%60%3C%2Fli%3E%60%0A%09%09%09%09%5D).join(%22%22)%3B%0A%20%20%20%20%20%20%20%20%09%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20%22%22%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rControl(control)%20%7B%0A%20%20%20%20%09const%20id%20%3D%20control.GetInputName()%3B%0A%20%20%20%20%09const%20applet%20%3D%20control.GetApplet()%3B%0A%20%20%20%20%09const%20pr%20%3D%20SiebelAppFacade.ComponentMgr.FindComponent(applet.GetName())%3F.GetPR()%3B%0A%20%20%20%20%09const%20bc%20%3D%20applet.GetBusComp()%3B%0A%20%20%20%20%09const%20ps%20%3D%20control.GetMethodPropSet()%3B%0A%20%20%20%20%09const%20up%20%3D%20control.GetPMPropSet()%3B%0A%20%20%20%20%09const%20con%20%3D%20SiebelApp.Constants%3B%0A%20%20%20%20%09let%20sel%20%3D%20%60%23%24%7Bapplet.GetFullId()%7D%20%5Bname%3D%24%7Bcontrol.GetInputName()%7D%5D%60%3B%0A%20%20%20%20%09if%20(con.get(%22SWE_CTRL_RTCEMBEDDED%22)%20%3D%3D%3D%20control.GetUIType())%20%7B%0A%20%20%20%20%09%09sel%20%3D%20%60%23%24%7Bapplet.GetFullId()%7D%20%23cke_%24%7Bcontrol.GetInputName()%7D%60%3B%0A%20%20%20%20%09%7D%0A%20%20%20%20%09const%20cls%20%3D%20control%20%3D%3D%3D%20applet.GetActiveControl()%20%3F%20'x_active'%20%3A%20%24(sel).length%20%3D%3D%200%20%7C%7C%20%24(sel).is(%22%3Avisible%22)%20%3F%20''%20%3A%20'x_hidden'%0A%20%20%20%20%20%20%20%20return%20%5B%60%3Cli%3E%60%2C%0A%09%09%20%20%20%20%60%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_c%22%20class%3D%22%24%7Bcls%7D%22%3E%60%2C%20%0A%09%09%09%20%20%20%20options%5B%22ctrl_list_by%22%5D%20%3D%3D%20'caption'%20%26%26%20control.GetDisplayName()%20%3F%20control.GetDisplayName()%20%3A%20control.GetName()%20%2C%20%0A%09%09%09%60%3C%2Fa%3E%60%2C%0A%09%09%09%60%3Cul%20id%3D%22%24%7Bid%7D_c%22%20class%3D%22ul_hide%22%3E%60%2C%0A%09%09%09rItem(control.GetControlType()%20%3D%3D%20con.get(%22SWE_PST_COL%22)%3F%22List%20column%22%3Acontrol.GetControlType()%20%3D%3D%20con.get(%22SWE_PST_CNTRL%22)%3F%22Control%22%3Acontrol.GetControlType()%2C%20control.GetName())%2C%0A%09%09%09rItem(%22Display%20name%22%2C%20control.GetDisplayName())%2C%0A%09%09%09rItem(%22Field%22%2C%20control.GetFieldName()%2C%20false%2C%20%7B%22data-handler%22%3A%22field%22%2C%22data-selector%22%3A%60ul%23%24%7Bapplet.GetFullId()%7D_bc%2Cul%23%24%7Bapplet.GetFullId()%7D_bc_%24%7Bapplet.GetBusComp().GetFieldMap()%5Bcontrol.GetFieldName()%5D%3F.index%7D%60%7D)%2C%0A%09%09%09rItem(%22Value%22%2C%20bc%3F.GetFieldValue(control.GetFieldName()))%2C%0A%09%09%09rItem(%22Message%22%2C%20control.GetControlMsg())%2C%0A%09%09%09...(control.GetMessageVariableMap()%20%26%26%20Object.keys(control.GetMessageVariableMap()).length%20%3F%20%0A%09%09%09%09%5B%60%3Cli%3E%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_var%22%3EMessage%20variables%20(%24%7BObject.keys(control.GetMessageVariableMap()).length%7D)%3A%3C%2Fa%3E%3C%2Fli%3E%60%2C%0A%09%09%09%09%60%3Cul%20id%3D%22%24%7Bid%7D_var%22%20class%3D%22ul_hide%22%3E%60%2C%0A%09%09%09%09Object.entries(control.GetMessageVariableMap()).map(rPS).join(%22%5Cn%22)%2C%0A%09%09%09%09%60%3C%2Ful%3E%60%5D%20%3A%20%5B%5D)%2C%09%0A%09%09%09rItem(%22Type%22%2C%20control.GetUIType())%2C%0A%09%09%09rItem(%22LOV%22%2C%20control.GetLovType())%2C%0A%09%09%09rItem(%22MVG%22%2C%20control.IsMultiValue())%2C%0A%09%09%09rItem(%22Method%22%2C%20control.GetMethodName()%2C%20false%2C%20%7B%22data-handler%22%3A%22control%20method%22%2C%20%22data-applet%22%3Aapplet.GetName()%2C%22data-control%22%3Acontrol.GetName()%7D)%2C%0A%09%09%09...(options%5B%22adv%22%5D%20%3D%3D%3D%20%60true%60%20%26%26%20ps%20%26%26%20ps.propArrayLen%20%3F%20%0A%09%09%09%09%5B%60%3Cli%3E%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_method%22%3EMethod%20properties%20(%24%7Bps.propArrayLen%7D)%3A%3C%2Fa%3E%3C%2Fli%3E%60%2C%0A%09%09%09%09%60%3Cul%20id%3D%22%24%7Bid%7D_method%22%20class%3D%22ul_hide%22%3E%60%2C%0A%09%09%09%09Object.entries(ps.propArray).map(rPS).join(%22%5Cn%22)%2C%0A%09%09%09%09%60%3C%2Ful%3E%60%5D%20%3A%20%5B%5D)%2C%0A%09%09%09%2F%2FrItem(%22Id%22%2C%20id)%2C%0A%09%09%09rItem(%22Id%22%2C%20options%5B%22adv%22%5D%20%3D%3D%3D%20%60true%60%20%26%26%20%24(sel).is(%22%3Afocusable%22)%20%3F%20%5Bid%2C%20%60%3Ca%20href%3D%22%23%22%20data-focus%3D'%24%7Bsel%7D'%3EFocus%3C%2Fa%3E%60%5D%3Aid)%2C%09%0A%09%09%09rItem(%22Immidiate%20post%20changes%22%2C%20control.GetPostChanges())%2C%0A%09%09%09rItem(%22Display%20format%22%2C%20control.GetDisplayFormat())%2C%0A%09%09%09rItem(%22HTML%20attribute%22%2C%20control.GetHTMLAttr()%2C%20true)%2C%0A%09%09%09rItem(%22Display%20mode%22%2C%20control.GetDispMode())%2C%0A%09%09%09rItem(%22Popup%22%2C%20control.GetPopupType()%20%26%26%20%5Bcontrol.GetPopupType()%2C%20control.GetPopupWidth()%2C%20control.GetPopupHeight()%5D.join(%22%20%2F%20%22))%2C%0A%09%09%09rHierarchy(%22Plugin%20wrapper%22%2C%20SiebelApp.S_App.PluginBuilder.GetPwByControl(pr%2C%20control)%2C%20true)%2C%0A%09%09%09rItem(%22Length%22%2C%20control.GetFieldName()%20%3F%20control.GetMaxSize()%20%3A%20%22%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20...(options%5B%22adv%22%5D%20%3D%3D%3D%20%60true%60%20%26%26%20up%20%26%26%20up.propArrayLen%20%3F%20%0A%09%09%09%09%5B%60%3Cli%3E%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_up%22%3EUser%20properties%20(%24%7Bup.propArrayLen%7D)%3A%3C%2Fa%3E%3C%2Fli%3E%60%2C%0A%09%09%09%09%60%3Cul%20id%3D%22%24%7Bid%7D_up%22%20class%3D%22ul_hide%22%3E%60%2C%0A%09%09%09%09Object.entries(up.propArray).map(rPS).join(%22%5Cn%22)%2C%0A%09%09%09%09%60%3C%2Ful%3E%60%5D%20%3A%20%5B%5D)%2C%09%0A%09%09%09rItem(%22Object%22%2C%20%60SiebelApp.S_App.GetActiveView().GetAppletMap()%5B%22%24%7Bapplet.GetName()%7D%22%5D.GetControls()%5B%22%24%7Bcontrol.GetName()%7D%22%5D%60%2C%20true)%2C%0A%09%09%09%24(sel).length%20%3E%200%20%3F%20rItem(%22Node%22%2C%20%60%24(%22%24%7Bsel%7D%22)%60%2C%20true)%20%3A%20%60%60%2C%0A%09%09%60%3C%2Ful%3E%60%2C%60%3C%2Fli%3E%60%5D.join(%22%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rApplet(applet)%20%7B%0A%20%20%20%20%09const%20cm%20%3D%20Object.values(applet.GetControls())%3B%0A%20%20%20%20%09const%20mm%20%3D%20applet.GetCanInvokeArray()%3B%0A%20%20%20%20%09const%20id%20%3D%20applet.GetFullId()%3B%0A%20%20%20%20%20%20%20%20return%20%5B%60%3Cul%20id%3D%22%24%7Bid%7D%22%20class%3D%22ul_hide%22%3E%60%2C%0A%09%09%09rItem(%22Applet%22%2C%20applet.GetName())%2C%0A%09%09%09rItem(%22BusComp%22%2C%20applet.GetBusComp()%3F.GetName()%2C%20false%2C%20%7B%22data-handler%22%3A%22buscomp%22%2C%20%22data-selector%22%3A%60ul%23%24%7Bapplet.GetFullId()%7D_bc%60%7D)%2C%0A%09%09%09rItem(%22Title%22%2C%20SiebelApp.S_App.LookupStringCache(applet.GetTitle()))%2C%0A%09%09%09rItem(%22Mode%22%2C%20applet.GetMode())%2C%0A%09%09%09rItem(%22Record%20counter%22%2C%20applet.GetPModel().GetStateUIMap().GetRowCounter%2C%20true)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rHierarchy(%22PModel%22%2C%20applet.GetPModel()%2C%20true)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rHierarchy(%22PRender%22%2C%20SiebelAppFacade.ComponentMgr.FindComponent(applet.GetName())%3F.GetPR()%2C%20true)%2C%0A%09%09%09rItem(%22Object%22%2C%20%60SiebelApp.S_App.GetActiveView().GetAppletMap()%5B%22%24%7Bapplet.GetName()%7D%22%5D%60%2C%20true)%2C%0A%09%09%09rItem(%22Node%22%2C%20%60%24(%22%23%24%7Bapplet.GetFullId()%7D%22)%60%2C%20true)%2C%0A%09%09%09%60%3Cli%3E%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_methods%22%3EMethods%20(%24%7Bmm.length%7D)%3A%3C%2Fa%3E%3C%2Fli%3E%60%2C%0A%09%09%09%60%3Cul%20id%3D%22%24%7Bid%7D_methods%22%20class%3D%22ul_hide%22%3E%60%2C%0A%09%09%09mm.map(m%3D%3E%5B%60%3Cli%3E%60%2C%20%60%3Ca%20href%3D%22%23%22%20data-handler%3D%22applet%20method%22%20data-applet%3D%22%24%7Bapplet.GetName()%7D%22%3E%60%2C%20m%2C%20%60%3C%2Fa%3E%60%2C%20%60%3C%2Fli%3E%60%5D.join(%22%22)).join(%22%5Cn%22)%2C%0A%09%09%09%60%3C%2Ful%3E%60%2C%0A%09%09%09%60%3Cli%3E%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_controls%22%3EControls%20(%24%7Bcm.length%7D)%3A%3C%2Fa%3E%3C%2Fli%3E%60%2C%0A%09%09%09%60%3Cul%20id%3D%22%24%7Bid%7D_controls%22%20class%3D%22ul_show%20keep_open%22%3E%60%2C%20%2F%2F%3Cul%20id%3D%22%24%7Bid%7D_controls%22%20class%3D%22ul_show%22%3E%0A%09%09%09...cm.map(rControl)%2C%0A%09%09%09%60%3C%2Ful%3E%60%2C%0A%09%09%60%3C%2Ful%3E%60%5D.join(%22%5Cn%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rField(field%2C%20id)%20%7B%0A%20%20%20%20%09const%20bc%20%3D%20field.GetBusComp()%3B%0A%20%20%20%20%09const%20name%20%3D%20SiebelApp.S_App.LookupStringCache(field.GetName())%3B%0A%20%20%20%20%09return%20%5B%60%3Cli%3E%60%2C%0A%09%09%09%60%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D%22%3E%60%2C%20name%2C%20%60%3C%2Fa%3E%60%2C%0A%09%09%09%60%3Cul%20id%3D%22%24%7Bid%7D%22%20class%3D%22ul_hide%22%3E%60%2C%0A%09%09%09rItem(%22Field%22%2C%20name)%2C%0A%09%09%09rItem(%22Value%22%2C%20field.GetBusComp().GetFieldValue(name))%2C%0A%09%09%09rItem(%22Type%22%2C%20field.GetDataType())%2C%0A%09%09%09rItem(%22Length%22%2C%20field.GetLength())%2C%0A%09%09%09rItem(%22Search%20spec%22%2C%20field.GetSearchSpec())%2C%0A%09%09%09rItem(%22Calculated%22%2C%20!!field.IsCalc())%2C%0A%09%09%09rItem(%22Bounded%20picklist%22%2C%20!!field.IsBoundedPick())%2C%0A%09%09%09rItem(%22Read%20only%22%2C%20!!field.IsReadOnly())%2C%0A%09%09%09rItem(%22Immediate%20post%20changes%22%2C%20!!field.IsPostChanges())%2C%0A%09%09%09rItem(%22Object%22%2C%20%60SiebelApp.S_App.GetBusObj().GetBusCompByName(%22%24%7Bbc.GetName()%7D%22).GetFieldMap()%5B%22%24%7Bname%7D%22%5D%60%2C%20true)%2C%0A%09%09%60%3C%2Ful%3E%60%2C%20%60%3C%2Fli%3E%60%5D.join(%22%5Cn%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rBC(a%2C%20id)%20%7B%0A%20%20%20%20%09var%20bc%20%3D%20a.GetBusComp()%3B%0A%20%20%20%20%09const%20fields%20%3D%20Object.values(bc.GetFieldMap())%3B%0A%20%20%20%20%20%20%20%20return%20%5B%60%3Cul%20id%3D%22%24%7Bid%7D%22%20class%3D%22ul_hide%22%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22BusComp%22%2C%20bc.GetName())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Commit%20pending%22%2C%20!!bc.commitPending%2C%20true)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Can%20update%22%2C%20!!bc.canUpdate)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Search%20spec%22%2C%20bc.GetSearchSpec())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Sort%20spec%22%2C%20bc.GetSortSpec())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Current%20row%20id%22%2C%20bc.GetIdValue())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Object%22%2C%20%60SiebelApp.S_App.GetBusObj().GetBusComp(%22%24%7Ba.GetBCId()%7D%22)%60%2C%20true)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cli%3E%3Clabel%3E%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_rec%22%3ERecords%3A%20%24%7BMath.abs(bc.GetCurRowNum())%7D%20of%20%24%7Bbc.GetNumRows()%7D%24%7Bbc.IsNumRowsKnown()%3F''%3A'%2B'%7D%3C%2Fa%3E%3C%2Flabel%3E%3C%2Fli%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cul%20id%3D%22%24%7Bid%7D_rec%22%20class%3D%22ul_hide%22%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Ctable%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Ctr%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20...Object.keys(bc.GetFieldMap()).map((i)%3D%3E%60%3Cth%3E%24%7Bi%7D%3C%2Fth%3E%60)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%2Ftr%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20...bc.GetRecordSet().map((r%2C%20i)%3D%3E%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3Ctr%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20...Object.values(r).map(v%3D%3E%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3Ctd%3E%3Ca%20href%3D%22%23%22%20%24%7Bbc.GetSelection()%20%3D%3D%20i%20%3F%20%60%20class%3D%22x_active%22%60%20%3A%20%60%60%7D%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20v%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%2Fa%3E%3C%2Ftd%3E%60%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D.join(%22%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%2Ftr%3E%60%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D.join(%22%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%2Ftable%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%2Ful%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cli%3E%3Clabel%3E%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Bid%7D_fields%22%3EFields(%24%7Bbc.GetFieldList()%3F.length%7D)%3A%3C%2Fa%3E%3C%2Flabel%3E%3C%2Fli%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cul%20id%3D%22%24%7Bid%7D_fields%22%20class%3D%22ul_show%20keep_open%22%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20...fields.map((field%2C%20i)%20%3D%3E%20rField(field%2C%20id%20%2B%20%22_%22%20%2B%20field.index))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3C%2Ful%3E%60%2C%0A%20%20%20%20%20%20%20%20%60%3C%2Ful%3E%60%5D.join(%22%5Cn%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20rApplication()%20%7B%0A%20%20%20%20%20%20%20%20const%20app%20%3D%20SiebelApp.S_App%3B%0A%20%20%20%20%20%20%20%20const%20view%20%3D%20app.GetActiveView()%3B%0A%20%20%20%20%20%20%20%20const%20bo%20%3D%20app%3F.GetBusObj()%3B%0A%20%20%20%20%20%20%20%20const%20bm%20%3D%20bo%3F.GetBCArray()%3B%0A%20%20%20%20%20%20%20%20const%20scrPM%20%3D%20SiebelApp.S_App.NavCtrlMngr()%3F.GetscreenNavigationPM()%3B%0A%20%20%20%20%20%20%20%20let%20am%20%3D%20Object.values(view%3F.GetAppletMap())%3B%0A%20%20%20%20%20%20%20%20var%20ws%20%3D%20SiebelApp.S_App.GetWSInfo().split(%22_%22)%3B%0A%20%20%20%20%20%20%20%20var%20wsver%20%3D%20ws.pop()%3B%0A%0A%20%20%20%20%20%20%20%20var%20amCache%20%3D%20%7B%7D%3B%0A%20%20%20%20%20%20%20%20Object.assign(amCache%2C%20view%3F.GetAppletMap())%3B%0A%0A%09%09%2F%2F%20Identifying%20a%20primary%20BC%0A%09%09var%20paa%20%3D%20Object.values(SiebelApp.S_App.GetActiveView().GetAppletMap()).filter((a)%20%3D%3E%20!a.GetParentApplet()%20%26%26%20(!a.GetBusComp()%20%7C%7C%20!a.GetBusComp().GetParentBusComp()))%3B%0A%09%09if%20(!paa.length)%20%7B%0A%09%09%09alert(%22Failed%20to%20identify%20a%20primary%20BusComp!%22)%0A%09%09%7D%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20return%20%5B%60%3Cul%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Application%22%2C%20app.GetName())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Screen%22%2C%20scrPM%3F.Get(%22GetTabInfo%22)%5BscrPM%3F.Get(%22GetSelectedTabKey%22)%5D%3F.screenName)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22View%22%2C%20view.GetName())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Task%22%2C%20view.GetActiveTask())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rHierarchy(%22PModel%22%2C%20SiebelAppFacade.ComponentMgr.FindComponent(view.GetName())%3F.GetPM()%2C%20true)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rHierarchy(%22PRender%22%2C%20SiebelAppFacade.ComponentMgr.FindComponent(view.GetName())%3F.GetPR()%2C%20true)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22BusObject%22%2C%20bo%3F.GetName())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20rItem(%22Workspace%22%2C%20%5Bws.join(%22_%22)%2C%20wsver%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Clabel%3EApplets%20(%24%7Bam.length%7D)%20%2F%20BusComps%20(%24%7Bbm.length%7D)%3A%3C%2Flabel%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cul%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20hierBC(paa%5B0%5D.GetBusComp()%2C%200%2C%20amCache)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20...Object.values(amCache).map((a)%3D%3ErAppletName(a%2C%200%2C%20amCache))%2C%0A%20%20%20%20%20%20%20%20%60%3C%2Ful%3E%3C%2Ful%3E%60%5D.join(%22%5Cn%22)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F%2F%20prints%20applet%20name%0A%20%20%20%20function%20rAppletName(a%2C%20l%2C%20amCache)%20%7B%0A%20%20%20%20%09delete%20amCache%5Ba.GetName()%5D%3B%0A%20%20%20%20%09return%20%5B%60%3Cli%3E%60%2C%0A%09%09%09%09%20%20%20%20%60%3Cul%3E%60.repeat(l)%2C%0A%09%09%09%09%09%60%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Ba.GetFullId()%7D%22%20class%3D%22%24%7Ba%20%3D%3D%3D%20SiebelApp.S_App.GetActiveView().GetActiveApplet()%20%3F%20'x_active'%20%3A%20%24(%60%23%24%7Ba.GetFullId()%7D%60).is(%22%3Avisible%22)%20%3F%20''%20%3A%20'x_hidden'%7D%22%3E%60%2C%0A%09%09%09%09%09%09options%5B'applet_list_by'%5D%20%3D%3D%20'title'%20%26%26%20SiebelApp.S_App.LookupStringCache(a.GetTitle())%20%3F%20SiebelApp.S_App.LookupStringCache(a.GetTitle())%20%3A%20a.GetName()%2C%0A%09%09%09%09%09%60%3C%2Fa%3E%60%2C%0A%09%09%09%09%09a.GetBusComp()%20%26%26%20options%5B%22applet_list%22%5D.indexOf(%22bc%22)%20%3E%20-1%20%3F%20%60%20%2F%20%3Ca%20href%3D%22%23%22%20data-ul%3D%22%24%7Ba.GetFullId()%7D_bc%22%3E%24%7Ba.GetBusComp().GetName()%7D%3C%2Fa%3E%60%20%3A%20%60%60%2C%0A%09%09%09%09%09a.GetBusComp()%20%26%26%20a.GetBusComp().GetIdValue()%20%26%26%20options%5B%22applet_list%22%5D.indexOf(%22rowid%22)%20%3E%20-1%20%3F%20%60%20%2F%20%3Ca%20href%3D%22%23%22%3E%24%7Ba.GetBusComp().GetIdValue()%7D%3C%2Fa%3E%60%20%3A%20%60%60%2C%0A%09%09%09%09%09rApplet(a)%2C%0A%09%09%09%09%09a.GetBusComp()%20%26%26%20rBC(a%2C%20a.GetFullId()%20%2B%20%22_bc%22)%2C%0A%09%09%09%09%09%60%3C%2Ful%3E%60.repeat(l)%2C%0A%09%09%09%09%60%3C%2Fli%3E%60%5D.join(%22%22)%3B%0A%20%20%20%20%7D%0A%0A%09%2F%2F%20prints%20applets%20based%20on%20bc%20or%20parent%20applet%20(rec)%0A%09function%20hierApplet(bc%2C%20pa%2C%20l%2C%20amCache)%20%7B%0A%09%09return%20Object.values(amCache).filter((a)%20%3D%3E%20bc%20%26%26%20a.GetBusComp()%20%3D%3D%3D%20bc%20%7C%7C%20pa%20%26%26%20a.GetParentApplet()%20%3D%3D%3D%20pa).map((a)%20%3D%3E%20!(a.GetName()%20in%20amCache)%20%3F%20%22%22%20%3A%20%5B%0A%09%09%09rAppletName(a%2C%20l%2C%20amCache)%2C%0A%09%09%09hierApplet(null%2C%20a%2C%20l%20%2B%201%2C%20amCache)%20%2F%2F%20look%20for%20child%20applets%0A%09%09%5D.join(%22%5Cn%22))%3B%0A%09%7D%0A%0A%09%2F%2F%20prints%20applets%20based%20on%20BC%20hierarchy%20(rec)%0A%09function%20hierBC(bc%2C%20l%2C%20amCache)%20%7B%0A%09%09return%20%5B%0A%09%09%09hierApplet(bc%2C%20null%2C%20l%2C%20amCache)%3F.join(%22%5Cn%22)%2C%0A%09%09%09...SiebelApp.S_App.GetActiveBusObj().GetBCArray().filter((e)%20%3D%3E%20e.GetParentBusComp()%20%3D%3D%3D%20bc).map((b)%20%3D%3E%20hierBC(b%2C%20l%20%2B%201%2C%20amCache))%0A%09%09%5D.join(%22%5Cn%22)%3B%0A%09%7D%0A%0A%09%2F%2F%20utilities%0A%09function%20escapeHtml(html)%20%7B%0A%09%09return%20html.toString()%0A%09%09%20%20%20%20.replace(%2F%26%2Fg%2C%20%22%26amp%3B%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.replace(%2F%3C%2Fg%2C%20%22%26lt%3B%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.replace(%2F%3E%2Fg%2C%20%22%26gt%3B%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.replace(%2F%22%2Fg%2C%20%22%26quot%3B%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.replace(%2F'%2Fg%2C%20%22%26%23039%3B%22)%3B%0A%09%7D%0A%7D)()%7D)()%3B 
  • About View 2.0 bookmarklet link for drag and drop install.

I used Caio's tool to convert source code into a bookmarklet.

Previous version can be found here and on GitHub.

Today I want to share a couple of SQLs which helps me to leverage workspaced repository tables.


Modified objects


The first one is a simple flat query of workspace affected objects. Same data you see when clicking workspace in Siebel Tools.
Filter by the object name/type to get an idea of who is modifying/modified the object.

select w.name WS_NAME, w.status_cd WS_STATUS, v.VERSION_NUM WS_VER, pw.name BASE_WS_NAME,
  u.fst_name WS_AUTHOR, o.OBJ_TYPE , o.OBJ_NAME, o.operation_cd, o.last_upd
from siebel.S_REPOSITORY r -- repository
  join siebel.S_WORKSPACE pw on r.ROW_ID = pw.REPOSITORY_ID -- baseline workspace
  join siebel.S_WORKSPACE w on w.PAR_WS_ID = pw.ROW_ID -- child / dev workspace with changes
  join siebel.S_WS_VERSION v on v.ws_id = w.row_id -- versions of the workspace
  join siebel.S_CONTACT u on u.ROW_ID = w.CREATED_BY -- ws author
  join siebel.S_RTC_MOD_OBJ o on o.WS_VER_ID = v.ROW_ID and r.ROW_ID = o.REPOSITORY_ID -- modified objects
where r.name = 'Siebel Repository'
    and w.NAME = 'dev_vbabkin_d412_1' -- filter by dev branch name
    --and pw.NAME = 'int_dev' -- or baseline/parent branch name
    --and o.obj_type = 'Business Component' and o.obj_name = 'Account' -- or by affected object
order by o.last_upd desc;

Keep in mind that SQL only lists top-level objects (Applet, BusComp, Picklist etc) without any specific details.


Complete object modification history


The second query gives full information about object modifications in delivered workspaces including each attribute across all sub-objects.
When you deliver a workspace Siebel logs the history of all modifications done to the objects in your workspace. Below SQL just puts all the logs tables together.

select v.VERSION_NUM WS_VER, dw.name WS_NAME, u.fst_name AUTHOR, m.last_upd, w.name BASE_WS_NAME,
    c.TOP_PARENT_TYPE TOP_OBJ_TYPE, c.TOP_PARENT_NAME TOP_OBJ_NAME,
    c.obj_type, c.object_name, 
    a.attribute, NVL(a.old_cust_value, a.old_std_value) OLD_VAL, a.new_std_value NEW_VAL,
    c.OBJ_NAME, c.STATUS
from siebel.S_REPOSITORY r -- repository
  join siebel.S_WORKSPACE w on r.ROW_ID = w.REPOSITORY_ID -- baseline workspace
  join siebel.S_WS_VERSION v on w.ROW_ID = v.WS_ID and r.ROW_ID = v.REPOSITORY_ID -- versions of baseline workspace
  join siebel.S_WS_MERGE_LOG m on m.RESULT_WS_VER_ID = v.ROW_ID -- workspace merge info
  join siebel.S_WS_VERSION dv on dv.ROW_ID = m.FROM_WS_VER_ID -- versions of delivered workspace
  join siebel.S_WORKSPACE dw on dw.ROW_ID = dv.WS_ID -- delivered workspace (child branches)
  join siebel.S_CONTACT u on u.ROW_ID = dw.CREATED_BY -- ws author
  left join siebel.S_WS_MG_LOGOBJ c on c.WS_MERGE_LOG_ID = m.row_id -- modified child objects3445rg
  left join siebel.S_WS_MG_LOGATTR a on a.ws_merge_log_id = m.row_id and a.ws_mg_logobj_id = c.row_id -- modified attributes
where r.name = 'Siebel Repository'
  and w.NAME = 'int_dev' --filter by parent/destination/baseline workspace name
  --and dw.name like 'dev_vbabkin_d412_1' -- by delivered workspace name
  and c.TOP_PARENT_TYPE = 'Business Component' and c.TOP_PARENT_NAME = 'Account' -- by top level object
  --and c.obj_type='Business Component User Prop' and c.object_name like 'Named Method%' -- by child object (could be same as top if you are looking for attrib changes)
  --and a.attribute = 'Inactive' and a.new_std_value = 'Y' -- by attribute
order by v.VERSION_NUM desc, c.TOP_PARENT_NAME, c.object_name;

Unlike the first SQL it doesn't give you info about in-progress workspaces.


Workspace delivery in progress


Finally, as a bonus below is a SQL to check if there is a workspace delivery in progress. Run it before delivering your workspace to avoid annoying "Another workspace delivery/rebase is in-progress. Please try after sometime." error with a failed workspace to be resubmitted.

DECLARE
    wsname varchar(30) := 'int_dev'; -- baseline/parent workspace where you delivering to
BEGIN
    SELECT name into wsname FROM siebel.S_WORKSPACE w WHERE w.name = wsname FOR UPDATE NOWAIT;
    ROLLBACK;
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE = -54 THEN
            raise_application_error(-20001, 'Another delivery in progress'); 
        ELSE
            ROLLBACK;
        END IF;
END;

To be continued...

In this article, we'll do a scriptless PreSetFieldValue validation as an alternative solution to field-level validation. Clearly, PreSetFieldValue is not always the best fit for user input validations. But when you've been given no choice, here is a declarative and scalable solution to consider.


Background


We'll be using a well-known stack: RunTime Events (RTE) + Data Validation Manager (DVM). And I'll also show you some tricks to work around the limitations.

The first challenge is to fetch a new field value. As you know at the PreSetFieldValue event BusComp holds an original value. Here is a couple of Siebel Support articles explaining the problem:

The solution is in the implicit parameter that runtime event passes by to a business service. And this parameter is [PreSetFieldValue]. Separate thanks to Jason for the hint!

Parameter Description
Object Name The name of the object experiencing the event
Event Type The type of object (BusComp, Applet, Application)
ActionSet The name of the action set
EventId The ROW_ID of the event
Sub Event The content of the Sub Event field (method, field, view name)
Action The name of the action
Event Name The name of the event such as PreWriteRecord, InvokeMethod etc
Context The content of the Business Service Context field. Alex has a great article on how to take an advantage of it.
Business Component Name An active BucComp name. Only available when [Object Type] = "Applet" or "BusComp"
PreSetFieldValue A new field value. Only available when [Event] = "PreSetFieldValue"
There are probably other event-specific parameters out there

Next problem is that the DVM business service can't handle implicit RTE parameters. I presume since DVM business service handles the Context parameter natively, it probably replaces the implicit inputs PropertySet with the new one constructed from the Context parameter.

Anyway, it shouldn't stop us. We can call another BS that accepts RTE parameters and converts them into profile attributes(PA) and then we can use the PA in the DVM rule. Feel free to create your generic BS which converts input parameter(s) into profile attribute(s) or use [User Registration] business service.


Case study


Imagine you've been asked to ensure [Account.Close Reason] is provided when you set [Account.Status] = 'Closed' and presumably locking the record.

So, let's start with creating a Runtime ActionSet "Account Close Reason Validation" with two actions as below:

Sequence Action Type Business Service Name Business Service Method Business Service Context
1 BusService User Registration SetProfileAttr  
2 BusService Data Validation Manager Validate RuleSet,Account Close Reason Validation

Now we can create a Runtime Event:

Object Type Object Name Event Subevent Action Set Name
BusComp Account PreSetFieldValue Status Account Close Reason Validation

And finally a DVM RuleSet with one rule:

Name Stop On Error Immediate Display Rule Expression
Account Close Reason Validation Y Y GetProfileAttr("PreSetFieldValue") <> "Closed" OR [Close Reason] IS NOT NULL

Done! Another scriptless solution in your toolbox!

P.S.: Beware that GetPofileAttr function always returns string. To retrieve other data types, please use type specific functions.

If you are not quite satisfied with out-of-the-box [Inspect Workspace UI], in this article I offer you to build your own UI or to check out my version.

As usual, the front-end going to be based on a JQuery dialog and compiled into a bookmarklet while the back-end is another method in the [FWK Runtime] business service (BS).


Back-end


The idea is simple: to find a workspace record in [Repository Workspace] BusComp by name and run OpenWS and PreviewWS methods.

Here is how your BS method might look like:

function InspectWS(Inputs, Outputs) {
    var name = Inputs.GetProperty("Name");
    var bo = TheApplication().GetBusObject("Workspace");
    var bc = bo.GetBusComp("Repository Workspace");
    try {
        bc.SetSearchExpr('[Name] = "' + name + '"');
        bc.SetViewMode(AllView);
        bc.ExecuteQuery(ForwardBackward);
        if (bc.FirstRecord()) {
            bc.InvokeMethod("OpenWS");
            bc.InvokeMethod("PreviewWS");
        } else {
            throw "Workspace name not found: " + name;
        }
    } catch (e) {
        throw e;
    } finally {
        bc = null;
        bo = null;
    }
}

Don't forget to publish your BS through [ClientBusinessService] application user property and probably make it a client-side business service, so you won't have a problem with the upstream migration.


Front-end


Here is a simplified dialog with a text field where you paste a workspace name and a button to run a BS method:

(() => {
    if ("undefined" === typeof SiebelApp) {
        alert("It works only in Siebel OUI session!");
        return;
    }
    const func = "SiebelInspectWS";
    const html = `<div title="Inspect Workspace"><input type="text" id = "${func}" style="width:100%"></div>`;
    const $d = $(html).dialog({
        modal: true,
        width: 640,
        buttons: [{
            text: 'Inspect',
            click: () => {
                const service = SiebelApp.S_App.GetService("FWK Runtime");
                let ps = SiebelApp.S_App.NewPropertySet();
                ps.SetProperty("Name", $('#' + func).val());
                let config = {
                    async: false,
                    cb: function (methodName, inputSet, outputSet) {
                        if (outputSet.GetProperty("Status") == "Error") {
                            sRes = outputSet.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg");
                        }
                        alert(sRes || "Done!");
                    }
                };
                service.InvokeMethod("InspectWS", ps, config);
            }
        }, {
            text: 'Close (Esc)',
            click: () => $(this).dialog('close')
        }]
    });
})();

To open the UI just run the above snippet from the browser console, snippet, file or compile it into a bookmarklet. Make sure you transform/compile it before using in old browsers like IE.


My implementation


I've advanced the initial version of Inspect Workspace UI with quite a few features, so check out my latest implementation below.

Features:

  • The text field accepts multiple formats:
    • an exact workspace name: vbabkin_20200924_d419_1
    • a search pattern of workspace name: *d419*
    • a full search specification for the Repository Workspace BC: [Parent Name] = "Release 21" AND [Created By] = LoginId()
    • leave it empty to search or inspect most recent undelivered workspaces created by the active user
  • Hit Enter or click [Search button] to search for 10 most recent workspaces matching the provided name/pattern/spec.
  • Click one of the workspaces link in the search result list to inspect the workspace.
  • Hit Ctrl+Enter or click [Inspect] button to inspect the most recent workspaces matching the provided name/pattern/spec.
  • Run the snippet twice or double-click the bookmark to inspect your latest undelivered workspace.
  • The text field remembers a history of 5 recent calls.
  • It highlights searching pattern in result list.
  • It prints calls, errors and success messages with a timestamp.
  • Click [Help] button to toggle help instructions.
  • To close the dialog:
    • click the [X] or [Close] button
    • hit the [Escape] key
    • click outside the dialog
  • Right-click a workspace link to copy it.

Source code (v1.5):

  • Business service: File
  • Front-end: Snippet | File
  • /* 
    @desc Inspect Workspace UI
    @author VB(xapuk.com)
    @version 1.5 2020/11/08
    @requires "FWK Runtime" business service to be published (Application ClientBusinessService usep property)
    @features
        +elements: help text hidden by default, input field with the history, message bar, 3 buttons
        +don't accept value shorter then 3 chars excluding *
        +async call with busy overlay
        +highlight search pattern in all found results
        +shows a text if number of results = limit
        +cut history to 5 items and don't store empty requests
        +insect ws on click of <a>
        +close on right-click of whitespace
        +change a default ws filter to only filter out Delivered WSs
        +copy ws name on right-click of link
        +make a ws name in a sucess message a link
        +put a timestamp in the message
        +fix contextmenu on text input
        +before opening a dialog check if it exists, and if so run auto inspect
        +clicking a ws name inspects the first in the list
        +dialog should have a unique selector itself so we don't mess with other dialogs
        +print a message before server call, like inspecting ws blabla, or searching for workspace blabla
        +use a function to print the message, keep a history of 3 messages
        +close when click outside
        +make it work and test in IE/Edge/Firefox
        +ES6 => Babel => ES5 => Bookmarklet
    @Fixed in 1.2:
        +print placeholder text on empty call
        +don't highlight search specs
        +clear results before next search
        +fix char limit error
        +fix hightlight
        +print user name instead of "my"
    @Fixed in 1.3:
        +placeholder text color
        +<> in placeholder text
        +when searching for exact ws name, shouldn't highlight it
        +link click doesn't work if clicked on highlighted part (<b>)
        +don't close on whitespace click
    @Fixed in 1.4
        +change to the layout
        +fixed empty call problem
        +instruction changes
    @fixed in 1.5
        +remove "workspace" from messages so inspecting latest workspace wording make sence
        +search results shouldn't be empty - when inspecting should spool a ws name, while search/inspect in progress put ...
        +more instructions
    */
    
    (() => {
        if ("undefined" === typeof SiebelApp) {
            alert("It works only in Siebel OUI session!");
            return;
        }
        // snippet id
        const func = "SiebelInspectWS";
        // selector preffix
        const id = "#" + func;
        const cls = "." + func;
        // max number of output records
        const iLimit = 10;
        // history of recent calls
        let aHistory = JSON.parse(window.localStorage[func] || "[]");
        // messages
        let aMsg = [];
        // double click of bookmarklet
        if ($("." + func).length) {
            $("." + func).find(id + "IBtn").click();
            return;
        }
    
        const placeholder = `${SiebelApp.S_App.GetUserName()||"my"} recent undelivered workspace`;
    
        const help = `<i><p>Welcome to Inspect Workspace UI</p>
        Text field accepts several formats:<br>
        <ul><li> - an exact workspace name: vbabkin_20200924_d419_1</li>
        <li> - a search pattern of workspace name: *d419*</li>
        <li> - an exact search spec for Repository Workspace BC: [Parent Name] = "Release 21" AND [Created By] = LoginId()</li>
        <li> - leave it empty to search / inspect most recent undelivered workspaces created by active user</li></ul>
        <p>Hit Enter to search for 10 most recent workspaces matching the provided name/pattern/spec and then click one of the workspaces in the list to inspect it.</p>
        <p>Hit Ctrl+Enter to inspect the most recent workspaces matching the provided name/pattern/spec.</p>
        <p>If you want to inspect/re-inspect your recent undelivered workspace, just hit Ctrl+Enter upon opening a dialog or double click a bookmark link.</p>
        <p>Right click on workspace name to copy it.</p>
        <p>Click anywhere outside of the dialog to close it.</p>
        <p>Check out <a href="http://xapuk.com/index.php?topic=125" target="_blank">http://xapuk.com/</a> for details.</p></i>`;
    
        const html = `<div title="Inspect workspace">
                <span id = "${func}Help" style = "display:none">${help}</span>
                <input placeholder = "<${placeholder}>" type="text" id = "${func}" list="${func}History" autocomplete="off">
                <ul id="${func}List">Provide a search criteria above and run [Search] to see a list of available workspaces<br>and/or run [Inspect] directly to inspect the most recent workspace matching the criteria</ul>
                <p id = "${func}Msg"></p>
                <datalist id = "${func}History"></datalist>
                <style>
                    .${func} input {
                        width: 100%!Important;
                        margin-bottom: 10px;
                    }
                    #${func}::placeholder {
                        color: lightslategrey;
                    }
                    #${func}List {
                        margin-left: 15px;
                    }
                    #${func}Help i {
                        font-size: 0.9rem;
                    }
                    .${func} li {
                        list-style-type: disc;
                        margin-left: 30px;
                    }
                    #${func}Msg {
                        border-top: 1px solid lightslategrey;
                        padding-top: 5px;
                    }
                </style>
            </div>`;
    
        const $d = $(html).dialog({
            modal: true,
            width: 640,
            classes: {
                "ui-dialog": func
            },
            buttons: [{
                text: 'Search (Enter)',
                click: () => Run(false)
            }, {
                id: func + "IBtn",
                text: 'Inspect (Ctrl+Enter)',
                click: () => Run(true)
            }, {
                text: 'Help',
                click: () => $d.find(id + "Help").toggle()
            }, {
                text: 'Close (Esc)',
                click: () => $d.dialog('close')
            }],
            open: function () {
                const $this = $(this);
                // autofocus
                $this.find('#' + func).focus();
                // key bindings
                $this.parent(".ui-dialog").contextmenu(function (e) {
                    const scope = e.target;
                    if (scope.nodeName === "A") {
                        // copy value on right-click of link
                        e.stopPropagation();
                        e.preventDefault();
                        // replace link with an input
                        $(scope).hide().after(`<input id='${func}Copy'>`);
                        $d.find(id + "Copy").val($(scope).text()).select();
                        // attempt to copy value
                        if (document.execCommand("copy", false, null)) {
                            // if copied, display a message for a second
                            $d.find(id + "Copy").attr("disabled", "disabled").css("color", "red").val("Copied!");
                            setTimeout(() => {
                                $d.find(id + "Copy").remove();
                                $(scope).show();
                            }, 700);
                        } else {
                            // if failed to copy, keep input element until blur, so it can be copied manually
                            $d.find(id + "Copy").blur(() => {
                                $(this).remove();
                                $d.find("a").show();
                            });
                        }
                    }
                }).click((e) => {
                    var a = $(e.target).closest("a");
                    if (a.length && a.closest(id + "List").length) {
                        Run(true, a.text());
                    }
                }).find(id).keydown((event) => {
                    if (event.keyCode === 13) {
                        Run(event.ctrlKey);
                    }
                });
                // close dialog when click outside
                $('.ui-widget-overlay').click(() => $d.dialog("close"));
                // render history
                aHistory.forEach((i) => $this.find(id + "History").append(`<option>${i}</option>`));
            },
            close: () => {
                $d.dialog('destroy').remove();
            }
        });
    
        function Run(bInspect, inpname) {
            const name = inpname ? inpname : $('#' + func).val();
            // don't accept specs shorter then 3 chars
            if (name && name.replace(/\*/gm, "").length < 3) {
                printMsg(`Value should be longer then 3 characters! ${name}`);
                return;
            }
            //clean up results before search
            if (!bInspect) {
                $d.find(id + "List").empty();
            }
            // save last query
            if (name) {
                if (aHistory.indexOf(name) > -1) {
                    aHistory.splice(name, 1);
                }
                aHistory.unshift(name);
                // limit history stack volume to 5
                if (aHistory.length > 5) {
                    aHistory.pop();
                }
                window.localStorage[func] = JSON.stringify(aHistory);
            }
            // invoke BS
            const service = SiebelApp.S_App.GetService("FWK Runtime");
            let ps = SiebelApp.S_App.NewPropertySet();
            ps.SetProperty("Name", name);
            ps.SetProperty("Inspect", bInspect ? "Y" : "N");
            ps.SetProperty("Limit", iLimit);
            let config = {
                async: true,
                scope: this,
                mask: true,
                cb: function (methodName, inputSet, outputSet) {
                    if (outputSet.GetProperty("Status") == "Error") {
                        sRes = outputSet.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg");
                    } else {
                        let psRS = outputSet.GetChildByType("ResultSet");
                        if (psRS) {
                            sRes = psRS.GetProperty("Result");
                            sWorkspaces = psRS.GetProperty("Workspaces");
                            if (!sRes) {
                                if (bInspect) {
                                    sRes = `Workspace <b><a href='#'>${sWorkspaces||"?"}</a></b> inspected successfully!`;
                                } else {
                                    if (sWorkspaces) {
                                        // print a list of workspaces
                                        $d.find(id + "List").empty();
                                        let aWorkspaces = sWorkspaces.split(",");
                                        aWorkspaces.forEach((w) => $d.find(id + "List").append(`<li><a href='#'>${highlightText(name, w)}</a></li>`));
                                        if (aWorkspaces.length == iLimit) {
                                            $d.find(id + "List").append(`<p><i>${iLimit} most recent workspaces are shown.</i></p>`);
                                        }
                                    }
                                }
                            } else if (sRes.indexOf("No workspace found") > -1) {
                                $d.find(id + "List").html(`Workspace not found, please provide a valid search criteria and run [Search] again...`);
                            }
                        }
                    }
                    if (sRes) {
                        printMsg(sRes);
                    }
                }
            };
            printMsg(`${bInspect?'Inspecting':'Searching for'} ${name||placeholder}`);
            service.InvokeMethod("InspectWS", ps, config);
        }
    
        function highlightText(pattern, value) {
            if (pattern && value && !pattern.match(/\[.*\]/gm) && pattern.replace(/\*/gm, "").length < value.length) {
                const patterns = pattern.split("*");
                let i, lastIndex = -1;
                value = patterns.reduce((res, p) => {
                    let i = res.indexOf(p, lastIndex);
                    if (p && i > -1) {
                        res = `${res.substr(0, i)}<b>${p}</b>${res.substr(i + p.length)}`;
                        lastIndex = i;
                    }
                    return res;
                }, value);
            }
            return value;
        }
    
        function printMsg(txt) {
            txt = (new Date).toLocaleTimeString() + ' >> ' + txt;
            // limit a message stack to 3 items
            aMsg.push(txt);
            if (aMsg.length > 3) {
                aMsg.shift();
            }
            $d.find(id + "Msg").html(aMsg.join("<br>"));
        }
    })();
  • Bookmarklet code: Snippet
  • javascript:(function(){function e(e,a){var c=a?a:$("#"+n).val();if(c&&c.replace(/\*/gm,"").length<3)return void i("Value should be longer then 3 characters! "+c);e||d.find(r+"List").empty(),c&&(s.indexOf(c)>-1&&s.splice(c,1),s.unshift(c),s.length>5&&s.pop(),window.localStorage[n]=JSON.stringify(s));var l=SiebelApp.S_App.GetService("FWK Runtime"),u=SiebelApp.S_App.NewPropertySet();u.SetProperty("Name",c),u.SetProperty("Inspect",e?"Y":"N"),u.SetProperty("Limit",o);var f={async:!0,scope:this,mask:!0,cb:function(n,s,a){if("Error"==a.GetProperty("Status"))sRes=a.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg");else{var p=a.GetChildByType("ResultSet");if(p)if(sRes=p.GetProperty("Result"),sWorkspaces=p.GetProperty("Workspaces"),sRes)sRes.indexOf("No workspace found")>-1&&d.find(r+"List").html("Workspace not found, please provide a valid search criteria and run [Search] again...");else if(e)sRes="Workspace <b><a href='#'>"+(sWorkspaces||"?")+"</a></b> inspected successfully!";else if(sWorkspaces){d.find(r+"List").empty();var l=sWorkspaces.split(",");l.forEach(function(e){return d.find(r+"List").append("<li><a href='#'>"+t(c,e)+"</a></li>")}),l.length==o&&d.find(r+"List").append("<p><i>"+o+" most recent workspaces are shown.</i></p>")}}sRes&&i(sRes)}};i((e?"Inspecting":"Searching for")+" "+(c||p)),l.InvokeMethod("InspectWS",u,f)}function t(e,t){return e&&t&&!e.match(/\[.*\]/gm)&&e.replace(/\*/gm,"").length<t.length&&function(){var i=e.split("*"),n=-1;t=i.reduce(function(e,t){var i=e.indexOf(t,n);return t&&i>-1&&(e=e.substr(0,i)+"<b>"+t+"</b>"+e.substr(i+t.length),n=i),e},t)}(),t}function i(e){e=(new Date).toLocaleTimeString()+" >> "+e,a.push(e),a.length>3&&a.shift(),d.find(r+"Msg").html(a.join("<br>"))}if("undefined"==typeof SiebelApp)return void alert("It works only in Siebel OUI session!");var n="SiebelInspectWS",r="#"+n,o=10,s=JSON.parse(window.localStorage[n]||"[]"),a=[];if($("."+n).length)return void $("."+n).find(r+"IBtn").click();var p=(SiebelApp.S_App.GetUserName()||"my")+" recent undelivered workspace",c='<i><p>Welcome to Inspect Workspace UI</p>Text field accepts several formats:<br><ul><li> - an exact workspace name: vbabkin_20200924_d419_1</li><li> - a search pattern of workspace name: *d419*</li><li> - an exact search spec for Repository Workspace BC: [Parent Name] = "Release 21" AND [Created By] = LoginId()</li><li> - leave it empty to search / inspect most recent undelivered workspaces created by active user</li></ul><p>Hit Enter to search for 10 most recent workspaces matching the provided name/pattern/spec and then click one of the workspaces in the list to inspect it.</p><p>Hit Ctrl+Enter to inspect the most recent workspaces matching the provided name/pattern/spec.</p><p>If you want to inspect/re-inspect your recent undelivered workspace, just hit Ctrl+Enter upon opening a dialog or double click a bookmark link.</p><p>Right click on workspace name to copy it.</p><p>Click anywhere outside of the dialog to close it.</p><p>Check out <a href="http://xapuk.com/index.php?topic=125" target="_blank">http://xapuk.com/</a> for details.</p></i>',l='<div title="Inspect workspace"><span id = "'+n+'Help" style = "display:none">'+c+'</span><input placeholder = "<'+p+'>" type="text" id = "'+n+'" list="'+n+'History" autocomplete="off"><ul id="'+n+'List">Provide a search criteria above and run [Search] to see a list of available workspaces<br>and/or run [Inspect] directly to inspect the most recent workspace matching the criteria</ul><p id = "'+n+'Msg"></p><datalist id = "'+n+'History"></datalist><style>.'+n+" input {width: 100%!Important;margin-bottom: 10px;}#"+n+"::placeholder {color: lightslategrey;}#"+n+"List {margin-left: 15px;}#"+n+"Help i {font-size: 0.9rem;}."+n+" li {list-style-type: disc;margin-left: 30px;}#"+n+"Msg {border-top: 1px solid lightslategrey;padding-top: 5px;}</style></div>",d=$(l).dialog({modal:!0,width:640,classes:{"ui-dialog":n},buttons:[{text:"Search (Enter)",click:function(){return e(!1)}},{id:n+"IBtn",text:"Inspect (Ctrl+Enter)",click:function(){return e(!0)}},{text:"Help",click:function(){return d.find(r+"Help").toggle()}},{text:"Close (Esc)",click:function(){return d.dialog("close")}}],open:function(){var t=$(this);t.find("#"+n).focus(),t.parent(".ui-dialog").contextmenu(function(e){var t=this,i=e.target;"A"===i.nodeName&&(e.stopPropagation(),e.preventDefault(),$(i).hide().after("<input id='"+n+"Copy'>"),d.find(r+"Copy").val($(i).text()).select(),document.execCommand("copy",!1,null)?(d.find(r+"Copy").attr("disabled","disabled").css("color","red").val("Copied!"),setTimeout(function(){d.find(r+"Copy").remove(),$(i).show()},700)):d.find(r+"Copy").blur(function(){$(t).remove(),d.find("a").show()}))}).click(function(t){var i=$(t.target).closest("a");i.length&&i.closest(r+"List").length&&e(!0,i.text())}).find(r).keydown(function(t){13===t.keyCode&&e(t.ctrlKey)}),$(".ui-widget-overlay").click(function(){return d.dialog("close")}),s.forEach(function(e){return t.find(r+"History").append("<option>"+e+"</option>")})},close:function(){d.dialog("destroy").remove()}})})();
  • Bookmarklet link: InspectWS
  • Version history is available in GitHub

Thanks to Manan for his contribution!

You never know when you might need a list of NamedSearches Siebel uses internally.
So, I thought to put it here along with some info where I've found it and some tips on how I usually use it. As you might've already guessed the main use of the list is to check you are not using these names with SetNamedSearch method in the production environment.

Name Description
System Search BusComp search specification
Applet Search Spec Named Search Applet search specification
VisibilitySearch View mode
Link Search Spec Named Search Link search specification
PickListSearch PickList search specification
Drill Down By Id Named Search After you drilled down into a view
Task UI Search Spec Named Search When using a [Task Step Context] tab in the Task UI steps
Content Targeting Named Search Personalization rules
Sort Search Optimization Sometimes sort spec performance can be optimised with search spec
Private Filter Search From BusComp User Prop = Private Activity Search Spec
Active Field Flag Search If you are using active flag property on BusComp
MVG Type Field Named Search When using Type field and value filter on MVG
Bookmark Id Named Search When you get to the view through a bookmark
Auxiliary Id Named Search ?
Snapshot ?
Override Filter Search ?
Link Spec Substitute ?

This comes handy when using eScript playground. For example, this is how you test an applet search spec without a compilation:

var bo = TheApplication().ActiveBusObject();
var bc = bo.GetBusComp("Account");
bc.SetNamedSearch("Applet Search Spec Named Search", '[Updated] > Today()-10');
bc.ExecuteQuery(ForwardBackward);

And below eScript snippet tells you all filters applied to an active BusComp if you don't feel like reading logs:

var bo = TheApplication().ActiveBusObject();
var bc = bo.GetBusComp("Account");
var aSpecs = [
    "System Search",
    "Applet Search Spec Named Search",
    "VisibilitySearch",
    "Link Search Spec Named Search",
    "PickListSearch",
    "Drill Down By Id Named Search",
    "Task UI Search Spec Named Search",
    "Content Targeting Named Search",
    "Sort Search Optimization",
    "Active Field Flag Search",
    "MVG Type Field Named Search",
    "Auxiliary Id Named Search",
    "Snapshot",
    "Bookmark Id Named Search"
];
if (bc.GetSearchExpr()) {
    log("User Search Spec", bc.GetSearchExpr());
}
for (var i in aSpecs) {
    var name = aSpecs[i];
    var spec = bc.GetNamedSearch(name);
    if (spec) {
        log(name, spec);
    }
}

Stay tuned and take care, folks!

In this one, I want to share a script that helps me to deliver Siebel workspaces. This time I'll start with the source code and then I'll tell you how to use it.


Source code


  • Parameters.vbs ( Download | Snippet ) - file with all parameters.
  • Const sODBC = "SBL DEV Server DSN"      ' ODBC32 name should be using OraClient. When using "Siebel Oracle" driver there'll be an annoying copyright message.
    Const sUsername = "approver"            ' DB user name
    Const sPassword = "pass123"             ' DB password
    Const sTools = """C:\Siebel\Tools\BIN\siebdev.exe"" /c ""C:\Siebel\Tools\bin\enu\tools_dev.cfg"" /d ServerDataSrc"	'Siebel tools path along with some params, be careful with quote escaping
    Const sLog = "C:\Siebel\Tools\LOG"      ' Logs directory if you want it to be opened delivery failed
    Const sDefWS = "dev_vbabkin_*"          ' Default workspace name prefix to ease typing
    Const sWSComment = "Delivered by VB"    ' Delivery comment - usually your name/username if using a common system user for delivery 
  • WSDelivery.wsf ( Download | Snippet ) - file to run on 32-bit system.
  • <job id = "WSDelivery">
       <script language="VBScript" src="Parameters.vbs"/>
       <script language="VBScript">
    '@desc Siebel workspaces delivery "automation"
    '@version 1.7 (20190914)
    '@author VB (http://xapuk.com)
    '@example For the sake of using x32 ODBC drivers, should be ran through x32 bit version of VBS engine ...windows\sysWOW64\cscript
    
    Dim sSQL1, sSQL2, sSQL3
    Dim sConnection, dbConnection, snpData
    Dim objShell
    Dim sWS
    Dim sId
    Dim sMsg
    
    Sub Deliver
    
    	Set objShell = WScript.CreateObject("WScript.Shell")
    	
    	' create objects first, if failed , there is no point in moving forward
    	Set snpData = CreateObject("ADODB.Recordset")
    	Set dbConnection = CreateObject("ADODB.Connection")
    	Set objShell = WScript.CreateObject("WScript.Shell")
    
    	' establish DB connection
    	sConnection = "Data Source=" & sODBC & "; User ID=" & sUsername & "; Password=" & sPassword & ";"
    	Err.Clear
    	On Error Resume Next
    	dbConnection.ConnectionString = sConnection
    	dbConnection.Open
    
    	' to handle DB connection errors
    	If Err.Number <> 0 Then
    		sMsg = Err.Description & chr(10) & chr(10) & sConnection & chr(10) & chr(10) & "Please open Parameters.vbs file to change connection parameters."
    		MsgBox sMsg, 0, "Siebel workspace delivery"
    	Else
    		On Error GoTo 0
    
    		' Input WS name
    		sWS = LCase(InputBox("Please enter a WS name", "Siebel workspace delivery", sDefWS))
    
    		If sWS > "" Then
    		
    			' if WS name has * search with like and only Submitted For Delivery
    			If InStr(1, sWS, "*", 1) > 0 Then
    			
    				sSQL2 = "select name, row_id from siebel.S_WORKSPACE where name like '" & Replace(sWS, "*", "%")  & "' and status_cd = 'Submitted for Delivery'"
    				snpData.Open sSQL2, dbConnection
    				If Not(snpData.EOF) Then
    					sId = snpData("row_id")
    					sWS = snpData("name") ' Get a full name of the workspace
    					sSQL1 = "select row_id, status_cd from siebel.S_WORKSPACE where name = '" & sWS & "'"
    					snpData.MoveNext() ' Check if there are more than one match
    					If Not(snpData.EOF) Then
    						MsgBox "Multiple records found. Please specify the query.", 0, sWS
    						snpData.Close
    						Exit Sub
    					End If
    				End If
    				snpData.Close
    			
    			Else ' otherwise look for exact name match
    
    				sSQL1 = "select row_id, status_cd from siebel.S_WORKSPACE where name = '" & sWS & "'"
    				snpData.Open sSQL1, dbConnection
    
    				' verify the WS
    				If Not(snpData.EOF) Then
    					sStatus = snpData("status_cd")
    					sId = snpData("row_id")
    					snpData.Close
    					If sStatus <> "Submitted for Delivery" Then
    						MsgBox "Wrong status (" + sStatus + ")", 0, sWS
    						Exit Sub
    					End If
    				End If
    			End If
    			
    			' if WS is found, delivering
    			If sId > "" Then
    				
    				' this is a basic SQL with a list of objects in WS. Feel free to enchance it with information of importance
    				'sSQL3 = "select v.VERSION_NUM, o.OBJ_TYPE, o.OBJ_NAME from siebel.S_REPOSITORY r join siebel.S_WORKSPACE w on r.ROW_ID = w.REPOSITORY_ID join siebel.S_WS_VERSION v on w.ROW_ID = v.WS_ID and r.ROW_ID = v.REPOSITORY_ID join siebel.S_RTC_MOD_OBJ o on o.WS_VER_ID = v.ROW_ID and r.ROW_ID = o.REPOSITORY_ID where r.name = 'Siebel Repository' and w.row_id = '" & sId & "' order by 1,2,3;"
    				
    				' I'm only curious about scripts :)
    				sSQL3 = "select v.VERSION_NUM, o.OBJ_TYPE, o.OBJ_NAME, GREATEST(nvl(max(s1.name),'-'), nvl(max(s2.name),'-'), nvl(max(s3.name),'-')) SCRIPT from siebel.S_REPOSITORY r join siebel.S_WORKSPACE w on r.ROW_ID = w.REPOSITORY_ID join siebel.S_WS_VERSION v on w.ROW_ID = v.WS_ID and r.ROW_ID = v.REPOSITORY_ID join siebel.S_RTC_MOD_OBJ o on o.WS_VER_ID = v.ROW_ID and r.ROW_ID = o.REPOSITORY_ID left join siebel.S_APPLICATION o1 on o1.name = o.OBJ_NAME and r.row_id = o1.REPOSITORY_ID and o.OBJ_TYPE = 'Application' left join siebel.S_APPL_SCRIPT s1 on s1.APPLICATION_ID = o1.ROW_ID and s1.WS_ID = w.row_id left join siebel.S_APPLET o2 on o2.name = o.OBJ_NAME and r.row_id = o2.REPOSITORY_ID and o.OBJ_TYPE = 'Applet' left join siebel.S_APPL_WEBSCRPT s2 on s2.APPLET_ID = o2.row_id and s2.WS_ID = w.row_id left join siebel.S_BUSCOMP o3 on o3.name = o.OBJ_NAME and r.row_id = o3.REPOSITORY_ID and o.OBJ_TYPE = 'Business Component' left join siebel.S_BUSCOMP_SCRIPT s3 on s3.BUSCOMP_ID = o3.row_id and s3.WS_ID = w.row_id where r.name = 'Siebel Repository' and w.row_id = '" & sId & "' group by v.VERSION_NUM, o.OBJ_TYPE, o.OBJ_NAME order by 1, 2, 3;"
    				
    				snpData.Open sSQL3, dbConnection
    				
    				'fetching workspace content
    				sMsg = "Object modified in the WS:"
    				Do While Not(snpData.EOF)
    					sMsg = sMsg & chr(10) & snpData("VERSION_NUM") & " / " & snpData("OBJ_TYPE") & " = " & snpData("OBJ_NAME")
    					If snpData("SCRIPT") > "-" Then ' special alarm if there is a new applet, application or BS script
    						sMsg = sMsg & " / <<<<<[SCRIPT]>>>>>"
    					End If
    					snpData.MoveNext
    				Loop
    				snpData.Close
    				
    				' Are you sure?
    				sMsg = sMsg + chr(10)+ chr(10) + "Are you sure you want to deliver the WS?"
    				If MsgBox(sMsg, 1, sWS) = 1 Then
    					' DELIVER
    					sMsg = sTools + " /u " & sUsername & " /p " & sPassword & " /Deliverworkspace " & sWS & " """ & sWSComment & ""
    					objShell.Run sMsg, 0, true
    					' check if delivery was successful
    					snpData.Open sSQL1, dbConnection
    					If Not(snpData.EOF) Then
    						sStatus = snpData("status_cd")
    						If sStatus = "Delivered" Then
    							MsgBox "WS is delivered!", 0, sWS	
    						Else
    							' If failed open log folder
    							If sLog > "" Then
    								sMsg = """C:\Windows\explorer.exe"" """ & sLog & """"
    								objShell.Run sMsg, 8, true
    							End If
    							MsgBox "Not delivered with status = " & sStatus & chr(10) & "Please, check a log file.", 0, sWS	
    						End If
    					Else
    						MsgBox "WS not found", 0, sWS			
    					End If
    				End If
    			Else
    				MsgBox "WS not found", 0, sWS
    			End If
    		End If
    		dbConnection.Close
    	End If
    
    	Set snpData = Nothing
    	Set dbConnection = Nothing
    	Set objShell = Nothing
    
    End Sub
    </script>
    <script language="VBScript">
    Call Deliver
    </script>
    </job> 
  • WSDelivery.bat ( Download | Snippet ) - file to run on 64-bit system.
  • ECHO OFF
    REM The only purpose of that shell script is to run VBScript in 32bit mode, same as ODBC driver
    REM If you are using ODBC64 data source, simply run the .vbs file itself
    time /t
    C:\windows\sysWOW64\cscript WSDelivery.wsf
    time /t
    timeout /t 5 
  • WSDelivery.zip ( Download ) - all at once in an archive.

How to use


Download all files and put them into the same folder. Open "Parameters.vbs" and change each parameter according to your environment.

If you are using 32-bit ODBC driver on 64-bit OS run the script through "WSDelivery.bat", otherwise just run "WSDelivery.wsf" directly.

Step 1.

Here you put the name of the workspace you want to be delivered.

There are 3 scenarios I've tuned the script for:

  • Delivering your own WS - just click Ok.
  • Delivering someone else's WS - copy & paste the full name.
  • If you are not certain about the WS name, use an asterisk (*) around the parts you are certain about. For example, to deliver John's WS with user story 123 type in "dev_john*us123*".
  • Delivering a WS when you don't know the exact name - use an asterisk (*). For example, to deliver John's WS with user story 123 type in "dev_john*us123*".

If the script finds more then one match or if the WS is in the wrong status, it will tell you that.

Step 2.

At this stage, you will be able to check the details of the workspace.

First, make sure the name in the title is correct.

Then, go through the list of changed objects. If objects require a detailed look, you can always Cancel.

Step 3.

Script will run Siebel Tools in WSDelivery mode and will wait for it to complete.

Once delivery is completed it will tell you if it was success or failure.

I've tried my best to make the script straight forward. Feel free to adjust it to your needs.

I hope you'll find the script or at least the idea useful. In my case, it saves me a couple of weeks annually.

If "Siebel Workflow Process" (WF) is your primary tool for business process automation as they are for me, it might be a useful topic for you.

Familiar with the error "The value entered in field Process Business Object of buscomp Workflow Process Deployment does not match any value in the bounded pick list FOW SRF Business Object." (SBL-DAT-00225) when activating a custom BO-based workflow process in Siebel Tools? The usual way to solve it is to activate a process through [Administration - Business Process] screen. It is still annoying, as you will lose your current context, and extremely annoying when you don't have BP screen or access to it in the test application. In this topic, I offer you another, effortless way to activate a WF process. On the same view/session where you are testing it, and with just a couple clicks.

Ok, let's get down to implementation.

1. Publishing the service.

As you might have guessed, we will be using the "Workflow Admin Service". Out of many ways to run the BS, I'll cover OUI scripts.

So, let's make the BS accessible from a browser script. Here, I recommend using a service runner - straight forward concept when you publish one to run any other service in the system. If you are not comfortable with granting access to all services in production, use a client-side business service for service runner. So, it wouldn't be automatically migrated to higher environments along with a repository.

Here is how your service runner method can look like:

function InvokeServiceMethod(Inputs, Outputs) {
    var bs;
    var sBS = Inputs.GetProperty("Service");
    Inputs.RemoveProperty("Service");
    var sMethod = Inputs.GetProperty("Method");
    Inputs.RemoveProperty("Method");
    
    try {
        bs = TheApplication().GetService(sBS);
        bs.InvokeMethod(sMethod, Inputs, Outputs);
    } catch(e) {
        throw e;
    } finally {
        bs = null;
    }
}

Here is a full version of my client-side Business Service - FWK Runtime. Simply, download and import it on Administration - Business Service screen.

And finally, granting access to run a service runner from browser scripts:

2. JS code with UI and service call.

We will need a UI to enter a WF process name and a button to run the BS. We will be using JQuery dialog with just a couple of elements. It is going to look like this:

Source code and installation links are below.

  • Front-end: Snippet
  • /* 
    @desc bookmarklet UI to activate Siebel Workflow Process
    @author VB (xapuk.com)
    @version 1
    */
    if ("undefined" == typeof SiebelApp) {
        alert("It works only in Siebel OUI session!");
    } else {
    
        // snippet id
        var id = "SiebelWFDeploy";
    
        // localStorage to store the history
        var aHist = window.localStorage[id]?JSON.parse(window.localStorage[id]):[];
    
        // just in case (experimental)
        $("#" + id).parent().remove();
    
        // constructing dialog content
        var s = '<div title="Activate workflow">';
        s += '<input id = "' + id + '" type="text" list="' + id + 'List" style="width:100%" value="' + (aHist.length?aHist[0]:"") + '">'; // most recent
        s += '<label class="pt-3">Recent workflows:</label><ul>';
        for (var i =0; i < aHist.length && i < 5; i++){ // five recent values as links
            s += '<li><a href="#">' + aHist[i] + '</a></li>';
        }
        s += '</ul></div>';
    
        // open dialog
        var $d = $(s).dialog({
            modal: true,
            width: 640,
            open: function() {
                $('#' + id).autocomplete({source: aHist});
                $('#' + id).focus().select(); // autofocus
            },
            close: function() {
                $(this).dialog('destroy').remove();
            },
            buttons: [{
                text: 'Activate (Enter/Click)',
                click: function(){
                    go($d.find('#' + id).val());
                }
            }, {
                text: 'Close (Esc)',
                click: function() {
                    $(this).dialog('close');
                }
            }]
        });
    
        // key bindings
        $d.keyup(function(event) {
            // enter
            if (event.keyCode === 13) { 
                go($d.find('#' + id).val());
            }
        });
    
        // running function on anchor link click
        $d.find("a").click(function(event) {
            go($(this).html());
        });
    }
    
    // Activate
    function go(name) {
        if (name){
            // moving recent view to the top
            if (aHist.indexOf(name) > -1){
               aHist.splice(aHist.indexOf(name),1);
            }
            aHist.unshift(name);
            window.localStorage[id] = JSON.stringify(aHist); 
            $d.dialog('close');
    
            // invoke BS
            var service = SiebelApp.S_App.GetService("FWK Runtime");
            var ps = SiebelApp.S_App.NewPropertySet();
            ps.SetProperty("Service", "Workflow Admin Service");
            ps.SetProperty("Method", "Activate");
            ps.SetProperty("FlowSearchSpec", "[Process Name] = '" + name + "'");
            var outputSet = service.InvokeMethod("InvokeServiceMethod", ps);
            if (console){
                console.log(outputSet);
            }
            if (outputSet.GetProperty("Status") == "Error"){
                alert(outputSet.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg"));    
            }else{
                if (outputSet.GetChildByType("ResultSet").GetProperty("NumFlowActivated") === "0"){
                    alert("Process definition [" + name + "] not found");
                }else{
                    alert("Done!");
                }
            }
        }
    }
  • Bookmarklet code: Snippet
  • javascript:function go(e){if(e){aHist.indexOf(e)>-1&&aHist.splice(aHist.indexOf(e),1),aHist.unshift(e),window.localStorage[id]=JSON.stringify(aHist),$d.dialog("close");var i=SiebelApp.S_App.GetService("FWK Runtime"),t=SiebelApp.S_App.NewPropertySet();t.SetProperty("Service","Workflow Admin Service"),t.SetProperty("Method","Activate"),t.SetProperty("FlowSearchSpec","[Process Name] = '"+e+"'");var o=i.InvokeMethod("InvokeServiceMethod",t);console&&console.log(o),alert("Error"==o.GetProperty("Status")?o.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg"):"0"===o.GetChildByType("ResultSet").GetProperty("NumFlowActivated")?"Process definition ["+e+"] not found":"Done!")}}if("undefined"==typeof SiebelApp)alert("It works only in Siebel OUI session!");else{var id="SiebelWFDeploy",aHist=window.localStorage[id]?JSON.parse(window.localStorage[id]):[];$("#"+id).parent().remove();var s='<div title="Activate workflow">';s+='<input id = "'+id+'" type="text" list="'+id+'List" style="width:100%" value="'+(aHist.length?aHist[0]:"")+'">',s+='<label class="pt-3">Recent workflows:</label><ul>';for(var i=0;i<aHist.length&&5>i;i++)s+='<li><a href="#">'+aHist[i]+"</a></li>";s+="</ul></div>";var $d=$(s).dialog({modal:!0,width:640,open:function(){$("#"+id).autocomplete({source:aHist}),$("#"+id).focus().select()},close:function(){$(this).dialog("destroy").remove()},buttons:[{text:"Activate (Enter/Click)",click:function(){go($d.find("#"+id).val())}},{text:"Close (Esc)",click:function(){$(this).dialog("close")}}]});$d.keyup(function(e){13===e.keyCode&&go($d.find("#"+id).val())}),$d.find("a").click(function(e){go($(this).html())})}
  • Bookmarklet link: Activate WF

3. Disable cache.

Workflow Process cache is controlled by VerCheckTime (Workflow Version Checking Interval) server parameter .

If the parameter is on, a new WF version wouldn't take in action in existing session right away after activating it. It makes sense to have it on production, but I recommend to turn it off on DEV environment.

Also, turn it off on your dedicated client in .cfg file:

...
[Workflow]
VerCheckTime = -1

Bonus

Same story with TBUI. Simply run [Task Activation Automation] service from JavaScript. Service runner is paying off already, by the way.

  • Bookmarklet code: Snippet
  • javascript:function go(e){if(e){aHist.indexOf(e)>-1&&aHist.splice(aHist.indexOf(e),1),aHist.unshift(e),window.localStorage[id]=JSON.stringify(aHist),$d.dialog("close");var t=SiebelApp.S_App.GetService("FWK Runtime"),i=SiebelApp.S_App.NewPropertySet();i.SetProperty("Service","Task Activation Automation"),i.SetProperty("Method","Activate Task"),i.SetProperty("TaskName",e);var o=t.InvokeMethod("InvokeServiceMethod",i);console&&console.log(o),alert("Error"==o.GetProperty("Status")?o.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg"):"0"===o.GetChildByType("ResultSet").GetProperty("NumFlowActivated")?"Process definition ["+e+"] not found":"Done!")}}if("undefined"==typeof SiebelApp)alert("It works only in Siebel OUI session!");else{var id="SiebelTaskDeploy",aHist=window.localStorage[id]?JSON.parse(window.localStorage[id]):[];$("#"+id).parent().remove();var s='<div title="Activate task">';s+='<input id = "'+id+'" type="text" list="'+id+'List" style="width:100%" value="'+(aHist.length?aHist[0]:"")+'">',s+='<label class="pt-3">Recent tasks:</label><ul>';for(var i=0;i<aHist.length&&5>i;i++)s+='<li><a href="#">'+aHist[i]+"</a></li>";s+="</ul></div>";var $d=$(s).dialog({modal:!0,width:640,open:function(){$("#"+id).autocomplete({source:aHist}),$("#"+id).focus().select()},close:function(){$(this).dialog("destroy").remove()},buttons:[{text:"Activate (Enter/Click)",click:function(){go($d.find("#"+id).val())}},{text:"Close (Esc)",click:function(){$(this).dialog("close")}}]});$d.keyup(function(e){13===e.keyCode&&go($d.find("#"+id).val())}),$d.find("a").click(function(e){go($(this).html())})}
  • Bookmarklet: Activate Task

This is one of my favourite repository SQL statements. It connects all layers of Siebel application into one flat structure:

ApplicationScreen/View
AppletControl/List column
Business ComponentField
TableColumn

Here are just a few of the questions it can answer:

  • Where a particular column (field or BC) is exposed in UI?
  • Under which caption a particular column/field is exposed in UI?
  • Is my field or column is already used in the application?
  • What fields or columns are exposed on a certain view (applet or application)?
  • What views are available in my application?

I'll share the most full version of the SQL, but feel free to tune it up to your purpose.

SELECT distinct
  t.NAME "TABLE", col.NAME "COLUMN",
  b.NAME "BUSCOMP", f.NAME "FIELD", 
  nvl(ci.CAPTION, li.DISPLAY_NAME) "CAPTION", nvl(c.NAME, lc.NAME) "CONTROL", a.NAME "APPLET",
  s.NAME "SCREEN", v.NAME "VIEW", ap.NAME "APPLICATION"
FROM siebel.S_REPOSITORY r
  -- workspace
  join siebel.S_WORKSPACE ws on ws.REPOSITORY_ID = r.ROW_ID
  -- application
  join siebel.S_APPLICATION ap on ap.REPOSITORY_ID = r.ROW_ID and ws.ROW_ID = ap.WS_ID
  join siebel.S_PAGE_TAB aps on aps.APPLICATION_ID = ap.WS_SRC_ID and aps.INACTIVE_FLG = 'N' and ws.ROW_ID = aps.WS_ID
  -- screen
  join siebel.S_SCREEN s on s.name = aps.SCREEN_NAME and s.REPOSITORY_ID = r.ROW_ID AND s.INACTIVE_FLG = 'N' and ws.ROW_ID = s.WS_ID
  join siebel.S_SCREEN_VIEW sv on sv.screen_id = s.WS_SRC_ID and sv.INACTIVE_FLG = 'N' and ws.ROW_ID = sv.WS_ID
  -- view
  join siebel.S_VIEW v on v.REPOSITORY_ID = r.ROW_ID and sv.VIEW_NAME = v.name and ws.ROW_ID = v.WS_ID
  join siebel.S_VIEW_WEB_TMPL vt on vt.VIEW_ID = v.WS_SRC_ID and vt.INACTIVE_FLG = 'N' and ws.ROW_ID = vt.WS_ID
  join siebel.S_VIEW_WTMPL_IT vti on vti.VIEW_WEB_TMPL_ID = vt.WS_SRC_ID and vti.INACTIVE_FLG = 'N' and ws.ROW_ID = vti.WS_ID
  -- applet (only form, list applets)
  join siebel.S_APPLET a on a.REPOSITORY_ID = r.ROW_ID and a.name = vti.APPLET_NAME and ws.ROW_ID = a.WS_ID
  join siebel.S_APPL_WEB_TMPL w on w.applet_id = a.WS_SRC_ID and w.TYPE = vti.APPLET_MODE_CD and w.INACTIVE_FLG ='N' and ws.ROW_ID = w.WS_ID
  join siebel.S_APPL_WTMPL_IT wi on wi.APPL_WEB_TMPL_ID = w.WS_SRC_ID and wi.INACTIVE_FLG = 'N' and ws.ROW_ID = wi.WS_ID
  -- control
  left join siebel.S_CONTROL c on c.APPLET_ID = a.WS_SRC_ID and wi.CTRL_NAME = c.name and c.INACTIVE_FLG = 'N' and ws.ROW_ID = c.WS_ID
  left join siebel.S_LIST l on l.APPLET_ID = a.WS_SRC_ID and ws.ROW_ID = l.WS_ID
  left join siebel.S_LIST_COLUMN lc on lc.LIST_ID = l.WS_SRC_ID and wi.CTRL_NAME = lc.name and ws.ROW_ID = lc.WS_ID
  -- control caption (only overrides!)
  left join siebel.S_CONTROL_INTL ci on ci.CONTROL_ID = c.WS_SRC_ID and ws.ROW_ID = ci.WS_ID
  left join siebel.S_LIST_COL_INTL li on li.LIST_COLUMN_ID = lc.WS_SRC_ID and ws.ROW_ID = li.WS_ID
  -- buscomp
  join SIEBEL.S_BUSCOMP b on b.name = a.BUSCOMP_NAME and b.REPOSITORY_ID = r.ROW_ID and ws.ROW_ID = b.WS_ID
  -- fields exposed either through control or list column
  join siebel.S_FIELD f on f.BUSCOMP_ID = b.WS_SRC_ID
    and (lc.FIELD_NAME = f.name or c.FIELD_NAME = f.name) and ws.ROW_ID = f.WS_ID
  -- join
  left join siebel.S_JOIN j on j.name = f.JOIN_NAME and j.BUSCOMP_ID = f.BUSCOMP_ID and ws.ROW_ID = j.WS_ID
  -- table
  join siebel.S_TABLE t on t.REPOSITORY_ID = r.ROW_ID
    and (t.name = b.TABLE_NAME and f.join_name is null  -- base table
    or t.name = j.DEST_TBL_NAME and j.row_id is not null  -- explicit joins
    or t.name = f.JOIN_NAME and j.row_id is null) -- implicit joins
  -- column
  join siebel.S_COLUMN col on col.TBL_ID = t.ROW_ID and f.COL_NAME = col.name and ws.ROW_ID = c.WS_ID
WHERE r.name = 'Siebel Repository'
  and ws.name = 'MAIN'
  and ap.name LIKE 'Siebel Financial Services'
ORDER BY 1, 2, 3;

Keep in mind that the SQL:

Here is also a version for old Siebel repositories (non-workspace): Snippet

SELECT distinct
  t.NAME "TABLE", col.NAME "COLUMN",
  b.NAME "BUSCOMP", f.NAME "FIELD", 
  nvl(ci.CAPTION, li.DISPLAY_NAME) "CAPTION", nvl(c.NAME, lc.NAME) "CONTROL", a.NAME "APPLET",
  s.NAME "SCREEN", v.NAME "VIEW", ap.NAME "APPLICATION"
FROM siebel.S_REPOSITORY r
  -- application
  join siebel.S_APPLICATION ap on ap.REPOSITORY_ID = r.ROW_ID
  join siebel.S_PAGE_TAB aps on aps.APPLICATION_ID = ap.ROW_ID and aps.INACTIVE_FLG = 'N'
  -- screen
  join siebel.S_SCREEN s on s.name = aps.SCREEN_NAME and s.REPOSITORY_ID = r.ROW_ID AND s.INACTIVE_FLG = 'N'
  join siebel.S_SCREEN_VIEW sv on sv.screen_id = s.ROW_ID and sv.INACTIVE_FLG = 'N'
  -- view
  join siebel.S_VIEW v on v.REPOSITORY_ID = r.ROW_ID and sv.VIEW_NAME = v.name
  join siebel.S_VIEW_WEB_TMPL vt on vt.VIEW_ID = v.ROW_ID and vt.INACTIVE_FLG = 'N'
  join siebel.S_VIEW_WTMPL_IT vti on vti.VIEW_WEB_TMPL_ID = vt.ROW_ID and vti.INACTIVE_FLG = 'N'
  -- applet (only form, list applets)
  join siebel.S_APPLET a on a.REPOSITORY_ID = r.ROW_ID and a.name = vti.APPLET_NAME
  join siebel.S_APPL_WEB_TMPL w on w.applet_id = a.ROW_ID and w.TYPE = vti.APPLET_MODE_CD and w.INACTIVE_FLG ='N'
  join siebel.S_APPL_WTMPL_IT wi on wi.APPL_WEB_TMPL_ID = w.ROW_ID and wi.INACTIVE_FLG = 'N'
  -- control
  left join siebel.S_CONTROL c on c.APPLET_ID = a.ROW_ID and wi.CTRL_NAME = c.name and c.INACTIVE_FLG = 'N'
  left join siebel.S_LIST l on l.APPLET_ID = a.ROW_ID
  left join siebel.S_LIST_COLUMN lc on lc.LIST_ID = l.ROW_ID and wi.CTRL_NAME = lc.name
  -- control caption (only overrides!)
  left join siebel.S_CONTROL_INTL ci on ci.CONTROL_ID = c.ROW_ID
  left join siebel.S_LIST_COL_INTL li on li.LIST_COLUMN_ID = lc.ROW_ID
  -- buscomp
  join SIEBEL.S_BUSCOMP b on b.name = a.BUSCOMP_NAME and b.REPOSITORY_ID = r.ROW_ID
  -- fields exposed either through control or list column
  join siebel.S_FIELD f on f.BUSCOMP_ID = b.ROW_ID 
    and (lc.FIELD_NAME = f.name or c.FIELD_NAME = f.name)
  -- join
  left join siebel.S_JOIN j on j.name = f.JOIN_NAME and j.BUSCOMP_ID = f.BUSCOMP_ID
  -- table
  join siebel.S_TABLE t on t.REPOSITORY_ID = r.ROW_ID 
    and (t.name = b.TABLE_NAME and f.join_name is null  -- base table
    or t.name = j.DEST_TBL_NAME and j.row_id is not null  -- explicit joins
    or t.name = f.JOIN_NAME and j.row_id is null) -- implicit joins
  -- column
  join siebel.S_COLUMN col on col.TBL_ID = t.ROW_ID and f.COL_NAME = col.name
WHERE r.name = 'Siebel Repository'
  and ap.name LIKE 'Siebel Financial Services'
ORDER BY 1, 2, 3;

Starting a series "Runtime objects cache". And the first runtime object I'll be covering is Runtime Events (RTE).

Background:

If you are working with RTE you probably know about "Reload Runtime Events" menu command, which allows you to clear RTE cache in your current session. But, what if you are working with multiple sessions? For example, doing your configuration under one user / application and testing it under another, where you don't have access to RTE screen. It could also be a case where you don't want to lose a context. And finally, you might be creating RTE indirectly, from workflows start connector. It all leads you to restarting a Siebel client or losing a context.

Solution:

It turns out "Reload Runtime Events" command is an implicit BC method supported by generic CSSBusComp class. It means we can run that command from any applet and let it propagate to BC. Thanks to Siebel OUI we can run any applet method right from the browser console. As simple as that:

SiebelApp.S_App.GetActiveView().GetActiveApplet().InvokeMethod("ClearCTEventCache");

Here I've made a bookmarklet out of it, so you can run Reload RTE command from browsers bookmark toolbar in any application/view/context without any pre-configuration:

  • Source code: Snippet
  • /* 
    @desc Reloads Runtime Events
    @author VB(xapuk.com)
    @version 1.1 2019/04/20
    */
    if("undefined" === typeof SiebelApp){
        alert("Please, log into Siebel application first!");
    }else{
        var v = SiebelApp.S_App.GetActiveView();
        var ap = v.GetActiveApplet();
        if ("undefined" === typeof ap) {
            ap = v.GetAppletMap()[Object.keys(v.GetAppletMap())[0]];
        }
        if ("undefined" === typeof ap) {
            alert("No applet found!");
        } else {
            ap.InvokeMethod("ClearCTEventCache", null, {
                "cb": function(e) {
                    alert("Runtime Events were reloaded!");
                },
                "errcb": function(e) {
                    console.log("Err", e);
                    alert(e.toString());
                }
            });
        }
    }
    
  • Bookmarklet code: Snippet
  • javascript:void function(){if("undefined"==typeof SiebelApp)alert("Please, log into Siebel application first!");else{var e=SiebelApp.S_App.GetActiveView(),t=e.GetActiveApplet();"undefined"==typeof t&&(t=e.GetAppletMap()[Object.keys(e.GetAppletMap())[0]]),"undefined"==typeof t?alert("No applet found!"):t.InvokeMethod("ClearCTEventCache",null,{cb:function(e){alert("Runtime Events were reloaded!")},errcb:function(e){console.log("Err",e),alert(e.toString())}})}}();
    
  • Bookmark link: Drag&drop me

Bonus:

You can use the same approach to reset List of Values cache:

SiebelApp.S_App.GetActiveView().GetActiveApplet().InvokeMethod("ClearLOVCache");

 

and View/Responsibility cache:

SiebelApp.S_App.GetActiveView().GetActiveApplet().InvokeMethod("ClearResponsibilityCache");

 

You know how calc expressions can become a nightmare when growing in size. Nested IIFs, long chains of logical operators, service calls and dozen of brackets. And all these in a single line. It can take minutes before one can understand what is going on. Sometimes I copy the expression into a text editor and manually format it - with tabs and newlines, the same way modern IDE formats the code. Once done, it is much easier to read a logic from the rule.

Inspired by the idea to automate the routine, I did a research and then spent some time on a "grammar playground". Check out the result below or in a new window.

Siebel Expressions online beautifier

Current formatting logic is to simply print function parameters (when more than 2) on new lines as well as logical expressions enclosed in parenthesis. I plan to make it a bit smarter in future.

Source code

function trav(o, t, f) {
	var r = "";
	if ("object" === typeof o) {
		var p = o.par;
		var n = o.not;

		if (o.type === "bin") {
			r =  trav(o.left, t) + " " + o.operator + " " + trav(o.right, t);
		} else if (o.type === "log") {
			if(p) { // format logical operators eclosed in brackets
				tt = t + "\t";
				r = "(\n";
				r += tt + trav(o.left, tt, true);
				r += "\n" + tt + o.operator + " " + trav(o.right, tt, true);
				r += "\n" + t + ")";
				p = false;
			} else {
				if(f) {
					r = trav(o.left, t, true);
					r += "\n" + t + o.operator + " " + trav(o.right, t, true);
				} else {
					r = trav(o.left, t) + " " + o.operator + " " + trav(o.right, t);
				}
			}
		} else if (o.type === "func") {
			var f = o.arguments.length > 2; // split params when more then 2
			var s = (f ? "\n" + t : "");
			var st = (f ? s + "\t" : "");
			r = o.name + "(";
			for (var i in o.arguments) {
				r += st + trav(o.arguments[i], t + "\t") + (i < o.arguments.length - 1 ? ", " : "");
			}
			r += s + ")";
		} else if (o.type === "field") {
			r = "[" + o.field + "]";
		} else if (o.type === "param") {
			r =  "[&" + o.param + "]";
		} else if (o.type === "num") {
			r =  o.value;
		} else if (o.type === "str") {
			r = o.quote + o.value + o.quote;
		}

		if (p) {
			r = "(" + r + ")";
		}
		if (n) {
			r = "NOT " + r;
		}

	} else {
		r = o.toString();
	}
    return r;
}

$(document).ready(function(){
	$("#ExpParser").on("click", function() {
		var s = $("#ExpParserInput").val();
		console.log(s);
		if (s) {
			try {
				var o = SiebelQueryLang.parse(s);
				s = trav(o.expression, "");
			} catch(e) {
				s = e.toString();
			}
		} else {
			s = "Please, insert a Siebel expression first";
		}
		$("#ExpParserOutput").val(s);
	}).click();
})

The main outcome of the effort is, of course, to make it a part of Expression Playground. Check out an updated version here.

Another playground for you Guys. This time it is to evaluate a "calc field" expressions. The official name of the syntax is Siebel Query Language and it is used widely through Siebel apps, for example:

  • Field calculated value, pre/post-default, validation
  • Data Validation Manager
  • User properties
  • Workflow processes and UI Tasks
  • Predefined queries
  • Runtime events
  • EAI/BC Data Map

A playground is very handy when it comes to prototyping or debugging expressions or simply if you need to get a value of an active field or profile attribute.

Less words more code, let's get down to the implementation.


Back end


This time we will be invoking a built-in BusComp method EvalExpr. So let's create another method in runtime business service:

function EvalExpr (Inputs, Outputs) {
    var bc:BusComp;
    try {
        bc = TheApplication().ActiveBusObject().GetBusComp(Inputs.GetProperty("BC"));
        Outputs.SetProperty("Result", bc.InvokeMethod("EvalExpr", Inputs.GetProperty("Expr")));
    } catch(e) {
        Outputs.SetProperty("Result", e.toString());
    } finally {
        bc = null;
    }
}

Here is a full version of my client-side Business Service.


Front end


For UI we will be using a bookmarklet with a dialog which contains a drop-down with active BusComps, two text areas for input expression and a result, and a button to run the service.

Here is the code from my most recent version: Snippet
/* 
@desc UI allowing to evaluate expressions (EvalExpr) on active BCs
@author VB(xapuk.com)
@version 1.2 2018/07/23
@requires BS "FWK Runtime" to be published
*/

if ("undefined" == typeof SiebelApp){
    alert("It works only in Siebel OUI session!");
}else{
    var func = "SiebelEvalExpr";
    $("#" + func).parent().remove();

    var a = LoadBCs();
    if (a.length === 0){
        alert("No BusComps/Records available!");
    }else{

        var s = '<div title="Runtime calculations">' +
        '<label for="' + func + 'List">Business Component:</label>' +
        '<select id = "' + func + 'List" style="display:block"></select>' +
        '<label for="' + func + '">Expression:</label>' +
        '<textarea id = "' + func + '" rows="3"></textarea>' +
        '<label for="' + func + 'Out">Results:</label>' +
        '<textarea id = "' + func + 'Out" disabled rows="2"></textarea>' +
        '<style>select,textarea{width:100%!Important}</style>' +
        '</div>';

        var d = $(s).dialog({
            modal: true,
            width: 1024,
            heigth: 640,
            open: function(){
                $('#'+func).focus();

                // key bindings
                $("#"+func+"Out").parent().keydown(function(event) {
                    if (event.ctrlKey && event.keyCode === 13) { // ctrl + Enter
                        EvalExpr();
                    }
                });

                // list of BCs
                $("#" + func + "List").append("<option>" + a.join("</option><option>") + "</option>");
                $("#" + func + "List").val(SiebelApp.S_App.GetActiveView().GetActiveApplet().GetBusComp().GetName());

                // recent expression
                $("#" + func).val(JSON.parse(window.localStorage[func]));

                //style
                $(this).append('<style type="text/css">textarea, select { height:auto; width:100% }</style>');
            },
            close: function(){
                $(this).dialog('destroy').remove();
            },
            buttons: [
                {
                    text:'Run (Ctrl+Enter)',
                    click: EvalExpr
                },
                {
                   text:'Close (Esc)',
                   click: function() {
                    $(this).dialog('destroy').remove();
                   }
                }
            ]
        });

        // bind and trigger auto-adjust
        $(d).find("#" + func).keyup(function(){
            TextAdjust(this, 3);
        }).keyup();
    }
}

function EvalExpr(){
    var sExpr = $('#'+func).val();
    var sRes = "";

    // save last query
    window.localStorage[func] = JSON.stringify(sExpr);

    // if there is a selection
    var ele = document.getElementById(func);
    if(ele.selectionStart !== undefined && ele.selectionStart != ele.selectionEnd){// Normal browsers
        sExpr = ele.value.substring(ele.selectionStart, ele.selectionEnd);
    }else if(document.selection !== undefined){// IE
        ele.focus();
        var sel = document.selection.createRange();
        sExpr = sel.text;
    }

    // invoke BS
    var service = SiebelApp.S_App.GetService("FWK Runtime");
    var ps = SiebelApp.S_App.NewPropertySet();
    ps.SetProperty("Expr", sExpr);
    ps.SetProperty("BC", $("#" + func + "List").val());
    var outputSet = service.InvokeMethod("EvalExpr", ps);
    if (outputSet.GetProperty("Status") == "Error"){
        sRes = outputSet.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg");
    }else{
        sRes = outputSet.GetChildByType("ResultSet").GetProperty("Result");
        console.log(outputSet);
    }
    TextAdjust($('#'+func + "Out").show().text(sRes)[0]);
}

// auto-ajust textarea height
function TextAdjust(scope, minrows, maxrows) {

    maxrows = maxrows>0?maxrows:10; 
    minrows = minrows>0?minrows:2;
    var txt = scope.value;
    var cols = scope.cols;

    var arraytxt = txt.split('\n');
    var rows = arraytxt.length; 

    if (rows > maxrows) {
        scope.rows = maxrows;
    } else if (rows < minrows) { 
        scope.rows = minrows;
    } else {
        scope.rows = rows;
    }
}

// gather the list of active BCs
function LoadBCs(){
    var a = [];
    for(var i in SiebelApp.S_App.GetActiveBusObj().GetBCMap()){
        var bc = SiebelApp.S_App.GetActiveBusObj().GetBCMap()[i];
        if (a.indexOf(bc.GetName()) == -1 && bc.GetNumRows() > 0){
            a.push(bc.GetName());
        }
    }
    return a;
} 
Bookmarklet code: Snippet
javascript:void function(){function e(){var e=$("#"+i).val(),o="";window.localStorage[i]=JSON.stringify(e);var l=document.getElementById(i);if(void 0!==l.selectionStart&&l.selectionStart!=l.selectionEnd)e=l.value.substring(l.selectionStart,l.selectionEnd);else if(void 0!==document.selection){l.focus();var r=document.selection.createRange();e=r.text}var s=SiebelApp.S_App.GetService("FWK Runtime"),a=SiebelApp.S_App.NewPropertySet();a.SetProperty("Expr",e),a.SetProperty("BC",$("#"+i+"List").val());var n=s.InvokeMethod("EvalExpr",a);"Error"==n.GetProperty("Status")?o=n.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg"):(o=n.GetChildByType("ResultSet").GetProperty("Result"),console.log(n)),t($("#"+i+"Out").show().text(o)[0])}function t(e,t,o){o=o>0?o:10,t=t>0?t:2;var i=e.value,l=(e.cols,i.split("\n")),r=l.length;r>o?e.rows=o:t>r?e.rows=t:e.rows=r}function o(){var e=[];for(var t in SiebelApp.S_App.GetActiveBusObj().GetBCMap()){var o=SiebelApp.S_App.GetActiveBusObj().GetBCMap()[t];-1==e.indexOf(o.GetName())&&o.GetNumRows()>0&&e.push(o.GetName())}return e}if("undefined"==typeof SiebelApp)alert("It works only in Siebel OUI session!");else{var i="SiebelEvalExpr";$("#"+i).parent().remove();var l=o();if(0===l.length)alert("No BusComps/Records available!");else{var r='<div title="Runtime calculations"><label for="'+i+'List">Business Component:</label><select id = "'+i+'List" style="display:block"></select><label for="'+i+'">Expression:</label><textarea id = "'+i+'" rows="3"></textarea><label for="'+i+'Out">Results:</label><textarea id = "'+i+'Out" disabled rows="2"></textarea><style>select,textarea{width:100%!Important}</style></div>',s=$(r).dialog({modal:!0,width:1024,heigth:640,open:function(){$("#"+i).focus(),$("#"+i+"Out").parent().keydown(function(t){t.ctrlKey&&13===t.keyCode&&e()}),$("#"+i+"List").append("<option>"+l.join("</option><option>")+"</option>"),$("#"+i+"List").val(SiebelApp.S_App.GetActiveView().GetActiveApplet().GetBusComp().GetName()),$("#"+i).val(JSON.parse(window.localStorage[i])),$(this).append('<style type="text/css">textarea, select { height:auto; width:100% }</style>')},close:function(){$(this).dialog("destroy").remove()},buttons:[{text:"Run (Ctrl+Enter)",click:e},{text:"Close (Esc)",click:function(){$(this).dialog("destroy").remove()}}]});$(s).find("#"+i).keyup(function(){t(this,3)}).keyup()}}}(); 
And a bookmarklet: Bookmarklet

Updated


Here is a new version with a built-in beautifier from another topic:

  • A parser to be placed at /public/scripts/3rdParty/SiebelQueryLang.js: file
  • Source code: Snippet
  • /* 
    @desc UI allowing to evaluate expressions (EvalExpr) on active BCs
    @author VB(xapuk.com)
    @version 1.3 2019/03/10
    @requires BS "FWK Runtime" to be compiled and published
    */
    
    if ("undefined" == typeof SiebelApp){
        alert("It works only in Siebel OUI session!");
    } else {
        var func = "SiebelEvalExpr";
        $("#" + func).parent().remove();
        var bBeauty = false;
        var a = LoadBCs();
        if (a.length === 0){
            alert("No BusComps/Records available!");
        }else{
    
            var s = '<div title="Runtime calculations">' +
            '<label for="' + func + 'List">Business Component:</label>' +
            '<select id = "' + func + 'List" style="display:block"></select>' +
            '<label for="' + func + '">Expression:</label>' +
            '<textarea id = "' + func + '" rows="5" nowrap></textarea>' +
            '<label for="' + func + 'Out">Results:</label>' +
            '<textarea id = "' + func + 'Out" disabled rows="2"></textarea>' +
            '<style type="text/css">.ui-dialog textarea, .ui-dialog select {height:auto; width:100%; margin-bottom:10px} .ui-dialog label{margin-top:20px!}</style>'
            '</div>';
    
            var d = $(s).dialog({
                modal: true,
                width: 1024,
                heigth: 640,
                open: function(){
                    $('#'+func).focus();
    
                    // key bindings
                    $("#"+func+"Out").parent().keydown(function(event) {
                        if (event.ctrlKey && event.keyCode === 13) { // ctrl + Enter
                            EvalExpr();
                        }
                    });
    
                    // list of BCs
                    $("#" + func + "List").append("<option>" + a.join("</option><option>") + "</option>");
                    $("#" + func + "List").val(SiebelApp.S_App.GetActiveView().GetActiveApplet().GetBusComp().GetName());
    
                    // recent expression
                    $("#" + func).val(JSON.parse(window.localStorage[func]));
    
                },
                close: function(){
                    $(this).dialog('destroy').remove();
                },
                buttons: [
                    {
                        text:'Format/Linarise',
                        click: Beauty,
                        id: 'BeautyBtn'
                    },{
                        text:'Run (Ctrl+Enter)',
                        click: EvalExpr
                    },{
                       text:'Close (Esc)',
                       click: function() {
                        $(this).dialog('destroy').remove();
                       }
                    }
                ]
            });
    
            // bind and trigger auto-adjust
            $(d).find("#" + func).keyup(function(){
                TextAdjust(this, 5);
            }).keyup();
    
            // bind a beautifier
            $(".ui-dialog #BeautyBtn").hide();
            require(["3rdParty/SiebelQueryLang"], function(e){
                console.log("Beautifier loaded!");
                $(".ui-dialog #BeautyBtn").show();
            });
        }
    }
    
    function Beauty(){
        var s = $('#'+func).val();
        if (s){
            if (bBeauty){
                // linarise
                s = s.replace(/\n(\t|\s)*/gm,"");
                $('#'+func).val(s).attr("wrap", "on");
                bBeauty = false;
                
            } else {
                // beautify
                try {
                    var o = SiebelQueryLang.parse(s);
                    s = trav(o.expression, "");
                    $('#'+func).val(s).attr("wrap", "off");
                    bBeauty = true;
                } catch(e) {
                    // silence the error
                    console.log(e);
                }
            }
            TextAdjust($('#'+func)[0]);
        }
    }
    
    function trav(o, t, f) {
    	var r = "";
    	if ("object" === typeof o) {
    		var p = o.par;
    		var n = o.not;
    
    		if (o.type === "bin") {
    			r =  trav(o.left, t) + " " + o.operator + " " + trav(o.right, t);
    		} else if (o.type === "log") {
    			if(p) { // format logical operators eclosed in brackets
    				tt = t + "\t";
    				r = "(\n";
    				r += tt + trav(o.left, tt, true);
    				r += "\n" + tt + o.operator + " " + trav(o.right, tt, true);
    				r += "\n" + t + ")";
    				p = false;
    			} else {
    				if(f) {
    					r = trav(o.left, t, true);
    					r += "\n" + t + o.operator + " " + trav(o.right, t, true);
    				} else {
    					r = trav(o.left, t) + " " + o.operator + " " + trav(o.right, t);
    				}
    			}
    		} else if (o.type === "func") {
    			var l = o.arguments.length;
    			var f = l > 2; // split params when more then 2
    			var s = (f ? "\n" + t : "");
    			var st = (f ? s + "\t" : "");
    			r = o.name + "(";
    			o.arguments.forEach(function(a, i) {
    				r += st + trav(a, t + "\t") + (i < l - 1 ? ", " : "");
    			});
    			r += s + ")";
    		} else if (o.type === "field") {
    			r = "[" + o.field + "]";
    		} else if (o.type === "param") {
    			r =  "[&" + o.param + "]";
    		} else if (o.type === "num") {
    			r =  o.value;
    		} else if (o.type === "str") {
    			r = '"' + o.value +'"';
    		}
    
    		if (p) {
    			r = "(" + r + ")";
    		}
    		if (n) {
    			r = "NOT " + r;
    		}
    
    	} else {
    		r = o;
    	}
        return r;
    }
    
    
    function EvalExpr(){
    
        var sExpr = $('#'+func).val();
        var sRes = "";
    
        // save last query
        window.localStorage[func] = JSON.stringify(sExpr);
    
        // if there is a selection
        var ele = document.getElementById(func);
        if(ele.selectionStart !== undefined && ele.selectionStart != ele.selectionEnd){// Normal browsers
            sExpr = ele.value.substring(ele.selectionStart, ele.selectionEnd);
        }else if(document.selection !== undefined){// IE
            ele.focus();
            var sel = document.selection.createRange();
            sExpr = sel.text;
        }
    
        // invoke BS
        var service = SiebelApp.S_App.GetService("FWK Runtime");
        var ps = SiebelApp.S_App.NewPropertySet();
        ps.SetProperty("Expr", sExpr);
        ps.SetProperty("BC", $("#" + func + "List").val());
        var outputSet = service.InvokeMethod("EvalExpr", ps);
        if (outputSet.GetProperty("Status") == "Error"){
            sRes = outputSet.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg");
        }else{
            sRes = outputSet.GetChildByType("ResultSet").GetProperty("Result");
            console.log(outputSet);
        }
        TextAdjust($('#'+func + "Out").show().text(sRes)[0]);
    }
    
    // auto-ajust textarea height
    function TextAdjust(scope, minrows, maxrows) {
    
        maxrows = maxrows>0?maxrows:30;
        minrows = minrows>0?minrows:5;
        var txt = scope.value;
        var cols = scope.cols;
    
        var arraytxt = txt.split('\n');
        var rows = arraytxt.length; 
    
        if (rows > maxrows) {
            scope.rows = maxrows;
        } else if (rows < minrows) { 
            scope.rows = minrows;
        } else {
            scope.rows = rows;
        }
    }
    
    // put active BC in the list with active applet's bc as selected
    function LoadBCs(){
        var a = [];
        for(var i in SiebelApp.S_App.GetActiveBusObj().GetBCMap()){
            var bc = SiebelApp.S_App.GetActiveBusObj().GetBCMap()[i];
            if (a.indexOf(bc.GetName()) == -1 && bc.GetNumRows() > 0){
                a.push(bc.GetName());
            }
        }
        return a;
    } 
  • Bookmarklet code: Snippet
  • javascript:void function(){function e(){var e=$("#"+a).val();if(e){if(l)e=e.replace(/\n(\t|\s)*/gm,""),$("#"+a).val(e).attr("wrap","on"),l=!1;else try{var o=SiebelQueryLang.parse(e);e=t(o.expression,""),$("#"+a).val(e).attr("wrap","off"),l=!0}catch(r){console.log(r)}i($("#"+a)[0])}}function t(e,o,i){var r="";if("object"==typeof e){var a=e.par,l=e.not;if("bin"===e.type)r=t(e.left,o)+" "+e.operator+" "+t(e.right,o);else if("log"===e.type)a?(tt=o+"	",r="(\n",r+=tt+t(e.left,tt,!0),r+="\n"+tt+e.operator+" "+t(e.right,tt,!0),r+="\n"+o+")",a=!1):i?(r=t(e.left,o,!0),r+="\n"+o+e.operator+" "+t(e.right,o,!0)):r=t(e.left,o)+" "+e.operator+" "+t(e.right,o);else if("func"===e.type){var n=e.arguments.length,i=n>2,s=i?"\n"+o:"",p=i?s+"	":"";r=e.name+"(",e.arguments.forEach(function(e,i){r+=p+t(e,o+"	")+(n-1>i?", ":"")}),r+=s+")"}else"field"===e.type?r="["+e.field+"]":"param"===e.type?r="[&"+e.param+"]":"num"===e.type?r=e.value:"str"===e.type&&(r='"'+e.value+'"');a&&(r="("+r+")"),l&&(r="NOT "+r)}else r=e;return r}function o(){var e=$("#"+a).val(),t="";window.localStorage[a]=JSON.stringify(e);var o=document.getElementById(a);if(void 0!==o.selectionStart&&o.selectionStart!=o.selectionEnd)e=o.value.substring(o.selectionStart,o.selectionEnd);else if(void 0!==document.selection){o.focus();var r=document.selection.createRange();e=r.text}var l=SiebelApp.S_App.GetService("FWK Runtime"),n=SiebelApp.S_App.NewPropertySet();n.SetProperty("Expr",e),n.SetProperty("BC",$("#"+a+"List").val());var s=l.InvokeMethod("EvalExpr",n);"Error"==s.GetProperty("Status")?t=s.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg"):(t=s.GetChildByType("ResultSet").GetProperty("Result"),console.log(s)),i($("#"+a+"Out").show().text(t)[0])}function i(e,t,o){o=o>0?o:30,t=t>0?t:5;var i=e.value,r=(e.cols,i.split("\n")),a=r.length;a>o?e.rows=o:t>a?e.rows=t:e.rows=a}function r(){var e=[];for(var t in SiebelApp.S_App.GetActiveBusObj().GetBCMap()){var o=SiebelApp.S_App.GetActiveBusObj().GetBCMap()[t];-1==e.indexOf(o.GetName())&&o.GetNumRows()>0&&e.push(o.GetName())}return e}if("undefined"==typeof SiebelApp)alert("It works only in Siebel OUI session!");else{var a="SiebelEvalExpr";$("#"+a).parent().remove();var l=!1,n=r();if(0===n.length)alert("No BusComps/Records available!");else{var s='<div title="Runtime calculations"><label for="'+a+'List">Business Component:</label><select id = "'+a+'List" style="display:block"></select><label for="'+a+'">Expression:</label><textarea id = "'+a+'" rows="5" nowrap></textarea><label for="'+a+'Out">Results:</label><textarea id = "'+a+'Out" disabled rows="2"></textarea><style type="text/css">.ui-dialog textarea, .ui-dialog select {height:auto; width:100%; margin-bottom:10px} .ui-dialog label{margin-top:20px!}</style>',p=$(s).dialog({modal:!0,width:1024,heigth:640,open:function(){$("#"+a).focus(),$("#"+a+"Out").parent().keydown(function(e){e.ctrlKey&&13===e.keyCode&&o()}),$("#"+a+"List").append("<option>"+n.join("</option><option>")+"</option>"),$("#"+a+"List").val(SiebelApp.S_App.GetActiveView().GetActiveApplet().GetBusComp().GetName()),$("#"+a).val(JSON.parse(window.localStorage[a]))},close:function(){$(this).dialog("destroy").remove()},buttons:[{text:"Format/Linarise",click:e,id:"BeautyBtn"},{text:"Run (Ctrl+Enter)",click:o},{text:"Close (Esc)",click:function(){$(this).dialog("destroy").remove()}}]});$(p).find("#"+a).keyup(function(){i(this,5)}).keyup(),$(".ui-dialog #BeautyBtn").hide(),require(["3rdParty/SiebelQueryLang"],function(e){console.log("Beautifier loaded!"),$(".ui-dialog #BeautyBtn").show()})}}}(); 
  • And a bookmarklet link: Bookmarklet

 

If you ever had a requirement to constrain an assocoation applet, you might be familiar with one of the methods:

To be honest, I always felt guilty using above methods and kept looking for a more declarative way. And finally found it.


Parent BC Constraint Field - applet user property


Let's say you need to constrain a [Contact Assoc Applet] with Opportunity's primary organisation => so you'll assoc only contacts which are from the same org as your opportunity.

First thing you'll need to do is to change an applet class to be CSSSWEFrameSIAAssocList.
Now create a couple of applet user properties:

User Property Value Description
BC Field Search LHS [Primary Organization Id] = Left (static) part of spec. A field mentioned here is from assoc applet's BC.
Parent BC Constraint Field Primary Organization Id Right (dynamic) part of spec is a field name from your list applet's parent BC (Opportunity)

Applet is ready to be compiled and tested!

As a result Siebel will construct a named search spec as shown in a log below:


... Named search [Associate Constraint Search]: [Primary Organization Id] ='2-9EZ5U1'

 

Afterword

As I already mentioned, these UserProps are part of a specific class, which means you can't use it together with other useful UserProprs from different classes (for example, Override Visibility)

Also keep in mind that you are now bond to parent BC field name. Association applet will fail, if used in the context, where parent BC doesn't have a referenced field.

What if you can run eScript code without even opening Siebel Tools. Right from the Siebel client.
Need to code a new service? Click your favourite bookmarklet button and prototype it immediately, no need of recompilations/restarts.
Need to hook into a current BO context and manipulate active BCs? Easy!

Before I start, I have to say this idea haven't visited my head first. If you need a full immersion you should ask my masterminds - Jason or Roy.

So, here is how you build a lightweight eScript interactive playground.


Back-end


As you may have guessed, it is all around eval() function. First step is to make it invokable from the browser:

1. Create a Business Service, as simple as this one:

function Service_PreInvokeMethod (MethodName, Inputs, Outputs) {
	if (MethodName == “EvalScript”){
		try {
			Outputs.SetProperty(“Result”, eval(Inputs.GetProperty(“Expr”)));
		} catch(e) {
			Outputs.SetProperty(“Result”, e.toString());
		}
	}
	return (CancelOperation);
}

A little trick here is to create the BS as a client-side business service, so it wouldn't be a part of repository => neither part of regular migrations => no security bleach on production.

2. Publish the service, so it can be accessible from a browser:


Front-end


You will need a dialog with input and output text areas and a button to run the BS. Here is how your JS will look like:

// dialog html
var s = '<div title="eScript">'
+ '<textarea id = "SiebelEvalScript" style="height:150px"></textarea>'
+ '<textarea id = "SiebelEvalScriptOut" rows="4" disabled></textarea>'
+ '<style>textarea{width:100%!Important}</style>'
+ '</div>';

// display dialog
$(s).dialog({
    modal: true,
    width: 1024,
    buttons: [{text:'Run', click: Eval}]
});

// run Business Service
function Eval(){
    var sRes = "";
    var ps = SiebelApp.S_App.NewPropertySet();
    ps.SetProperty("Expr", $('#SiebelEvalScript').val());
    var outputSet = SiebelApp.S_App.GetService("FWK Runtime").InvokeMethod("EvalScript", ps);
    if (outputSet.GetProperty("Status") == "Error"){
        sRes = outputSet.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg");
    }else{
        sRes = outputSet.GetChildByType("ResultSet").GetProperty("Result");
    }
    $('#SiebelEvalScriptOut').text(sRes);
}

Try it in browser console or compile into a bookmarklet.


Recent release


Don't have time building your own playground? Try mine, it is free and has some advanced features you might enjoy:

  • JavaScript code editor;
  • log() function to print intermediate results also works with PropertySets;
  • Prints outputs into browser console;
  • Run full snippet or a select a partucular peace of code to run it;
  • Orginise a library of your favourite code snippets;
  • Key bindings (Ctrl+Enter, Ctrl+S);

Front: Source code, Bookmarklet code, Bookmarklet

/* 
@desc Framework allowing to run/evaluate eScript code
@author VB(xapuk.com)
@version 1.3 2018/12/05
@requires BS=FWK Runtime to be published
*/

if ("undefined" == typeof SiebelApp){
    alert("It works only in Siebel OUI session!");
}else{
    var editor; // AceJS editor object
    var func = "SiebelEvalScript"; // function identifier
    var snip; // an array of saved snippets
    var last; // current snippet name

    // dialog html
    var s = '<div title="eScript">'
    + '<select id = "' + func + 'List" style="display:block"><option value="*">New...</option></select>'
    + '<textarea id = "' + func + '" placeholder="eSciript code..." style="height:150px"></textarea>'
    + '<label id = "' + func + '_lbl" for="' + func + '">Initialised</label>'
    + '<textarea id = "' + func + 'Out" rows="4" disabled></textarea>'
    + '<style>select,textarea{width:100%!Important}.ui-dialog-content{padding:0.5em 1em}</style>'
    + '</div>';

    // hard-remove dialog object from DOM, just in case
    $("#"+func + "List").parent().remove();

    var d = $(s).dialog({
        modal: true,
        width: 1024,
        open: function(){

            $('#'+func).focus();

            // load acejs plugin
            if (typeof(ace) == "undefined"){
                // injecting a script tag, also you can use require() function instead
                var jsCode = document.createElement('script');    
                jsCode.setAttribute('src', "https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.2/ace.js");
                jsCode.setAttribute('async', true);
                jsCode.onload = attachACE;
                document.body.appendChild(jsCode);
            }else{
                attachACE();
            }

            // List onchange
            $("#"+func+"List").change(function(event) {
                var n = $(this).val();
                if (n != "*" && n > ""){
                    if (editor){
                        editor.setValue(snip[n]);
                    }else{
                        $("#"+func).text(snip[n]);
                    }
                    window.localStorage[func+"Last"] = n;
                }

            });

            // key bindings
            $("#"+func+"Out").parent().keydown(function(event) {
                if (event.ctrlKey && event.keyCode === 13) { // ctrl + Enter
                    Eval();
                    return false;
                }else if (event.ctrlKey && event.keyCode === 83) { // ctrl + S
                    Save();
                    return false;
                }
            });

            Load(); // load presaved params

        },
        close: function(){
            $(this).dialog('destroy').remove();
        },
        buttons: [
            {
                text:'Run (Ctrl+Enter)',
                click: Eval
            },
            {
               text:'Save (Ctrl + S)',
               click: Save
            },
            {
               text:'Remove',
               click: Delete
            },
            {
               text:'Close (Esc)',
               click: function() {
                $(this).dialog('close');
               }
            }
        ]
    });
}

function Eval(){

    var sExpr = GetCode();
    var sRes = "";
    var dTS = new Date();
    var isChrome = !!window.chrome;
    var isFirefox = typeof InstallTrigger !== 'undefined';

    // execution timestamp
    $('#'+func + "_lbl").text("Last executed at " + dTS.toISOString().replace("T", " ").replace("Z", " "));

    Save(); // save snippets every time you run it

    // invoke BS
    var service = SiebelApp.S_App.GetService("FWK Runtime");
    var ps = SiebelApp.S_App.NewPropertySet();
    ps.SetProperty("Expr", sExpr);
    var outputSet = service.InvokeMethod("EvalScript", ps);
    if (outputSet.GetProperty("Status") == "Error"){
        sRes = outputSet.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg");
    }else{
        sRes = outputSet.GetChildByType("ResultSet").GetProperty("Result");
    }
    $('#'+func + "Out").text(sRes);

    // show results in browser console
    if (console) {
        var a = sRes.split(String.fromCharCode(13));
        for(var i = 0; i < a.length; i++) {
            // split into 3 parts for styling
            var a2 = a[i].split('\t|\t');
            var s1 = "", s2 = "", s3= "";
            if (a2.length > 1){
                if (a2.length > 2){
                    s1 = a2[0];
                    s2 = a2[1];
                    for(var j = 2; j < a2.length; j++) {
                        s3 += "\t" + a2[j];
                    }
                } else {
                    s1 = a2[0];
                    s3 = a2[1];
                }
            } else {
                s3 = a[i];
            }
            
            // collapse miltiline results
            if (s3.indexOf("\n") > -1) {
                if (isFirefox  || isChrome ) {
                    console.groupCollapsed("%c" + s1 + " \t%c" + s2, "color:DarkCyan;", "color:Maroon;font-weight:bold");
                } else {
                    console.groupCollapsed(s1 + " \t" + s2);
                }
                console.log(s3);
                console.groupEnd();
            } else {
                if (isFirefox  || isChrome ) {
                    console.log("%c" + s1 + " \t%c" + s2 + " \t%c" + s3, "color:DarkCyan;", "color:Maroon;font-weight:bold", "color:black;font-weight:normal");
                } else {
                    console.log(s1 + " \t" + s2 + " \t" + s3);
                }
            }
        }
    }
}

// attach acejs plugin
function attachACE(){
    editor = ace.edit(func);
    editor.session.setMode("ace/mode/javascript");
    $(".ace_editor").css("height","300");
}

// save button
function Save(){
    var n = $('#' + func + "List").val();
    if (n == "*" || n == null){ // new
        n = prompt("Snippet name");
        if(n){
            if (n.match(/.{2,}/)){
                snip[n] = GetCode(true);
                window.localStorage[func] = JSON.stringify(snip);
                $('#' + func + "List").append('<option value="' + n + '">' + n +'</option>');
                $('#' + func + "List").val(n).change();
            }else{
                alert("Invalid snippet name!");
            }
        }
    }else{ // existing
        snip[n] = GetCode(true);
        window.localStorage[func] = JSON.stringify(snip);
    }
}

// Remove button
function Delete(){
    var n = $('#' + func + "List").val();
    if (confirm("Are you sure you want to delete a snippet: " + n)){
        if (n && n != "*"){
            delete snip[n]; // remove item
            window.localStorage[func] = JSON.stringify(snip);
            delete window.localStorage[func + "Last"];
            Load(); // reload list
        }
    }
}

// loads preserved code snippets
function Load() {

    var s = window.localStorage[func];
    
    // remove all dropdown items
    $("#" + func + "List option").remove();

    //clear editor
    if (editor){
        editor.setValue("");
    }else{
        $("#"+func).text("");
    }

    // retrieve code snippets saved in local storage
    var li = '';
    if (s){
        snip = JSON.parse(s);
        for (k in snip){
            li += '<option value="' + k + '">' + k + '</option>';
        }
    }else{
        snip={};
    }
    $("#" + func + "List").append(li);

    //last snippet
    last = window.localStorage[func+"Last"];
    if(last){
        $('#' + func + "List").val(last).change();
    }
}

// returns either selected peace of code or full value from text area or ACEJS plugin
function GetCode(bFull)
{
    var sRes;
    if (editor){
        if (bFull || editor.getSelectedText() === ""){
            sRes = editor.getValue();
        }else{
            sRes = editor.getSelectedText();
        }
    }else{
        var textComponent = document.getElementById(func);
        if (bFull){
            sRes = $('#'+func).val();
        }else if(textComponent.selectionStart !== undefined && textComponent.selectionStart != textComponent.selectionEnd){// Normal browsers
            sRes = textComponent.value.substring(textComponent.selectionStart, textComponent.selectionEnd);
        }else if(document.selection !== undefined){// IE
            textComponent.focus();
            var sel = document.selection.createRange();
            sRes = sel.text;
        }else{
            sRes = $('#'+func).val();
        }
    }
    return sRes;
}  
javascript:void function(){function e(){var e=n(),t="",l=new Date,i=!!window.chrome,a="undefined"!=typeof InstallTrigger;$("#"+s+"_lbl").text("Last executed at "+l.toISOString().replace("T"," ").replace("Z"," ")),o();var r=SiebelApp.S_App.GetService("FWK Runtime"),c=SiebelApp.S_App.NewPropertySet();c.SetProperty("Expr",e);var d=r.InvokeMethod("EvalScript",c);if(t="Error"==d.GetProperty("Status")?d.GetChildByType("Errors").GetChild(0).GetProperty("ErrMsg"):d.GetChildByType("ResultSet").GetProperty("Result"),$("#"+s+"Out").text(t),console)for(var p=t.split(String.fromCharCode(13)),u=0;u<p.length;u++){var f=p[u].split("	|	"),g="",v="",S="";if(f.length>1)if(f.length>2){g=f[0],v=f[1];for(var y=2;y<f.length;y++)S+="	"+f[y]}else g=f[0],S=f[1];else S=p[u];S.indexOf("\n")>-1?(a||i?console.groupCollapsed("%c"+g+" 	%c"+v,"color:DarkCyan;","color:Maroon;font-weight:bold"):console.groupCollapsed(g+" 	"+v),console.log(S),console.groupEnd()):a||i?console.log("%c"+g+" 	%c"+v+" 	%c"+S,"color:DarkCyan;","color:Maroon;font-weight:bold","color:black;font-weight:normal"):console.log(g+" 	"+v+" 	"+S)}}function t(){a=ace.edit(s),a.session.setMode("ace/mode/javascript"),$(".ace_editor").css("height","300")}function o(){var e=$("#"+s+"List").val();"*"==e||null==e?(e=prompt("Snippet name"),e&&(e.match(/.{2,}/)?(r[e]=n(!0),window.localStorage[s]=JSON.stringify(r),$("#"+s+"List").append('<option value="'+e+'">'+e+"</option>"),$("#"+s+"List").val(e).change()):alert("Invalid snippet name!"))):(r[e]=n(!0),window.localStorage[s]=JSON.stringify(r))}function l(){var e=$("#"+s+"List").val();confirm("Are you sure you want to delete a snippet: "+e)&&e&&"*"!=e&&(delete r[e],window.localStorage[s]=JSON.stringify(r),delete window.localStorage[s+"Last"],i())}function i(){var e=window.localStorage[s];$("#"+s+"List option").remove(),a?a.setValue(""):$("#"+s).text("");var t="";if(e){r=JSON.parse(e);for(k in r)t+='<option value="'+k+'">'+k+"</option>"}else r={};$("#"+s+"List").append(t),c=window.localStorage[s+"Last"],c&&$("#"+s+"List").val(c).change()}function n(e){var t;if(a)t=e||""===a.getSelectedText()?a.getValue():a.getSelectedText();else{var o=document.getElementById(s);if(e)t=$("#"+s).val();else if(void 0!==o.selectionStart&&o.selectionStart!=o.selectionEnd)t=o.value.substring(o.selectionStart,o.selectionEnd);else if(void 0!==document.selection){o.focus();var l=document.selection.createRange();t=l.text}else t=$("#"+s).val()}return t}if("undefined"==typeof SiebelApp)alert("It works only in Siebel OUI session!");else{var a,r,c,s="SiebelEvalScript",d='<div title="eScript"><select id = "'+s+'List" style="display:block"><option value="*">New...</option></select><textarea id = "'+s+'" placeholder="eSciript code..." style="height:150px"></textarea><label id = "'+s+'_lbl" for="'+s+'">Initialised</label><textarea id = "'+s+'Out" rows="4" disabled></textarea><style>select,textarea{width:100%!Important}.ui-dialog-content{padding:0.5em 1em}</style></div>';$("#"+s+"List").parent().remove();{$(d).dialog({modal:!0,width:1024,open:function(){if($("#"+s).focus(),"undefined"==typeof ace){var l=document.createElement("script");l.setAttribute("src","https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.2/ace.js"),l.setAttribute("async",!0),l.onload=t,document.body.appendChild(l)}else t();$("#"+s+"List").change(function(e){var t=$(this).val();"*"!=t&&t>""&&(a?a.setValue(r[t]):$("#"+s).text(r[t]),window.localStorage[s+"Last"]=t)}),$("#"+s+"Out").parent().keydown(function(t){return t.ctrlKey&&13===t.keyCode?(e(),!1):t.ctrlKey&&83===t.keyCode?(o(),!1):void 0}),i()},close:function(){$(this).dialog("destroy").remove()},buttons:[{text:"Run (Ctrl+Enter)",click:e},{text:"Save (Ctrl + S)",click:o},{text:"Remove",click:l},{text:"Close (Esc)",click:function(){$(this).dialog("close")}}]})}}}(); 

Back: Service, and don't forget about Application UserProp

I have this idea since the release of OpenUI - to pull all useful info from SiebelApp object (BO, BCs, View, Applets, Controls etc.) into a dialog which can be opened with a hit of a button. And maybe also to built in some features for a better user experience.

Check out my version of "About View" bookmarklet:

Source code & Installation
  • Drag&drop it over the browser favourites toolbar: About View
  • Or replace a bookmark URL with this code: Snippet
  • javascript:{var id="SiebelAboutView",$d,tmp='<div title="About View" id="<%=id%>"><%var v=SiebelApp.S_App.GetActiveView(),am=v.GetAppletMap()%><b>Application:</b> <a><%=SiebelApp.S_App.GetName()%></a><br><b>View:</b> <a><%=v.GetName()%></a><br><b>BusObject:</b> <a><%=SiebelApp.S_App.GetActiveBusObj().GetName()%></a><br><%if(v.GetActiveTask()){%><b>Task:</b> <a><%=v.GetActiveTask()%></a><br><%}%><b>Applets(<%=Object.keys(am).length%>) / BusComps(<%=Object.keys(SiebelApp.S_App.GetActiveBusObj().GetBCMap()).length%>):</b><br><ul style="padding-left:20px"><%for(sa in am){var a=am[sa];var bc=a.GetBusComp();var r = bc.GetSelection() < bc.GetRecordSet().length?bc.GetRecordSet()[bc.GetSelection()]:{};var os="SiebelApp.S_App.GetActiveView().GetAppletMap()[\'"+sa+"\']";var $ds=$("#"+a.GetFullId());%><li><a data-target="controls"><b style="<%if($ds.is(":hidden")){%>font-style:italic;<%}if(a===v.GetActiveApplet()){%>text-decoration:underline<%}%>"><%=sa%></b></a> / <a data-target="fields"><b><%=bc.GetName()%></b></a><ul id="controls" style="display:none"><hr><b>Applet:</b> <a><%=a.GetName()%></a><br/><b>BusComp:</b> <a><%=bc.GetName()%></a><br/><b>Mode:</b> <a><%=a.GetMode()%></a><br/><b>Title:</b> <a><%=a.GetAppletLabel()%></a><br/><%var at=a.GetToggleApplet();if(at){%><b>Toggle:</b> <a><%=at%></a><br/><%}%><b>Object Selector:</b> <a><%=os%></a><br><b>DOM Selector:</b> <a>$("<%=$ds.selector%>")</a><br><b>Controls (<%=Object.keys(a.GetControls()).length%>): </b><ul><%for(control in a.GetControls()){var c=a.GetControls()[control];var $cds=$ds.find("[name=\'"+c.GetInputName()+"\']")%><li><a data-target="control"><b style="<%if($cds.is(":hidden")){%>font-style:italic;<%}if(c===a.GetActiveControl()){%>text-decoration:underline<%}%>"><%=c.GetDisplayName()||control%></b></a><ul id="control"><hr><%if($cds.is(":visible")&&$cds.is(":focusable")){%><button data-eval="$(\'<%=$cds.selector%>\').focus()">Focus</button><br><%}%><b>Control:</b> <a><%=control%></a><br><%if(c.GetFieldName()){%><b>Field:</b> <a><%=c.GetFieldName()%></a><br><%if(r){%><b>Value:</b> <a><%=r[c.GetFieldName()]%></a><br><%}%><b>Immediate post changes:</b> <a><%=c.IsPostChanges()%></a><br><%}%><b>Type:</b> <a><%=c.GetUIType()%></a> <br><b>Input:</b> <a><%=c.GetInputName()%></a><br><b>Object Selector:</b> <a><%=os+".GetControls()[\'"+control+"\']"%></a><br><b>DOM Selector:</b> <a>$("<%=$cds.selector%>")</a><br><%if(c.GetMethodName()){%><b>Method:</b> <a><%=c.GetMethodName()%></a><br><%}%><%if(c.GetPMPropSet()&&c.GetPMPropSet().propArrayLen > 0){%><b>User Props (<%=Object.keys(c.GetPMPropSet().propArray).length%>):</b><br><ul><%for(p in c.GetPMPropSet().propArray){%><%if("string"===typeof c.GetPMPropSet().propArray[p]){%><li><a><%=p%></a>=<a><%=c.GetPMPropSet().propArray[p]%> </a></li><%}%><%}%></ul><%}%><%if(c.GetMethodPropSet()&&c.GetMethodPropSet().propArrayLen > 0){%><b>Method PS (<%=Object.keys(c.GetMethodPropSet().propArray).length%>):</b><ul><%for(p in c.GetMethodPropSet().propArray){%><%if("string"===typeof c.GetMethodPropSet().propArray[p]){%><li><a><%=p%></a>=<a><%=c.GetMethodPropSet().propArray[p]%> </a></li><%}%><%}%></ul><%}%><hr></ul></li><%}%></ul><hr></ul><ul id="fields" style="display:none"><hr><b>BusComp:</b> <%=bc.GetName()%><br/><b>Commit pending:</b> <%=bc.commitPending%><br/><b>Fields:</b> <%=Object.keys(bc.GetFieldList()).length%><br/><b>Row:</b> <%=bc.GetCurRowNum()==-1?0:bc.GetCurRowNum()%> of <%=bc.GetNumRows()%><%=bc.IsNumRowsKnown()?"":"+"%><br/><ul><%for(var f in r){%><li><a><%=f%></a>=<a><%=r[f]%></a></li><%}%></ul><hr></ul></li><%}%></ul></div>';function AV(){var html=new EJS({text:tmp}).render(SiebelApp.S_App);$d=$(html).dialog({modal:!0,width:1024,open:function(){$(this).find("li").find("ul[id]").hide(),$(this).find("a").click(function(){copy(this)}),$(this).find("a").contextmenu(function(){return $(this).siblings("#"+$(this).attr("data-target")).toggle(),$(this).siblings("ul[id]:not([id='"+$(this).attr("data-target")+"'])").hide(),!1}),$(this).find("button").click(function(){var s=$(this).attr("data-eval");$d.dialog("close"),eval(s)})},close:function(){$(this).dialog("destroy").remove()},buttons:[{text:"Help",click:function(){window.open("http://xapuk.com/index.php?topic=80","_blank")}},{text:"Close (esc)",click:function(){$(this).dialog("close")}}]}),$d.css("padding-left","20px").find("ul").css("padding-left","20px"),$d.find("hr").css("margin","5px"),$d.find("a").hover(function(e){$(this).css({"text-decoration":"mouseenter"==e.type?"underline":"none"})})}function copy(e){var t=$(e).text();$(e).hide().after("<input id='"+id+"i'>"),$d.find("#"+id+"i").val(t).select(),document.execCommand("copy")?($d.find("#"+id+"i").attr("disabled","disabled").css("color","red").val("Copied!"),setTimeout(function(){$d.find("#"+id+"i").remove(),$(e).show()},700)):$d.find("#"+id+"i").blur(function(){$(this).remove(),$d.find("a").show()})}$("#"+id).parent().remove(),"undefined"==typeof SiebelApp?alert("It works only in Siebel OUI session!"):"undefined"==typeof EJS?requirejs(["3rdParty/ejs/ejs_production"],AV,function(){alert("Failed to load EJS")}):AV();};void(0) 
  • You are also free to fork it. Here is a source code: Snippet
/* 
@desc advanced AboutView plugin
@author VB(xapuk.com)
@version 1.3 2018/07/10
*/

var id = "SiebelAboutView";

// template
var $d;
var tmp = ''+
'<div title="About View" id = "<%= id%>">'+
  '<% var v = SiebelApp.S_App.GetActiveView() %>'+
  '<b>Application:</b> <a><%= SiebelApp.S_App.GetName() %></a><br>'+
  '<b>View:</b> <a><%= v.GetName() %></a><br>'+
  '<b>BusObject:</b> <a><%= SiebelApp.S_App.GetActiveBusObj().GetName() %></a><br>'+
  '<% if(v.GetActiveTask()) { %>'+
    '<b>Task:</b> <a><%= v.GetActiveTask() %></a><br>'+
  '<% } %>'+
  '<b>Applets(<%= Object.keys(v.GetAppletMap()).length %>) / BusComps(<%= Object.keys(SiebelApp.S_App.GetActiveBusObj().GetBCMap()).length %>):</b><br>'+
  '<ul style="padding-left:20px">'+
    '<% for(applet in v.GetAppletMap()) { var a = v.GetAppletMap()[applet]; var bc = a.GetBusComp(); var r = bc.GetSelection() < bc.GetRecordSet().length?bc.GetRecordSet()[bc.GetSelection()]:{}; var os = "SiebelApp.S_App.GetActiveView().GetAppletMap()[\'" + applet + "\']"; var $ds = $("#" + a.GetFullId()); %>'+
      '<li>'+
        '<a data-target="controls"><b style="<% if($ds.is(":hidden")){ %>font-style:italic;<% } if(a===v.GetActiveApplet()){ %>text-decoration:underline<% } %>"><%= applet %></b></a> / '+ 
        '<a data-target="fields"><b><%= bc.GetName() %></b></a>'+
        '<ul id="controls" style="display:none">'+
          '<hr>'+
          '<b>Applet:</b> <a><%= a.GetName() %></a><br/>'+
          '<b>BusComp:</b> <a><%= bc.GetName() %></a><br/>'+
          '<b>Mode:</b> <a><%= a.GetMode() %></a><br/>'+
          '<b>Title:</b> <a><%= a.GetAppletLabel() %></a><br/>'+
          '<% if(a.GetToggleApplet()){ %>'+
            '<b>Toggle:</b> <a><%= a.GetToggleApplet() %></a><br/>'+
          '<% } %>'+
          '<b>Object Selector:</b> <a><%= os %></a><br>'+
          '<b>DOM Selector:</b> <a>$(\"<%= $ds.selector %>\")</a><br>'+
          '<b>Controls (<%= Object.keys(a.GetControls()).length %>): </b>'+
          '<ul>'+
          '<% for(control in a.GetControls()) { var c = a.GetControls()[control]; var $cds = $ds.find("[name=\'" + c.GetInputName() + "\']") %>'+
            '<li>'+
              '<a data-target="control"><b style="<% if($cds.is(":hidden")){ %>font-style:italic;<% } if(c===a.GetActiveControl()){ %>text-decoration:underline<% } %>"><%= c.GetDisplayName()||control %></b></a>'+
              '<ul id="control">'+
                '<hr>'+
                '<% if($cds.is(":visible") && $cds.is(":focusable")){ %>'+
                  '<button data-eval="$(\'<%= $cds.selector %>\').focus()">Focus</button><br>'+
                '<% } %>'+
                '<b>Control:</b> <a><%= control %></a><br>'+
                '<% if(c.GetFieldName()){ %>'+
                  '<b>Field:</b> <a><%= c.GetFieldName() %></a><br>'+
                  '<% if(r){ %>'+
                    '<b>Value:</b> <a><%= r[c.GetFieldName()] %></a><br>'+
                  '<% } %>'+
                  '<b>Immediate post changes:</b> <a><%= c.IsPostChanges() %></a><br>'+
                '<% } %>'+
                '<b>Type:</b> <a><%= c.GetUIType() %></a> <br>'+ // to decode value trhough SiebelJS.Dependency("SiebelApp.Constants");
                '<b>Input:</b> <a><%= c.GetInputName() %></a><br>'+
                '<b>Object Selector:</b> <a><%= os+".GetControls()[\'" + control + "\']" %></a><br>'+
                '<b>DOM Selector:</b> <a>$(\"<%= $cds.selector %>\")</a><br>'+
                '<% if(c.GetMethodName()){ %>'+
                  '<b>Method:</b> <a><%= c.GetMethodName() %></a><br>'+
                '<% } %>'+
                '<% if(c.GetPMPropSet() && c.GetPMPropSet().propArrayLen > 0){ %>'+
                  '<b>User Props (<%= Object.keys(c.GetPMPropSet().propArray).length %>):</b><br>'+
                  '<ul>'+
                    '<% for(p in c.GetPMPropSet().propArray){ %>'+
                      '<% if("string" === typeof c.GetPMPropSet().propArray[p]){ %>'+
                        '<li><a><%= p %></a> = <a><%= c.GetPMPropSet().propArray[p] %> </a></li>'+
                      '<% } %>'+
                    '<% } %>'+
                  '</ul>'+
                '<% } %>'+
                '<% if(c.GetMethodPropSet() && c.GetMethodPropSet().propArrayLen > 0){ %>'+
                  '<b>Method PS (<%= Object.keys(c.GetMethodPropSet().propArray).length %>):</b>'+
                    '<ul>'+
                      '<% for(p in c.GetMethodPropSet().propArray){ %>'+
                        '<% if("string" === typeof c.GetMethodPropSet().propArray[p]){ %>'+
                          '<li><a><%= p %></a> = <a><%= c.GetMethodPropSet().propArray[p] %> </a></li>'+
                        '<% } %>'+
                      '<% } %>'+
                    '</ul>'+
                  '<% } %>'+
                '<hr>'+
              '</ul>'+
            '</li>'+
          '<% } %>'+
          '</ul>'+
          '<hr>'+
        '</ul>'+
        '<ul id="fields" style="display:none">'+
          '<hr>'+
          '<b>BusComp:</b> <%= bc.GetName() %><br/>'+
          '<% if(r && r.hasOwnProperty("Id")){ %>'+
            '<b>Row Id:</b> <a><%= r.Id %></a><br/>'+
          '<% } %>'+
          '<% if(r && r.hasOwnProperty("Created")){ %>'+
            '<b>Created:</b> <a><%= r.Created %></a><br/>'+
          '<% } %>'+
          '<% if(r && r.hasOwnProperty("Updated")){ %>'+
            '<b>Updated:</b> <a><%= r.Updated %></a><br/>'+
          '<% } %>'+
          '<b>Commit pending:</b> <%= bc.commitPending %><br/>'+
          '<b>Fields:</b> <%= Object.keys(bc.GetFieldList()).length %><br/>'+
          '<b>Row:</b> <%= bc.GetCurRowNum()==-1?0:bc.GetCurRowNum() %> of <%= bc.GetNumRows() %><%= bc.IsNumRowsKnown()?"":"+" %><br/>'+
          '<ul>'+
            '<% for(var f in r){ %>'+
              '<li><a><%= f %></a> = <a><%= r[f] %></a></li>'+
            '<% } %>'+
          '</ul>'+
          '<hr>'+
        '</ul>'+
      '</li>'+
    '<% } %>'+
'</ul>'+
'</div>';

// to support single session
$("#" + id).parent().remove();

// show the dialog
function SiebelAboutView(){

    var html = new EJS({text: tmp}).render(SiebelApp.S_App);

    $d = $(html).dialog({
        modal: true,
        width: "1024",
        open:function(){
            // hide all expandable ULs by default
            $(this).find("li").find("ul[id]").hide();
            // attempt to copy span content (click)
            $(this).find("a").click(function(){
                copy(this);
            });
            // expand (right click)
            $(this).find("a").contextmenu(function(){
                $(this).siblings("#"+$(this).attr("data-target")).toggle();
                $(this).siblings("ul[id]:not([id='"+$(this).attr("data-target")+"'])").hide();
                return false;
            });
            // focus on control
			$(this).find("button").click(function(){
				var str = $(this).attr("data-eval");
				$d.dialog('close');
				eval(str);
			});
        },
        close: function(){
            $(this).dialog('destroy').remove();
        },
        buttons: [
            {
               text:'Help',
               click: function(){
                   window.open("http://xapuk.com/index.php?topic=80", "_blank");
               }
            },
            {
               text:'Copy (left click)',
               disabled: true
            },
            {
               text:'Expand (right click)',
               disabled: true
            },
            {
               text:'Close (esc)',
               click: function() {
				 $(this).dialog('close');
               }
            }
        ]
    });
	
	// styling
	$d.css("padding-left","20px");
	$d.find("ul").css("padding-left","20px");
	$d.find("hr").css("margin","5px");
	$d.find("a").hover(function(e){
		$(this).css({"text-decoration":e.type=="mouseenter"?"underline":"none"});
	});
}

// copy value
function copy(scope){
	// replacing link with intput and select the value
	var val = $(scope).text();
    $(scope).hide().after("<input id='" + id + "i'>");
    $d.find("#" + id + "i").val(val).select();
    // attempt to copy value
    if (document.execCommand("copy", false, null)){
    	// if copied, display a message for a second
        $d.find("#" + id + "i").attr("disabled", "disabled").css("color","red").val("Copied!");
        setTimeout(function(){
        	$d.find("#" + id + "i").remove();
        	$(scope).show();
        }, 700);
    }else{
    	// if failed to copy, leave input until blur, so it can be copied manually
		$d.find("#" + id + "i").blur(function(){
			$(this).remove();
			$d.find("a").show();
		});    	
    }
}

if ("undefined" === typeof SiebelApp || "undefined" === typeof SiebelApp.S_App){
	alert("Please launch Siebel application first.");
}else if ("undefined" === typeof EJS){
	var src = "3rdParty/ejs/ejs_production";
	requirejs([src], SiebelAboutView, function(){alert("Failed to load EJS library ! \n" + src);});
}else{
	SiebelAboutView();
}
  

Quick tips
  • All values can be copied with a mouse click.
  • Values in bold can be expanded with right click.
  • Underlined style indicates active items.
  • Italic style indicates hidden items.
  • Unlike the original About View (server side) it works with popup applets and in expired session.

A new version is available here.

Since OpenUI release, browser console became a powerful tool in hands of confident Siebel developer. In this topic I'll give you an idea how to organise your browser code snippets and will share couple of examples how to take an advantage of SiebelApp object.


Browser console


Traditional way of injecting browser code.

How to run: F12 => Console tab => type the command or use up key to flip through history => enter


SiebelApp.S_App.GetActiveView().GetActiveApplet().GetName();

Chrome code snippets


Useful if you need to store more than oneliners.

How to run: F12 => Source tab => Snippets => right click => run

Here is an example why you might need it.


console.log(SiebelApp.S_App.GetActiveView().GetName());
var am = SiebelApp.S_App.GetActiveView().GetAppletMap();
for (sAppletName in am) {
  console.log('\t' + sAppletName + '(' + am[sAppletName].GetBusComp().GetName() + ')');
}

Bookmarklets


Easiest way to run a code snippet. It is not only takes one click to run your code but it also allows 'endless' features (fancy UI, working with clipboard, history of queries). I see bookmarklets as an open source, cross-browser, cross-environment Siebel plugin. If you manage to use Chrome code snippets to build and debug snippets and then compile them into bookmarklets.

I usually use Chrome snippets as a source code vault and as GUI to build and debug my code. Once the code is stable, I make it a bookmarklet and enjoy it.

How to create: Linearise your JS code, remove comments or simply minify it (for example, JSCompress or JS/CSS Minifier, thanks Emma!), escape quotes and finally wrap it with "javascript:{...};void(0)". Here is how the code from above example will look like as a bookmarklet:


javascript:{var s=SiebelApp.S_App.GetActiveView().GetName();var am=SiebelApp.S_App.GetActiveView().GetAppletMap();for(sAppletName in am){s+='\n\t'+sAppletName+'('+am[sAppletName].GetBusComp().GetName()+')';}alert(s);};void(0)

You can always use online bookmarklet converters. Here are couple examples:

How to install: Easiest way is to drag & drop a link on your favourites toolbar. I'm a bookmarklet Drag & Drop me. Here is a full article on how to install bookmarklets for different browsers.

How to run: Click

Check out how powerful and convenient bookmarklets could be:

/* 
@desc fancy UI wrapper for GetProfileAttr Siebel function
@author VB(xapuk.com)
@version 1.1 2018/06/08
*/
if ("undefined" == typeof SiebelApp) {
    alert("It works only in Siebel OUI session!");
}

// snippet id
var id = "SiebelProfileAttr";

// localStorage to store the history
var aHist = window.localStorage[id] ? JSON.parse(window.localStorage[id]) : [];

// just in case (experimental)
$("#" + id).parent().remove();

// constructing dialog content
var s = '<div title="Get Profile Attribute">';
s += '<input id = "' + id + '" type="text" list="' + id + 'List" style="width:100%" value="' + (aHist.length ? aHist[0] : "") + '">';
s += '<datalist id="' + id + 'List">';
for (var i = 0; i < aHist.length; i++){
    s += '<option>' + aHist[i] + '</option>';
}
s += '</datalist>';
s += '<input id="' + id + 'Out" type ="text" style="display:none">';
s += '<ul></ul></div>';

// open dialog
var $d = $(s).dialog({
    modal: true,
    width: 640,
    open: function() {
        $('#' + id).focus().select(); // autofocus
    },
    close: function() {
        $(this).dialog('destroy').remove();
    },
    buttons: [{
        text: 'Get (Enter)',
        click: go
    }, {
        text: 'Close (Esc)',
        click: function() {
            $(this).dialog('close');
        }
    }]
});

listHistory();

function go() {
    var name = $d.find('#' + id).val();
    if (name) {
        // moving recent query to the top
        if (aHist.indexOf(name) > -1) {
            aHist.splice(aHist.indexOf(name), 1);
        }
        aHist.unshift(name);
        window.localStorage[id] = JSON.stringify(aHist);

        //rerender history
        listHistory();
    }
    return name;
}

// print a list of recent queries
function listHistory() {
    var $ul = $d.find("ul").empty();
    for (var i = 0; i < aHist.length && i < 5; i++) {
        // five recent values
        $ul.append('<li><b>' + aHist[i] + '</b> = <a href="#">' + SiebelApp.S_App.GetProfileAttr(aHist[i]) + '</a></li>');
    }
    // copy value on click
    $d.find("a").click(function(event) {
        var val = $(this).html();
        $("#" + id + "Out").show().val(val).select();
        var r = document.execCommand("copy");
        $("#" + id + "Out").hide();
        if(r){
            $(this).hide().after('<span id="tmp" style="color:red">Copied!</span>');
            setTimeout(function(){
                $d.find("a").show();
                $d.find("#tmp").remove();
            }, 500);
        }
    });
}

// key bindings
$d.keyup(function(event) {
    // enter
    if (event.keyCode === 13) {
        go();
    }
});
/* 
@desc fancy wrapper for GotoView Siebel function
@author VB(xapuk.com)
@version 1.0 12/06/2018
*/
if ("undefined" == typeof SiebelApp){
    alert("It works only in Siebel OUI session!");
}

// snippet id
var id = "SiebelGotoView";

// localStorage to store the history
var aHist = window.localStorage[id]?JSON.parse(window.localStorage[id]):[];

// just in case (experimental)
$("#" + id).parent().remove();

// constructing dialog content
var s = '<div title="Goto View">';
s += '<input id = "' + id + '" type="text" list="' + id + 'List" style="width:100%" value="' + (aHist.length?aHist[0]:"") + '">'; // most recent
s += '<datalist id="' + id + 'List">';
for (var i =0; i < aHist.length; i++){ // full history into unbounded picklist
    s += '<option>' + aHist[i] + '</option>';
}
s += '</datalist><ul>';
for (var i =0; i < aHist.length && i < 5; i++){ // five recent values as links
    s += '<li><a href="#">' + aHist[i] + '</a></li>';
}
s += '</ul></div>';

// open dialog
var $d = $(s).dialog({
    modal: true,
    width: 640,
    open: function() {
        $('#' + id).focus().select(); // autofocus
    },
    close: function() {
        $(this).dialog('destroy').remove();
    },
    buttons: [{
        text: 'Go (Enter)',
        click: function(){
            go($d.find('#' + id).val());
        }
    }, {
        text: 'Close (Esc)',
        click: function() {
            $(this).dialog('close');
        }
    }]
});

// GotoView
function go(name) {
    if (name){
        // moving recent view to the top
        if (aHist.indexOf(name) > -1){
           aHist.splice(aHist.indexOf(name),1);
        }
        aHist.unshift(name);
        window.localStorage[id] = JSON.stringify(aHist); 
        $d.dialog('close');
        SiebelApp.S_App.GotoView(name); //running GotoView command
    }
}

// running GotoView on Enter
$d.keyup(function(event) {
    // enter
    if (event.keyCode === 13) { 
        go($d.find('#' + id).val());
    }
});

// running GotoView on link click
$d.find("a").click(function(event) {
    go($(this).html());
});
Stay tuned - best snippets are yet to come :)

Not sure if blog is a right word though. You know like sometimes we maintain a library of frequently used code snippets, templates, tools, useful links, ideas to research someday, etc. Starting with a single text file it grows into a massive "ARCHIVE" folder. So, yes that is what it is - a vault of my code snippets, ideas, and tools. I finally made that folder my online playground and started sharing some of these. Really hope it will be of some use to anyone.

Tools - several converters I use almost every day and will be happy if you guys find it useful. Could bind variables into SQL statement or convert a PropertySet from the logs or HTTP responses into readable XML, etc. Check out for help() and example() commands over there.

Blog - some ideas and code snippets, I think worth sharing and I had free time to describe a little. Actually, I'll be describing a very little and just let the code samples talk for me. I'm always happy to answer questions, get feedback or hear your thoughts.

Most of my code snippets are also available on GitHub and free to use as is or to fork.

In case you wonder, "Xapuk" is a transliteration from my russian nickname Харик, pronounced like [Harik].

 Since last versions of Siebel don't allow logging in through GET requests (credentials in URL address), it is now a bit more challenging to have a "log into Siebel" browser bookmarks. However, we can acheive the same by sending a POST request. Check it out in the below example.

Quick description:

A single-file page to orginise all Siebel links which allow you to log into Siebel thin client in one click.

Setup instruction:

  1. Download a Sample page (right click - Save link as ... / Save target as ...).
  2. Open the page in text editor, and update it with your structure and credentials following inline comment tips:
    • each level of grid can have url, usr, pwd attributes defined
      • url is concatinated from top to bottom
      • usr and pwd give a priority to the deepest level
      • usr = '*' when you want to specify credentials manually
  3. Open the page in browser and make it your home page.

Couple tricks how to use Notepad++ to analyse Siebel logs


Plain text

(Ctrl+F, Search All Document)

Workflow steps
Instantiating step definition
Task steps
Task engine requested to navigate to next step
Browser calls server (both user and system)
SWECmd=

Regex

(Regular expression option should be checked)

Workflows and tasks together
(Instantiating .* definition)|(Task engine requested to navigate to next step)
Let's say you want to know which processes down the stack are doing updates
(Instantiating .* definition)|(Task engine requested to navigate to next step)|(UPDATE)
This one traces where WF starts and ends with all the steps:
(Stopping process instance of)|(Executing process definition)|(Instantiating step)

Ok, you've got the idea.

EAI Data Maps
Executing the (Component|Field) Map
Client-Server method calls
(?<=SWEMethod=).*?(?=\;)
Low performance (general) / operations which took more than a second
(\s[1-9]|\d{2,})\.\d*\sseconds
Low performance SQL execution
SQL Statement .* Time for SQL Cursor with ID \w*: [^0]
Low performance SQL fetch
(.*)(\d{4}\-\d{2}\-\d{2}) (\d{2}\:\d{2}\:\d{2})(.*)Fetch All Time: [^0](d|.*) seconds.
Time gaps between two lines (experimental)
(.*)(\d{4}\-\d{2}\-\d{2}) (\d{2}\:\d{2}\:\d{2})(.*).\s*(.*)\2 (?!\3)(.*)

Starting a series of SQLs to check the consistency of Siebel repository object.

And the first SQL is to identify Integration Component fields referencing non-existing BusComp fields:


with io as (  -- IO/IC/Field
  SELECT io.repository_id, io.name IO, ic.name IC, ic.ext_name BC, nvl(ifu.value, iff.ext_name) FIELD
  FROM siebel.S_INT_OBJ io
       join siebel.S_INT_COMP ic on io.row_id = ic.int_obj_id
            and ic.inactive_flg = 'N'
       join siebel.S_INT_FIELD iff on iff.int_comp_id = ic.row_id 
            and iff.inactive_flg = 'N' 
            and iff.field_type_cd = 'Data'
       left join siebel.S_INTFLD_UPROP ifu on ifu.int_field_id = iff.row_id  -- MVF
            and ifu.inactive_flg = 'N' 
            and ifu.name in ('MVGFieldName', 'AssocFieldName')
  where io.base_obj_type = 'Siebel Business Object'
        and io.inactive_flg = 'N'), 
        
bc as ( -- BC/Field
  select bc.repository_id, bc.name BC, f.name FIELD 
  from siebel.S_BUSCOMP bc 
      join siebel.S_FIELD f on f.buscomp_id = bc.row_id 
        and f.inactive_flg = 'N')
  
select io.io, io.ic, io.field, io.bc 
from io 
  join siebel.S_REPOSITORY r on r.row_id = io.repository_id
where r.name = 'Siebel Repository'
    and io.field not in ('Id','Conflict Id','Created','Created By','Mod Id','Updated','Updated By', 'SSA Primary Field', 'IsPrimaryMVG') -- excluding system fields
    and (bc, field) not in (select bc, field from bc where repository_id = r.row_id)
and io.io like 'AMS%'; -- to filter by your project preffix

Do you remember a pain when calculated expression splits across dozens calculated fields?

That SQL will help you to visualise complex dependencies of calculated fields:


SELECT level,
  SYS_CONNECT_BY_PATH(t.NAME, '/') PATH,
  t.*
FROM
  (SELECT f.NAME, f.CALCVAL
   FROM siebel.S_FIELD f
     JOIN siebel.S_BUSCOMP b ON b.row_id  = f.BUSCOMP_ID
   WHERE b.name = 'Order Entry - Orders' -- bus comp name
  ) t
START WITH name = 'Payment Order Total' -- field name
CONNECT BY prior CALCVAL LIKE '%[' || NAME || ']%'
ORDER BY 2;

 

It will show you all subsequent fields for vanilla calculated field [Payment Order Total] (BusComp = Order Entry - Orders) in recursion:

Here is a full version of SQL with more details


with t as (
  select f.NAME, f.CALCVAL,
    DECODE(f.MULTI_VALUED, 'Y', f.MVLINK_NAME || '.' || f.DEST_FLD_NAME, 
      DECODE(f.CALCULATED, 'Y', f.CALCVAL, 
        DECODE(f.JOIN_NAME, null, b.TABLE_NAME || '.' || f.COL_NAME, 
          DECODE(j.DEST_TBL_NAME, null, f.JOIN_NAME || '.' || f.COL_NAME,
            j.DEST_TBL_NAME || '.' || f.COL_NAME)))) VAL
  from siebel.S_FIELD f
    join siebel.S_BUSCOMP b on b.row_id = f.BUSCOMP_ID
    left join siebel.S_JOIN j on j.NAME = f.JOIN_NAME AND j.BUSCOMP_ID = f.BUSCOMP_ID
  where b.name = 'Order Entry - Orders')
select distinct level, SYS_CONNECT_BY_PATH(t.NAME, '/') PATH, t.NAME, t.VAL from t
start with name = 'Payment Order Total'
connect by prior CALCVAL LIKE '%[' || NAME || ']%'
order by 2;

Copy below script in browser console (F12) and run it once. It will keep Siebel client alive during the day.


function keepAlive(iHour, iNum)
{
	var iStep = 60; // will send the request every minnute
	iNum = iNum>0?iNum:0;

	if (iHour*60*60 > iNum*iStep){
		setTimeout(function(){
			if (typeof(SiebelApp.S_App.GetProfileAttr("ActiveViewName")) != "undefined"){
				SiebelJS.Log("keep alive / " + iNum + " / " + (new Date()));
				keepAlive(iHour, ++iNum);
			}
		}, iStep * 1000);
	}
}
keepAlive(8); // 8 = number of hours you want Siebel to live

It is helpful when you are working with Siebel client all day long and you don't want to re-login after a short pause. Very handy during Siebel frontend development.


Updates

A fancy version with a pulsing indicator and on / off toggle.

  • Bookmarklet link: About View
  • Bookmarklet source: Snippet
  • javascript:{function keepAlive(e,i){i=0<i?i:0,console&&console.log("keep alive / "+i+" / "+new Date),60*i<60*e*60?(0===$("#SiebelKeepAlive").length?$("body").append("<div id='SiebelKeepAlive' style='position:fixed;top:5px;left:5px;background-color:#ff5e00;border:solid 2px;border-radius:5px;padding:5px'><b>KA</b></div>"):$("#SiebelKeepAlive").fadeTo(100,1),$("#SiebelKeepAlive").fadeTo(54e3,.2),window.timerId=setTimeout(function(){void 0!==SiebelApp.S_App.GetProfileAttr("ActiveViewName")&&keepAlive(e,++i)},6e4)):keepAlive_stop()}function keepAlive_stop(){clearTimeout(window.timerId),delete window.timerId,$("#SiebelKeepAlive").remove(),console.log("keep alive time stopped")}"undefined"==typeof SiebelApp?alert("It works only in Siebel OUI session!"):void 0===window.timerId?keepAlive(4):keepAlive_stop();};void(0) 
  • Source code: Snippet
/* 
@desc Keeps Siebel session alive for a certain amount of time
@author VB(xapuk.com)
@version 2.0 2018/07/20
*/


if ("undefined" == typeof SiebelApp){
    alert("It works only in Siebel OUI session!");
}else{
	if ("undefined" === typeof window.timerId){
		keepAlive(4); // number of hours you want Siebel to live
	}else{
        keepAlive_stop();
	}
}

function keepAlive(iHour, iNum)
{
	var iStep = 60; // frequency in secs
	iNum = iNum>0?iNum:0;
	if (console) console.log("keep alive / " + iNum + " / " + (new Date()));
	if (iHour*60*60 > iNum*iStep){
	    if($("#SiebelKeepAlive").length === 0){
	        $("body").append("<div id='SiebelKeepAlive' style='position:fixed;top:5px;left:5px;background-color:#ff5e00;border:solid 2px;border-radius:5px;padding:5px'><b>KA</b></div>");
	    }else{
	        $("#SiebelKeepAlive").fadeTo(100, 1);
	    }
	    $("#SiebelKeepAlive").fadeTo(iStep * 900, 0.2);
		window.timerId = setTimeout(function(){
			if (typeof(SiebelApp.S_App.GetProfileAttr("ActiveViewName")) != "undefined"){
				keepAlive(iHour, ++iNum);
			}
		}, iStep * 1000);
	}else{
		keepAlive_stop();
	}
}

function keepAlive_stop(){
    clearTimeout(window.timerId);
    delete window.timerId;
    $("#SiebelKeepAlive").remove();
    console.log("keep alive time stopped");
}
 

Here I want to share the way you can prettify Siebel built-in code editor and make Client-side Business Service development process a bit more pleasant.

Regarding to to my choice of online code editor. After a quick loop through "top 5 editors", I've picked AceJS - mostly because I had a recent experience working with it.


1. Download AceJS

After you download lib (https://github.com/ajaxorg/ace-builds/), place the content of "src-min-noconflict" subfolder in "\PUBLIC\enu\23048\SCRIPTS\3rdParty\ace\".


2. Create PM/PR files

Create Presentation Model and Physical Renderer files:

siebel/custom/CSBS_PM.js


if (typeof (SiebelAppFacade.CSBS_PM) === "undefined") {
	SiebelJS.Namespace("SiebelAppFacade.CSBS_PM");
	define("siebel/custom/CSBS_PM", ["siebel/pmodel"],
		function () {
			SiebelAppFacade.CSBS_PM = (function () {
				
				function CSBS_PM(pm) {
					SiebelAppFacade.CSBS_PM.superclass.constructor.apply(this, arguments);
				}
				
				SiebelJS.Extend(CSBS_PM, SiebelAppFacade.PresentationModel);

				CSBS_PM.prototype.Init = function () {
					SiebelAppFacade.CSBS_PM.superclass.Init.apply(this, arguments);
				}
				
				CSBS_PM.prototype.Setup = function (propSet) {
					SiebelAppFacade.CSBS_PM.superclass.Setup.apply(this, arguments);

					this.AddProperty("ScriptValue", ""); // property to pass field value PM->PR
					bc = this.Get("GetBusComp");
					var sField = "Script"; // hardcoded field name to attach ace plugin
					this.AddProperty("ScriptFieldName", sField);

					// update ace editor value everytime script field value changes
					this.AddMethod("GetFormattedFieldValue", function (control) {
						if (control.GetFieldName() == sField && bc.GetFieldValue(sField) != this.Get("ScriptValue")) {
							this.SetProperty("ScriptValue", bc.GetFieldValue(sField));
						}
					}, {
						sequence: false,
						scope: this
					});
				}
				
				return CSBS_PM;
				
			}());
			return "SiebelAppFacade.CSBS_PM";
		})
}

siebel/custom/CSBS_PR.js


if (typeof (SiebelAppFacade.CSBS_PR) === "undefined") {
	SiebelJS.Namespace("SiebelAppFacade.CSBS_PR");
	define("siebel/custom/CSBS_PR", ["3rdParty/ace/ace", // determines dependencies (path to ace.js file)
									 "siebel/phyrenderer"],
		function () {
			SiebelAppFacade.CSBS_PR = (function () {

				function CSBS_PR(pm) {
					SiebelAppFacade.CSBS_PR.superclass.constructor.apply(this, arguments);
				}

				SiebelJS.Extend(CSBS_PR, SiebelAppFacade.PhysicalRenderer);

				CSBS_PR.prototype.Init = function () {
					SiebelAppFacade.CSBS_PR.superclass.Init.apply(this, arguments);
				}

				CSBS_PR.prototype.ShowUI = function () {
					SiebelAppFacade.CSBS_PR.superclass.ShowUI.apply(this, arguments);

					var pm = this.GetPM(); // to use in global scope functions
					var bc = pm.Get("GetBusComp");
					var sField = pm.Get("ScriptFieldName");

					// get original control
					var oOrig = $("textarea[name='" + pm.Get("GetControls")[sField].GetInputName() + "']");

					// add control for ace editor
					var sNewId = "ace_code_editor";
					SiebelJS.Log($(oOrig).parent().after('<div id="' + sNewId + '"></div>'));
					var oNew = $("#" + sNewId);

					// attach ace editor
					var oAce = ace.edit(sNewId);
					oAce.setTheme("ace/theme/monokai");
					oAce.getSession().setMode("ace/mode/javascript");
					oAce.$blockScrolling = Infinity;

					// to be replaced with css file
					oNew.css("height", oOrig.height() + "px"); // copy control height 
					oOrig.remove(); // remove orig control
					$("#s_" + pm.Get("GetFullId") + "_div").find(".mceLabel").remove(); // remove labels

					// copy value from ace editor into the field
					oAce.getSession().on('change', function () {
						bc.SetFieldValue(sField, oAce.getValue());
					});

					// copy field value to ace editor
					this.AttachPMBinding("ScriptValue", function () {
						field_value = pm.Get("ScriptValue");
						if (field_value != oAce.getValue()) {
							oAce.setValue(field_value);
							oAce.gotoLine(1); // first line by default
						}
					});
				}

				CSBS_PR.prototype.BindEvents = function () {
					SiebelAppFacade.CSBS_PR.superclass.BindEvents.apply(this, arguments);
				}

				CSBS_PR.prototype.BindData = function () {
					SiebelAppFacade.CSBS_PR.superclass.BindData.apply(this, arguments);
				}

				return CSBS_PR;
			}());
			return "SiebelAppFacade.CSBS_PR";
		})
}

3. Manifest administration

Don't forget to attach PM/PR files to applet (Business Service Script Editor Applet2) on Application -> Manifest Administration view.


What's next ...

Optimise a view layout (custom .SWT?).

Maybe attach a custom CSS to prettify a view.

Put BS simulator applets on the same page and pre-default BS Name there.

Enjoy your runtime playground!

Full-stack software developer with 20+ years of experience.

From front-end technologies to DB optimisation.

Since 2005 primarily focusing on Siebel.

Please, feel free to contact me. I'm always open to challenges.

Email address: [email protected]

My LinkedIn profile: https://www.linkedin.com/in/vababkin/

Skype: iXapuk

GitHub profile: https://github.com/xapuk