APS Technology: Front End Control Panel and JS SDK Features

    Last time we talked about APS (Application Packaging Standard) - our open technology for integrating applications into the Odin Automation cloud service platform (SaaS marketplace) . Our platform connects developers and consumers of cloud services through the infrastructure of large service providers (providers of telecommunications and hosting services), while providing an entry point for end users: a control panel or portal with which you can create a website, configure mail, buy an antivirus or virtual car in the cloud. In this post, we will dwell in more detail on how the frontend of the control panel and APS applications is arranged and what features the APS JavaScript SDK provides.



    Control Panel and Application Screens


    End users can use the control panel to manage applications, upsell and cross-sell services. It provides a common interface into which APS application developers embed their user interface.

    Each service provider brands the Odin Automation installation in its own colors. Therefore, we abandoned the use of proprietary markup and use the popular Twitter Bootstrap markup with the CSS LESS preprocessor. Since all applications use the APS JS SDK, it’s enough for the theme developer to specify just a few parameters to get a design that matches the brand book of the service provider or reseller.

    In support of mobile devices, we did not limit ourselves to adaptive layout and the Bootstrap grid, which the application developer can use to determine the size of widgets on various devices, but went further. Some complex widgets completely change their appearance on mobile devices. For example, a table is displayed in tiles; and a slider with a slider, which, obviously, is inconvenient to use on touch-devices, turns into a spinner, consisting of input and buttons "+" / "-".



    The panel itself and each application are Single Page Applications. In the simplest case, each APS application is isolated in its IFrame, which the control panel router does not delete, but hides and shows. Similarly, inside the IFrame, the screens are not deleted when changing, but are hidden and shown. Iframes are necessary to isolate applications from each other, because Applications are written by various vendors. Moreover, isolation between the screens of one application is weaker: each screen is just a JavaScript module inherited from the corresponding class, and its display widgets are placed in a div. Thus, we have obtained SPA over SPA.

    Single Page Application


    Let's consider our SPA in more detail. Each APS application is described by a special APP-Meta file. In particular, this file contains a description of all application screens and related data. It also describes the relationship of applications. For example, if application A provides a wizard into which application B wants to integrate, then application A declares support for embedding by declaring a so-called placeholder, and application B declares its desire to embed its screen in this placeholder.


    At the same time, the placeholder is not tied to a specific application. Several applications can declare the same placeholder, and then the application B screen will be embedded in all these applications.

    We return to the browser and explain how SPA works with a small example.

    1. The user opens the view-11 screen of application A in the control panel .
    2. The control panel router creates an IFrame and loads the start bootstrapApp.html common to all applications into it. Then it connects the view module A1 . The instance of this module will remain in the IFrame, even if the user switches to another screen.
    3. In the same application, the user switches to view A2 screen .
    4. The router connects the view A2 module in the IFrame . Now all the transitions between these screens will occur in one IFrame.
    5. The user switches the screen view B1 applications in .
    6. The router creates a new IFrame and loads bootstrapApp.html with the view source B1 into it . Now this code will remain in the IFrame, even if the user switches to another screen.

    After that, everything just as in Appendix A .



    Let us dwell in more detail on the life cycle of the application screen. It consists of the following phases:

    • Initialization (init method). In it, the application must declare widgets and associate them with the model.
    • Preparation for display (onShow method). Here, the application can perform preparatory actions that do not require data.
    • Display (onContext method). In this phase, the screen received data from the control panel that was previously described in the APP-Meta file. Of course, the screen itself can go to the server for data, but we recommend using a declaration, as this saves loading time. The fact is that to display each screen, the frontend makes a request to the server, because the structure of the screens could change. If the data was previously described, then it will be received in the same request. If the screen itself goes after the data, then the end user will have to wait until the first request is received, and then wait for the second and subsequent ones. After the screen has received the data, it puts them into the model, and the widgets change their state.
    • Hiding (onHide method). Here, the application clears the widgets and returns them to a neutral state. For this, the screen has a special method.

    The simplest integration method has been described above. But there are more difficult situations. Take an example: there is a dashboard application A, and application B wants to display some information in A using a widget. For such point integration, we developed view plugins. Embedding view plugins is completely similar to the placeholder mechanism described above. To maintain isolation between A and B, all communication between them is through a mediator. This is a special object that contains the description of the view plug-in API in the form of a JSON-scheme and first checks the plug-in for all the required properties and methods, and then controls all communication between the host screen and the view plug-in.



    Let's take an example. The mediator provides data on resourceUsage and a custom operation getWidget, which takes an optional argument in the form of a Boolean value:

    { 
        "properties": { 
            "resourceUsage": { 
            // type указывает на тип объектов, которые должны содержатся в поле resourceUsage 
            // URI-подобные типы описывают специальные APS ресурсы, которые хранятся в post-noSQL базе данных APS, о которой мы расскажем в следующих постах 
            "type": "http://aps-standard.org/types/core/subscription/1.0#SubscriptionResource", 
        } 
    }, 
    "operations": { 
        "getWidget": { 
             "parameters": { 
                "withData": { "type": "boolean", "required": false } 
             }, 
             "response": { 
        "type": "string", 
             "required": false 
             } 
        } 
    } 
    } 

    View plugins can provide not only UI, but also logic. In this case, a certain API is placed in the mediator, hiding the logic from an external system, for example, a billing system. The application does not need to know which billing system is deployed by the service provider. Therefore, the specifics of working with it is hidden for a unified API described in the standard, and the implementation of this API in each specific system is done as a plug-in view plug-in.

    SDK


    The first version of the APS JS SDK was developed over five years ago and has since been continuously developed with the APS standard. The basis was taken by the Dojo framework. Now this may seem strange, but by the standards of the Web-world it was a whole era ago. Then Angular was just beginning, and React did not exist at all.

    What we liked about Dojo:

    • AMD boot loader and modular system;
    • Built-in support for classes with multiple inheritance;
    • implementation of promises;
    • a large number of various auxiliary modules;
    • thoughtful APIs and developed documentation.

    Now our framework provides the following modules for APS application developers:

    • a large number of different widgets (spinner, slider, grid, password, etc.);
    • modules for working with data (both client and server storages) and modules for two-way data binding and display;
    • various auxiliary modules: API for working with billing systems, localization and internationalization utilities, password generator for a given security policy, and much more.

    Developers can also connect third-party libraries both directly in the AMD format and in the form of ES2015 modules that will be converted to AMD.

    Widgets


    Widgets are the “building blocks” of the user interface. In the APS JS SDK, they are logically separate from the HTML view and can:

    • dynamically change the values ​​of their properties;
    • inherited from each other;
    • include other widgets at the template level.

    It is possible to add child widgets, both dynamically and when describing the screen. There are two ways to describe widgets, and as needed, these methods can be combined on one screen in any combination. Consider these methods in more detail.

    Creating widgets using constructors . First you need to connect the necessary modules using the function require()or use the keyword importif you are using a transporter, and then create widgets by calling the constructor with the necessary parameters. Their hierarchy is determined by a method addChildthat adds child widgets.

    import Button from "aps/Button"; 
    var btn = new Button({ 
       id: "example1", 
       label: "I am simple button" 
    }); 

    Creating widgets using a declaration. The widget hierarchy and their properties are defined as a JSON-like structure, which is passed to the load function. The declaration of each widget is a JavaScript array that can contain three elements:

    • widget name;
    • (optionally) a set of widget properties,
    • (optionally) an array containing child elements.

    import load from "aps/load"; 
    load([ "aps/ProgressBar", { value: "35%" } ]); 

    The method load itself connects the necessary modules, therefore it works asynchronously and returns a promise that will be resolved by the widget declared in the root of the passed structure.

    load([ "aps/ProgressBar", { id: "myProgBar", value: 0 } ]) 
       .then(function(pb) { 
           pb.set("value", 41); 
       }); 

    When using load-a, the code turns out to be more logical and readable: first comes the parent widget, and then the child ones. Large structures can be divided into sections and decomposed into separate variables with clear names, and then combined in one structure.

    Work with data


    Obviously, you can’t get by with just some widgets when creating a UI - they need data. Data sources for widgets come in the form of modules of two types: modules of type Model and modules of type Store.

    Type modulesModel - a set of modules for two-way or one-way binding of widgets and data. Using the method at(), a bundle with the widget is executed. A method is used to track changes in Model watch(). Model Methods get()and are used to work with properties set().

    Example initialization Model from a JSON representation:

    require([ 
       "dojox/mvc/getStateful", 
       ... 
       "aps/json!./newoffer.json" 
    ], function (getStateful, ..., newOffer) { 
       /* Declare the data source */ 
          var model = getStateful(JSON.parse(newOffer)); 
          ... 
    }); 

    Binding Model to widgets:

    var widgets = 
       ["aps/PageContainer", { id: "page"}, [ 
          ["aps/FieldSet", { title: true}, [ 
             ["aps/TextBox", { 
                id: "offerName", 
                label: _("Offer Name"), 
                value: at(model, "name"), 
                required: true 
             }], 
             ["aps/TextBox", { 
                label: _("Description"), 
                value: at(model, "description") 
             }] 
          ]], 
          ... 
       ]]; 
    load(widgets); 

    Type modules areStore designed to work with various data sources. Sources are local when all the data is on the client, and remote when the data is on the backend. Since the remote source is usually an APS controller, the module for working with it provides authentication information and supports properties related to the specifics of APS, for example, apsType. Regardless of the type of data source, the interaction with widgets that display data goes unilaterally. To reflect changes in widgets, you must explicitly call for data updates.

    Queries for any data sources are ultimately made using the Resource Query Language (RQL). RQL is a query language designed for use in URIs for working with object-like data structures. In more detail about it we will tell in the following posts.

    Ad example Store:

    import Store from  "aps/ResourceStore"; 
    var offerStore = new Store({ 
        apsType:    "http://aps-standard.org/samples/vpscloud/offer/1.0", 
        target:     "/aps/2/resources/" + aps.context.vars.cloud.aps.id + "/offers" 
    ... 
    }); 

    Binding Store to a widget displaying a table:

    load(["aps/PageContainer", { id: "page" }, [ 
       ["aps/Grid", { 
          id:                "grid", 
          columns:   [ 
             { field: "offername",             name: "Name", type: "resourceName" }, 
             { field: "hardware.memory",       name: "RAM, MB" }, 
             ... 
          ], 
          store: offerStore 
       }, ... 
       ] 
    ]]); 
    

    Documentation and Sandbox


    Our APS platform is focused on third-party developers, so we must ensure stability and ease of development. Without elaborated documentation, this would not have been possible.

    We have created a portal for developers, on which all the necessary documentation for creating a UI with code samples is available. This is a complete reference: first, some general description of the interface, module, or method is given, and in the nested levels, more detailed information. Moreover, part of the documentation is automatically generated based on the current code of our platform. Inside, properties, methods, and return values ​​are described.

    Another "trick" is the sandbox, integrated into the portal for developers. Getting into it is very simple: click on the “Run demo” button, which is in every code example:



    Our APS Fiddle:

    • knows all the APIs of our widgets and can prompt property names and method signatures;
    • allows you to compare code behavior in different versions of the APS standard;
    • able to switch from mobile to desktop view;
    • Provides links to code snippets that you can send to your colleagues or support team (Share);
    • allows you to work on code together (Collab);
    • can generate a finished file with your code, as if it is a separate application screen, and this file can be immediately thrown into a real project and tested.



    A detailed description of working with the sandbox is available here: Development Tools -> APS Fiddle .

    Finally


    We provide a public API on which the performance of over 500 applications with a total audience of several million users depends. This is a great responsibility. To facilitate the work of third-party developers and to simplify the work with our platform as much as possible, we made detailed documentation and a sandbox. And in order not to accidentally break something, we provided a very high coverage of the code with tests. How we achieved this - read about it in the next post.

    Also popular now: