I'm not sure if I'm correct here but I think you can eliminate 2 for-loops and the call to getArticle():

In SD031 there should be only one key for "x" (should be a 1:1 relationship), also try to avoid setting up a list just to .count() to see if "dataExists". You should use something like $data (in ^SB02906 there could be one dataset for every item you sale, for every customer). That should speed everything up a little.

My approach:

set a = $order(^SD031S("0","7",x,a))

        if (a '="") {

            set b = $order(^SD031S("0","7",x,a,b))

            if (b '= "") {

                if ($piece($get(^SD031("0",a,b,1)),Y,9) = x) {

                    set c = a

                    set d = ""

                    for {

                        set d = $order(^FA060S("0","22",c,b,c,d))

                        if (d = "") { quit }

                        if ($piece($get(^FA060("0",c,d,2),""),Y,25) = b) {

                            #dim innerObject = ##class(%ZEN.proxyObject).%New()

                            set innerObject.d = d

                            set innerObject.c = c

                            set k = $o(^SB02906("0",c,d,""))

                            if (k'="") {

                                set innerObject.dataExist = ($d(^SB02906("0",c,d,k,"EUR","0","1"))'=0)

                            } else {

                                set innerObject.dataExist = 0

                            }

                            do responseList.Insert(innerObject)

                        }

                    }

                }     

            }   

        }

Also that caching problem should not exist in production because FA060 and SD031 are read for almost everything in your ERP so it stays in the cache. Otherwise you probably need to create a background-job that reads a few global-nodes every 5-10 minutes to get it cached.

Would be nice to have it. 

$$$TOE(sc, method()) gives a warning "Consider inlining/replacing this macro to make the code more readable".

It is more readable but I want to use the macro: 

set sc = method()

if ('sc) {

throw ##class(%Exception.StatusException).CreateFromStatus(sc)

}

It's just a rule to eliminate macros in my opinion :/

Other example:

Method has too many lines (93 > 50). Am I not allowed to write methods bigger 50 lines?

Method declares too many variables? Also a rule I can't understand. Sure you need to minimize but it's not always possible. 

I really like sonarlint but I can't use it at 100% because I can't disable specific rules in specific circumstances, 

I think that should work much better. Didn't test but that is how I would do it with CSP-Method #call / #server

It's also asynch so you browser still reacts while getting the data. 

I'm using innerHTML instead of the table-functions. innerHTML is the fastest way to render HTML actually. It's faster than jQueries $('query').html('htmlcontent');

<!--Demo of (re-)loading a table dynamically in a page-->
<html>
<head>
<title>   Cache Server Page To Demo Table Re-Render</title>
</head>
<body>
<table id="TEST" border="1">
  <thead>
    <tr>
        <td>SSN</td><td>Name</td>
    </tr>
  </thead>
  <tbody id="tablebody">
  </tbody>
</table>
<hr>
<!-- changed to call bc asynchronous -->
<input type="Button" name="render" value="Create Table"
    OnClick="#call(..renderTable())#;">
 
<script language = Cache method = renderTable arguments = "">
    #dim data = []
    #dim sql = "SELECT SSN,Name FROM Sample.Person"
    #dim rset AS %SQL.StatmentResult = ##class(%SQL.Statement).%ExecDirect(, sql)
    while (rset.%Next()) {
        d data.%Push({
            "SSN": (rset.%GetData(1)),
            "Name": (rset.%GetData(2))
        });
    }
    // create callback-function for rendering
    &js<
        function cb(data) {
            var tableBody = document.getElementById("tablebody");
            // clear tablebody
            var html = [];
            tableBody.innerHTML = html.join('');
            for (var i in data) {
                html.push('<tr><td>'+data[i].SNN+'</td><td>'+data[i].Name+'</td></tr>');
            }
            // innerHTML is way faster than using the functions from table or creating javascript objects like HTMLTableRow or innerText
            tableBody.innerHTML = html.join('');
        };
    >
    // calling the cb with data
    w !,"cb("_data.%ToJSON()_")"
</script>
         
</body>
</html>

Hi,

this should give you an idea on how to achieve "popups" in HTML. This code is untested but it should give you a start.
First of all you need 2 containers (DIV) at the end of your document:

<div id="backdrop" class="backdrop"></div>

<div id="popup" class="popup">
    <div id="popup_container"></div>
    <button id="popup_btn1"></button>
    <button id="popup_btn2"></button>
</div>


in css you need 4 classes:

.backdrop {
    position: fixed;
    opacity: 0.3;
    background-color: rgb(0,0,0);
    top: 0;
    left: 0;
    width:100%;
    height: 100%
    display: none;
}

.backdrop.active {
    display: block;
}

.popup.active {
    display:block;
}

.popup {
    display:none;
    <!-- add your styling here -->
}

In Javascript you need something like this:

function showConfirm(message, callback) {
    showPopup(message, "Yes", "No", callback);
}

function showPopup(content, button1desc, button2desc, callback) {
    $('#popup').addClass("active");
    $('#backdrop').addClass("active");
    $('#popup_container').html(content);
    $('#popup_btn1').html(button1desc);
    if (button2desc) {
        $('#popup_btn2').html(button2desc).show();
    } else {
        //If we don't get a 2nd description it's a alert-message with one button
        $('#popup_btn2').hide();
    }
    $('#popup_btn1').off().on('click', function() {
        disable(); //First disable our popup
        if (callback) callback(0); //Returns the index of the button clicked
    });

    $('#popup_btn2').off().on('click', function() {
        disable();
        if (callback) callback(1);
    });

    var disable = function() {  
         $('#popup').removeClass("active");
         $('#backdrop').removeClass("active");
         $('#popup_container').html('');
    }
}

//Example of calling the confirm-message

showConfirm("Do you really want to delete?", function(choice) {
    if (choice===0) 
        alert("You pressed yes");
    } else {
        alert("You pressed no");
    }
});

