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!

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

 

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

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!