WSO2 Cloud jaggery.js coding practices


Introduction


WSO2 Cloud, CloudMgt web app is built on jaggery.js. [1] It's a complete framework to write web apps and http-focused web services for all the aspects of the web application. Front-end, communication, server side back end logic in pure javascript.

Directory structure of the CloudMgt web app. As in general if you are writing a web app using jaggery.js you might have to have an understanding what each of the following described components do.

Figure 1: Directory structure of the cloudmgt app



























jaggery.conf : Jaggery configuration file specifies the application specific configurations such as welcome page, URL mappings, security constraints and etc.[2]. Basically this configurations are for the jaggery.js framework.
templates : Templates can be considered as reusable UI blocks. A single template is a single view of the web app. This is described in details how the UI blocks taken into play with all the css, client side javascript and html later in this article.
blocks : Blocks are the controller part of the web application. Basically blocks are responsible for handling respective template dynamically. There is a one-to-one mapping with template and blocks.
modules : This is responsible for maintaining the state of the application. Basically this is the "Model" of the application. It contains and maintain all the references plugged through java modules to the CloudMgt web app.
jagg : This directory holds all the functionality required for maintain the interactions between modules, blocks and templates.
pages : Pages holds the definitions of the individual pages rendering one or more blocks and templates combination.
conf : conf directory is consists with the cloudmgt app specific configurations files. cloud-mgt.json, email templates and etc.

Basically the above described templates, blocks and modules defines the MVC architecture of the jaggery.js framework.
Common utilities such as common styles, images. logos, fonts and etc are resides in directories such as images, css, fonts in the themes default directory.

CloudMgt app request flow


Figure 2: CloudMgt app request flow


































Coding practices

Basically as best practices we uses common basic javascript best practices that any developer practices currently ex: [3]. In this section I'm going to give some guidance on what should do/ should not do in jaggery.js framework perspective and code maintainability/readability/performance perspective of the CloudMgt app.

Templates

As described earlier a template is a reusable UI block, and a particular template holds a single view of the webapp. For an example see the following directory structure of a template. it contains custom css files specific to that template. client side javascript in the "js" directory. initializer.jag and the template.jag files.

├── subscriptions
│   ├── css
│   │   └── custom-styles.css
│   ├── initializer.jag
│   ├── js
│   │   └── subscriptions.js
│   └── template.jag

template.jag should contain only the raw html which needs to build a view. Developer can add the functionality to be executed at the initialization time of a template to the initializer.jag. Mostly it's consists with the includes of the javascript files and the css files to a template.

<%
jagg.initializer("subscriptions", {

    preInitialize:function () {
        jagg.addHeaderJS("subscriptions", "subscriptions", "templates/subscriptions/js/subscriptions.js");
        jagg.addHeaderCSS("subscriptions", "custom-styles", "templates/subscriptions/css/custom-styles.css");
    }
});
%>


Blocks

As described earlier blocks are controller of the framework. it manipulates the respective template according to the incoming http requests. Following is the respective directory structure of the subscriptions block. Point to keep in mind is the ajax directory which acts as the API for that view. This is not belongs to the blocks. This is the API layer which uses the client side to interact dynamically with the server. ex: form POST requests.

├── subscriptions
│   ├── ajax
│   │   └── subscriptions.jag
│   └── block.jag

Please see the example block.jag

<%
jagg.block("subscriptions", {
    initializer:function (data) {

    },

    getOutputs:function (inputs) {
        var user, result, mod, tenant;

        user = request.getParameter("user");
        tenant=inputs.tenant;

        if (null == user) {
            return {
                "result": null
            }
        }

        mod = jagg.module("billing");
        result = mod.getBillingInfo({"user":user});

        return {
            "result":result,
            "tenant":tenant
        }
    }

});
%>

As you can see block manipulates the inputs given from the pages with the request parameters and it uses the required modules to derive the output required for the template to become a view. this outputs are passed to the template to populate the dynamic values of the view.
Do not plug java modules or services in blocks or the apis. you are required only to use the respective jaggery modules in this layer.
Keep in my when you are exposing functionality to the client side with the APIs, please check the user is authorized to execute the functionality by filtering the requests using the functions as follows.

var loginStatus = jagg.isUserLoggedIn();
if (loginStatus.error) {
    response.status = 401;
    print(loginStatus);
    return;
}