Edit: You can achieve this also by using native-javascript: document.getElementById("popup_container").innerHTML = content; for example

Hei,

if you want to use Darkest-Dark theme:

Go to <eclipse-workspace>/.metadata/.plugin/org.eclipse.core.runtime/.settings/com.intersys.eclipse.studio.ui.prefs

and add this. I didn't found all colors but It's a good start. Edit theme in preferences if you find a color that you don't like or maybe isn't set by me. You can also reset to default-settings by removing every Studio_Color-Setting.

I also tweaked the colors for specific things because using the theme strictly results in too much blue ($-commands was blue etc.)

Studio_Color_BG_false=0,0,0

Studio_Color_BG_true=37,37,37

Studio_Color_brace=255,255,255

Studio_Color_bracket=255,255,255

Studio_Color_classmethodname=189,214,255

Studio_Color_classname=210,81,81

Studio_Color_collationname=189,214,255

Studio_Color_cosBitlogicOperator=210,81,81

Studio_Color_cosClassName=239,191,143

Studio_Color_cosCommandWordDeprecated=210,81,81

Studio_Color_cosCommandword=210,81,81

Studio_Color_cosDollarName=210,81,81

Studio_Color_cosDollarNameDeprecated=210,81,81

Studio_Color_cosEmbedDelim=0,128,0

Studio_Color_cosEmbeddingStartOrEnd=210,81,81

Studio_Color_cosExtrinsic=210,81,81

Studio_Color_cosGlobalname=141,203,226

Studio_Color_cosGlobalname_bold=true

Studio_Color_cosIOFormat=255,255,255

Studio_Color_cosJsonBrace=255,255,255

Studio_Color_cosJsonBracket=255,255,255

Studio_Color_cosJsonKeyword=210,81,81

Studio_Color_cosKeyword=210,81,81

Studio_Color_cosLabel=210,81,81

Studio_Color_cosLabelname=141,203,226

Studio_Color_cosLocalname=136,214,255

Studio_Color_cosMacroArgsAnything=136,214,255

Studio_Color_cosMacroCall=210,81,81

Studio_Color_cosOperator=255,255,255

Studio_Color_cosPPCommand=141,203,226

Studio_Color_cosPPFunc=210,81,81

Studio_Color_cosPattern=210,81,81

Studio_Color_cosSQLFieldName=210,81,81

Studio_Color_cosstring=255,198,0

Studio_Color_csqlIdentifier=255,198,0

Studio_Color_delim=255,255,255

Studio_Color_foreignkeyname=121,171,255

Studio_Color_htmlAttributeName=121,171,255

Studio_Color_htmlAttributeValue=255,255,255

Studio_Color_indexname=189,214,255

Studio_Color_indexpropertyname=255,255,255

Studio_Color_jsKeyword=141,203,226

Studio_Color_jsOperator=255,255,255

Studio_Color_jsString=255,198,0

Studio_Color_jsonString=255,198,0

Studio_Color_methodname=255,255,255

Studio_Color_name=255,255,255

Studio_Color_number=129,180,71

Studio_Color_parameter=121,171,255

Studio_Color_parametername=189,214,255

Studio_Color_paren=255,255,255

Studio_Color_propertyname=189,214,255

Studio_Color_queryname=189,214,255

Studio_Color_sqlstring=255,255,128

Studio_Color_storagename=189,214,255

Studio_Color_triggername=189,214,255

Studio_Color_udlDocComment=201,222,12

Studio_Color_udlKeyword=80,175,226

Studio_Color_udlMCKeywordName=255,255,255

Studio_Color_udlMCKeywordValue=129,180,71

Studio_Color_udlNumericLiteral=129,180,71

Studio_Color_udlStringLiteral=255,198,0

Studio_Color_xdataname=189,214,255

Studio_Color_xmlAttributeName=255,255,255

I prefer REST. 

The built-in transportation is good way for ZEN but the fact that the most plugins can't handle it well is a good reason to use REST. Some plugins just want an URL to do a simple jQuery-AJAX-Call and parsing that into a callback function. I some cases you can submit a function serving the data instead of an URL but working that around that is a bit harsh and not really comfortable in my opinion (Typeahead for example). 

Let's assume that you code a ZEN-App working with Cordova and in the Browser as well. The data comes from the built-in feature of ZEN-Mojo. Everything works fine. 

Now a new iOS or Android-Update is distributed that have new security guidelines and Cordova dies out of it (maybe some stuff in the webview are not allowed anymore but is needed). That's why I keep my data-transportation as flexible as possible. Always thinking of "This 3rd party tool is maybe not supported anymore in the future. What are my alternatives?". In this case a native app without ZEN. 

You can still use the built-in feature by calling the methods that the REST calls. You are in the CSP-Enviroment in both cases. 

We are using Hashtags to load the templates.  

Thats the URL for example:

www(.)mypage.com/myzenapp/mojoclass.cls#Login?r=Dashboard

or

www(.)mypage.com/myzenapp/mojoclass.cls#Dashboard

or

www(.)mypage.com/myzenapp/mojoclass.cls

Our custom-script CAJAXNavigation uses onHashChange-Callback (that we bound to zenPage.onHashChanged). There we change the layout with pop and push document. This works very well with the browser history stack. The browser manages hashtag-changes automatically.

With this concept we are changing the layout by simply clicking an anchor with href="#MyLayout"

 <mojo:documentView
 id="mainView"
ongetdata="return zenPage.getContent('data',key,criteria);"
ongetlayout="return zenPage.getContent('layout',key,criteria);"
developerMode="false"
effectsEnabled="false"
maxPanels="2"
onload="zen('mainView').setDocumentKey(CAJAXNavigation.GetInstance().GetCurrentPage() || 'Home');zen('mainView').setLayoutKey(CAJAXNavigation.GetInstance().GetCurrentPage() || 'Home')"
>

Hi Fabio,

 

in template dispatch mode you can have a super layout class where you can have a collection of clientmethods implemented in every layout extending from it. Our menu is initialized by every layout each time it loads. You can define a method that initialize the menuitems as active for example or load dynamic data.

I'm sure that there is a better way of handling menus with onepage-concepts. Menus in general are difficult in client oriented template systems.

We implemented a way to display two layouts in a single pane; a detail part in a modal for example. I think this technique can be used to display a dynamic-menu. Just create a menu-layout that just provide the layout by criteria-parameters. In your superclass add a method that calls this layout and injects it into a div provided by every layout. Example:

Content-Layout:

var that = this;

return {...,children:[{type:'$div',id:'menu'}], $executeCodeAfterChildren: function() {  that.initMenu({"parm":"value"}) }}

Super-Layout:

ClientMethod initMenu(view, criteria) [ language = "javascript] 

{

    var layout =  zenPage.getContent("layout","Menu",criteria)

     view = view || zen('mainView');
     var menucontainer view.getComponentById("menu"); *****

    //Empty all children-elements first before refresh and add the layout

    menucontainer.$children = menucontainer.children = layout.children;

    //rerenders menu

    menucontainer.$refresh();

}

***** I added following code in createLayoutObjects before return in bootstrap-plugin to get layout-elements by id instead of index:

 

var mainView instance.$documentView; //zen('mainView');
if (typeof mainView._IDIndex === "undefined") {
mainView._IDIndex [];
}
if (instance && instance.id) {
mainView._IDIndex[instance.id]=instance;
}
if (!mainView.getComponentById) {
mainView.getComponentById function(id) {
return this._IDIndex[id] || null;
}
}

It's to change the namespace later back to the namespace you're comming from.

ClassMethod DoIt() As %Status
{
    //Current Namespace = "A"
    Do ..MethodThatChangesNamespace()
    //Namespace is now "B"
    Do ##class(Class).AnotherMethod()
    //Results in "NOT FOUND"
}

 

Edit: I don't know if "new $namespace" results in changing back the namespace. I got some problems changing namespaces in methods. Maybe it's changed since Version 2008.

Here is an example how you should code in ObjectScript:

 

ClassMethod listClasses(ns As %String = {$namespace}, projectName As %String) As %Status
{
    #dim tSC As %Status = $$$OK
    #dim tException As %Exception.StatusException = $$$NULLOREF
    #dim tOrigNS = $namespace
    try {
        // Switch namespaces to the new one
        set $namespace = ns

        // Grab our project, by name; fail otherwise
        // TODO: failing is CRUDE at this point...
        #dim project as %Studio.Project
        #dim status as %Status

        // TODO: note sure what the "concurrency" parameter is; leave the default which is -1
        set project = ##class(%Studio.Project).%OpenId(projectName,, .status)
        if ($$$ISERR(status)) {
            Throw ##class(%Exception.StatusException).CreateFromStatus(status)
        }
        #dim item as %Studio.ProjectItem

        #dim key = ""

        while (project.Items.GetNext(key)'="") {
            set key = project.Items.GetNext(key)
            set item = project.Items.GetAt(key)
            if ($IsObject(item)) {
                write "Name: " _ item.Name _ ", type: " _ item.Type, !
            }
        }
    } catch (tException) {
        Set tSC = tException.AsStatus()
    }
    set $namespace = tOrigNS
    quit tSC
}

Hi,

you need to re-render all components of header and footer on each layout-change. But there is a way to initialize the header and footer once and outside of the zen-pane.

In your zenPage create a ClientMethod that pushes html-code outside of the panes:

ClientMethod InitHeader(force) [ language = javascript ]
{
     if (force || !zenPage._zHeaderInit) {
        var head_html = [];
        var footer_html = [];
        head_html.push('<div id="header">');
        head_html.push('...things...')
        head_html.push('</div>');
        footer_html.push('<div id="header">...things...</div>')
        $('#zen1').html(head_html.join('') + $('#zen1').html() + footer_html.join(''));
        //init client-code here like onclick
        $('#header').find('a').each(function() {
            $(this).on('click',function() {
                //I clicked a emnu-item
            })
        });
        zenPage._zHeaderInit = true;
    }
}

In $executeCodeAfterChildren in getLayout() you can execute zenPage.InitHeader()

Hi Daniel,

use bootstrap and javascript. There are a lot of frameworks out there. You can use ZEN-Mojo as framework for example. I wouldn't try to work with angular.js because it's more expert-level Javascript and templating. But when you do it correctly it could be the best option out there.

Responsive apps need a lot of CSS and Javascript-Knowledge. Bootstrap gives you an idea and a good basic on how to achieve that. Maybe it's enough to redesign your old CSP-Pages to bootstrap to make it responsive enough.

The easiest way to work with an REST-API is jQuery-ajax (for http-requests in general)

I also use a Java-Class like structure for my scripts. Here is an example:

function MyClass(initParm, initListener) {
    var m_PrivateProperty = initParm || null;
    var m_EventListener = (typeof initListener === "function" ? initListener : new MyClass.OnEventListener())
    this.PublicProperty = "Hello World";
    
    this.getOnEventListener = function() {
        return m_EventListener;
    }
    this.setOnEventListener = function(EventListener) {
        m_EventListener = EventListener;
    }
    
    var privateFunc = function(parms) {
        return stuff;
    }
    this.publicFunction = function(parms) {
        return privateFunc(stuff)
    }
    
    this.getPrivateProperty = function() {
        return m_PrivateProperty;
    }
    this.setPrivateProperty = function(PrivateProperty) {
        m_PrivateProperty;
    }
    
    this.sendToServer = function() {
        var that = this;
        jQuery.ajax("/rest/do/things/", {
            method : "GET",
            asynch : true,
            data : {
                parm1 : this.getPrivateProperty()
            }
        }).done(function(a,b,c) {
            //we need "that" here. "this" is not MyClass anymore; could be jqXHR-Object or window
            that.getOnEventListener().OnThingsListener(a);
        }).error(function(a,b,c) { 
            //read jquery-doc
        })
    }
    
}
MyClass.OnEventListener = function() {
    this.OnClickListener = function(parms) { };
    this.OnThingsListener = function(parms) { };
}
 
var a = new MyClass(parm1,parm2)