If it's tenant admin related please use jagg.module("util").isUserInAdminRole(); methods and etc.
When getting the request parameters keep in mind to use "org.wso2.carbon.ui.util.CharacterEncoder" 's getSafeText() method. In here too follow the return object format specified in module practices section in this article.


Modules

There is nothing much to specify there in modules except the return objects should be as in following format, do not throw exceptions. Log and handle the exceptions at that level. Please use user friendly self explanatory error messages.

return {
    error: false,
    statusCode: 200,
    params: clientParams
}

return {
    error: true,
    statusCode: 500,
    message: "Internal error. Please retry..."
};


Pages

Here you can render different views according to the incoming http requests within a same page. see the example page.

<%
include("/jagg/jagg.jag");
include("../header.jag");
var site = require("/site/conf/site.json");
var i18n = require("i18n");
var localeResourcesBasePath = "/site/conf/locales/jaggery/";
i18n.init(request, localeResourcesBasePath);
var tenantDomain = jagg.getTenantDomain();
var errorPageUri = "/site/pages/error-pages/404.html";
var encoder = Packages.org.wso2.carbon.ui.util.CharacterEncoder;

(function () {
    var user = jagg.getUser(), isMonetizationEnabledObj, selectedApp, middleObj;

    isMonetizationEnabledObj = jagg.module("billing").isMonetizationEnabled(tenantDomain);
    isTenantAllowed();
    //if user is not logged in
    if (!user) {
        response.sendRedirect(getRedirectPathForNonAuthenticatedUser());
        return;
    }

    //if monetization is not available for tenant
    if (isMonetizationEnabledObj == null || isMonetizationEnabledObj.error || !isMonetizationEnabledObj.monetizationEnabled) {
        response.sendRedirect(jagg.getAbsoluteUrl("/site/pages/list.jag") + jagg.getTenantURLPrefix("?"));
        return;
    }

    var action = encoder.getSafeText(request.getParameter("action"));
    var workflowReference = encoder.getSafeText(request.getParameter("workflowReference"));

    var inputsObj = {
        "tenant": tenantDomain
    };

    if ("paymentMethod".equals(action)) {

        //When returned from a unsuccessful payment
        var paymentSuccess = encoder.getSafeText(request.getParameter("success"));
        var errorObj = {
            "error": false
        };

        if (paymentSuccess != null && !"".equals(paymentSuccess) && !paymentSuccess) {
            errorObj.error = true;
            errorObj.errorMessage = encoder.getSafeText(request.getParameter("errorMessage"));
        }

        inputsObj.errorObj = errorObj;

        middleObj = [{
            "name": "billing/payment-method/add",
            "inputs": inputsObj
        }];
    } else if ("createAccount".equals(action)) {
        var signature = encoder.getSafeText(request.getParameter("signature"));
        var refId = encoder.getSafeText(request.getParameter("refId"));

        inputsObj.refId = refId;
        inputsObj.signature = signature;

        middleObj = [{
            "name": "billing/account/create",
            "inputs": inputsObj
        }];
    } else {
        response.sendRedirect(jagg.getAbsoluteUrl("/site/pages/list.jag") + jagg.getTenantURLPrefix("?"));
        return;
    }

    jagg.render({
        "name": "page/base",
        "inputs": {
            "title": "My Account",
            "pagePath": "/site/pages/billing/manage.jag",
            "body": [
                {
                    "name": "layout/base",
                    "title": "My Account",
                    "inputs": {
                        "top": [

                            {
                                "name": "search",
                                "inputs": null
                            }
                        ],
                        "left": [
                            {
                                "name": "subscriptions",
                                "inputs": {"tenant": tenantDomain}
                            }
                        ],
                        "middle": middleObj,
                        "right": null
                    }
                }
            ]
        }
    });
}());

%>

Here the inputs that passed for the particular block specified. You can manipulate those inputs as needed for your templates.

Url mappings in jaggery.conf


"urlMappings": [{
    "url": "/billing/account",
    "path": "/admin/billing/account-info.jag"
}]

As above you can create paths to a page by simply adding a the mapping object to the jaggery.conf 's urlMapping object array.

Note that the code chunks specified here are not the exact way they are there in the cloudmgt app.

[1] http://jaggeryjs.org
[2] http://jaggeryjs.org/documentation.jag?api=jagconf
[3] http://www.w3schools.com/js/js_best_practices.asp


Comments

Popular Posts