Presentation Logic

In accordance with the Design Review, the application UI must provide some auxiliary widgets to demonstrate the vendor rated data (VRD) processing.

This phase continues the demo project from the previous step.

You are supposed to do the following updates in UI:

  • Add a separate list showing the VRD reports.

  • Add a button that allows a user to request one more report to be added to the above-mentioned list.

  • Create a popup view that shows details of a selected report.

List of VRD Reports

In the main view source ui/servers.js that manages all VPSes of a customer, create a list of VRD reports and a button that allows a user to request more reports. In the list, a user can click on a report to get a popup that displays the report details.

Update the ui/servers.js to implement the design or create it from scratch as follows.

  1. In the top-level define function, ensure all modules used in the further updates are included in the list. The updated list must look as follows:

    define([
        "dojo/_base/declare",
        "dojo/when",
        "dojox/mvc/getStateful",
        "dojox/mvc/at",
        "dojo/_base/array",
    
        "aps/Memory",
        "aps/View",
        "aps/Status",
        "aps/ResourceStore",
        "aps/xhr",
    
        "./displayError"
    ], function(
           declare,
           when,
           getStateful,
           at,
           arr,
    
           Memory,
           View,
           Status,
           Store,
           xhr,
    
           displayError
       ) {
       // ...
    });
    
  2. Add a function to get the current date and time in the required format.

    Note

    We use here the date-time format for demo purposes only. The RatedDataSupport APS type actually requires the date format in the data exchange. However, you can modify the function to use the needed format.

    function getCurrentTime() {
        var d = new Date();
        return(
            d.getUTCFullYear().toString() + '-' +
            (d.getUTCMonth() > 8 ? (d.getUTCMonth()+1).toString() : '0' + (d.getUTCMonth()+1).toString()) + '-' +
            (d.getUTCDate() > 9 ? d.getUTCDate().toString() : '0'+d.getUTCDate().toString()) + 'T' +
            (d.getUTCHours() > 9 ? d.getUTCHours().toString() : '0'+d.getUTCHours().toString()) + ':' +
            (d.getUTCMinutes() > 9 ? d.getUTCMinutes().toString() : '0' + d.getUTCMinutes().toString()) + ':' +
            (d.getUTCSeconds() > 9 ? d.getUTCSeconds().toString() : '0' + d.getUTCSeconds().toString())
        );
    }
    
  3. Declare the global variables that can be used throughout the view definition:

    var self, page, vpsGrid, ratedDataGrid, selectionArray, contextId, accountId, parentId, lastPollTime;
    
  4. In the init method, use the same variable and function definitions as in the input package:

    self = this;
    
    /* Create the data store */
    var vpsStore = new Store({
        apsType: "http://aps-standard.org/samples/vrd/vps/1.0",
        target: "/aps/2/resources/"
    });
    
    /* Initialize a cache to collect reports on resource charges */
    var chargeReportCache = new Memory({
        data: [],
        idProperty: "invoiceEndDate"
    });
    
    /* Handler for the *New* button */
    var add = function() {
        /* Start the process of creating a VPS by going to the first screen */
        aps.apsc.gotoView("vps-wizard");
    };
    
    var refresh = function() {
            vpsGrid.refresh();
            ratedDataGrid.refresh();
    };
    
  5. In the init method, add a cache to store the VRD reports:

    /* Initialize a cache to collect reports on resource charges */
    var chargeReportCache = new Memory({
        data: [],
        idProperty: "invoiceEndDate"
    });
    
  6. In the init method, add a function to poll VRD reports:

    var pollCharges = function() {
       var currentTime = getCurrentTime();
       xhr.get(
          "/aps/2/resources/" +
          contextId +
          "/exportRatedData?invoiceStartDate=" +
          lastPollTime
       ).then(function(report) {
          if(report == null) return;
          chargeReportCache.add(report);
          ratedDataGrid.refresh();
       });
       lastPollTime = currentTime;
    };
    

    The function requires a list of charges for the period starting from the last poll.

    Note

    The last poll date-time is alive while the current IFrame is alive, that is until you refresh or reload your browser. Recall, this is a simplified demo, and the platform’s periodic task will poll the application every day.

  7. The definition of the other functions in the input package does not require any updates except for the names of some variables that were changed if you followed the above steps:

                function changeState(state, btn) {
                    var counter = selectionArray.length;
    
                    /* Clear the current messages on the screen */
                    page.get("messageList").removeAll();
    
                    arr.forEach(selectionArray, function(vpsId){
                        console.log(
                            "I'm trying to change state of VPS with id = [" + vpsId + "]"
                        );
    
                        /* Save the VPS properties */
                        when(xhr(
                                "/aps/2/resources/" + vpsId + "/" + state,
                                { method: "GET",  handleAs: "text" }
                            ),
                            /* If success, process the next VPS until the list is empty */
                            function(){
                                console.log("State of VPS with id = [" + vpsId + "] changed");
                                selectionArray.splice(selectionArray.indexOf(vpsId),1);
                                vpsGrid.refresh();
                                if (--counter === 0) { btn.cancel(); } /* Make the button available */
                            },
                            /* If failure, call the error handler */
                            function(e){
                                displayError(e);
                                if (--counter === 0) { btn.cancel(); }
                            }
                        );
                    });
                }
    
                /* Create the on-click handler for the *Stop* button */
                var stop = function() {
                    changeState("stop", this);
                };
    
                /* Create the on-click handler for the *Start* button */
                var start = function() {
                    changeState("start", this);
                };
    
                /* Create the on-click handler for the *Delete* button */
                var remove = function() {
                    var btn = this;
                    /* Get confirmation from the user for the delete operation */
                    if (!confirm("Are you sure you want delete VPSes?")) {
                        btn.cancel();
                        return;
                    }
    
                    var counter = selectionArray.length;
    
                    /* Clear the current messages on the screen */
                    page.get("messageList").removeAll();
    
                    selectionArray.forEach(function(vpsId){
                        console.log("I'm trying to delete VPS with id = [" + vpsId + "]");
    
                        /* Remove the VPS from the APS controller DB */
                        when(vpsStore.remove(vpsId),
                            /* If success, process the next VPS until the list is empty */
                            function(){
                                console.log("VPS with id = [" + vpsId + "] removed");
                                selectionArray.splice(selectionArray.indexOf(vpsId),1);
                                vpsGrid.refresh();
                                refresh();
                                if (--counter === 0) { btn.cancel(); }
                            },
                            /* If failure, call the error handler */
                            function(e){
                                displayError(e);
                                if (--counter === 0) { btn.cancel(); }
                            }
                        );
                    });
                };
    
  8. At the end of the init method, define and return widgets:

    return [
        ["aps/Panel", {
            title: "Virtual Servers"
        },[
            ["aps/Grid", {
                id:                this.genId("srv_grid"),
                store:             vpsStore,
                selectionMode:     "multiple",
                apsResourceViewId: "server-edit",
                noDataText: "No servers provisioned",
                noEntriesFoundText: "No servers meet your search criteria",
                columns: [{
                    field: "name",
                    name: "Name",
                    type: "resourceName",
                    filter: true
                }, {
                    field: "hardware.memory",
                    name: "RAM",
                    title: "RAM",
                    type: "integer",
                    filter: true
                }, {
                    field: "hardware.diskspace",
                    name: "Disk Space",
                    title: "Disk",
                    type: "integer",
                    filter: true
                }, {
                    field: "hardware.CPU.number",
                    name: "CPU",
                    type: "integer",
                    filter: true
                }, {
                    field: "platform.OS.name",
                    name: "OS",
                    filter: true
                }, {
                    field: "state",
                    name: "State",
                    filter: true
                }
                ]}, [
                ["aps/Toolbar", [
                    ["aps/ToolbarButton", {
                        id: this.genId("srv_new"),
                        iconClass:"fa-plus",
                        type: "primary",
                        label: "Add New VPS",
                        onClick: add
                    }],
                    ["aps/ToolbarButton", {
                        id: this.genId("srv_start"),
                        iconClass:"fa-play",
                        type: "success",
                        label: "Start",
                        requireItems: true,
                        onClick: start
                    }],
                    ["aps/ToolbarButton", {
                        id: this.genId("srv_stop"),
                        iconClass:"fa-pause",
                        type: "warning",
                        label: "Stop",
                        requireItems: true,
                        onClick: stop
                    }],
                    ["aps/ToolbarButton", {
                        id: this.genId("srv_delete"),
                        iconClass:"fa-trash",
                        type: "danger",
                        label: "Delete",
                        requireItems: true,
                        onClick: remove
                    }],
                    ["aps/ToolbarButton", {
                        id: this.genId("srv_refresh"),
                        iconClass:"fa-refresh",
                        label: "Refresh",
                        autoBusy: false,
                        onClick: refresh
                    }]
                ]]
            ]]
        ]],
        ["aps/Panel", {
            title: "Vendor Rated Charge Reports"
        },[
            ["aps/Grid", {
                id: this.genId("srv_charges"),
                store: chargeReportCache,
                noDataText: "No rated data yet",
                noEntriesFoundText: "No rated data collected yet from the vendor",
                onRowClick: function(row) {
                    aps.apsc.showPopup({
                        viewId: "http://aps-standard.org/samples/vrd#rated-data",
                        params: {"report": JSON.stringify(row)}
                    });
                },
                columns: [{
                    field: "invoiceEndDate",
                    name: "Report Date-Time, UTC   -- click on a row to get details",
                    filter: true
                }, {
                    name: "Total for Customer, USD",
                    renderCell: function (row) {
                        var total = 0;
                        row.charges.forEach(function (charge) {
                            if(charge["accountId"] == accountId)
                                total = total + charge["totalCost"];
                        });
                        return total.toFixed(2);
                    }
                }, {
                    name: "Total for Reseller, USD",
                    renderCell: function (row) {
                        var total = 0;
                        row.charges.forEach(function (charge) {
                            if(charge["accountId"] == parentId)
                                total = total + charge["totalCost"];
                        });
                        return total.toFixed(2);
                    }
                }]}, [
                ["aps/Toolbar", [
                    ["aps/ToolbarButton", {
                        id: this.genId("srv_pollCharge"),
                        iconClass:"fa-refresh",
                        label: "Poll charges",
                        autoBusy: false,
                        onClick: pollCharges
                    }]
                ]]]
            ]
        ]]
    
    ];
    

    Keynotes:

    • In addition to the VPS grid, you added a grid to display a list of VRD reports.

    • The list of VRD reports shows the total price for the customer and its direct parent (a reseller or the provider).

    • The onRowClick function calls the popup view (defined later) passing the respective report to it.

    • To poll the application for a new list of charges, you defined the Poll charges button.

  9. Update the onContext method with calling refresh of the new grid.

    onContext: function(context) {
       lastPollTime = getCurrentTime();
       vpsGrid = this.byId("servers_list");
       ratedDataGrid = this.byId("vrd_charges");
       page = this.byId("apsPageContainer");
       selectionArray = vpsGrid.get("selectionArray");
       var mgmtContext = context.vars.context;
       var account = context.vars.account;
       accountId = account.aps.id;
       parentId = account.parent.aps.id;
       contextId = mgmtContext.aps.id;
       vpsGrid.refresh();
       ratedDataGrid.refresh();
       aps.apsc.hideLoading();
    },
    
  10. Keep the onHide method intact:

    onHide: function() {
       this.byId("srv_new").cancel();
    }
    

VRD Report Details

The ui/vrd-details.js view source declared in Update Metadata must display details of a report passed to it as the input argument.

Define the popup view as explained in the Popup View concepts:

define([
        "dojo/_base/declare",
        "dojox/mvc/at",
        "dojox/mvc/getStateful",
        "aps/PopupView"
    ], function(declare, at, getStateful, PopupView) {
    var chargeReportModel;
    return declare(PopupView, {
        init: function(){
            chargeReportModel = getStateful({"report": {}});
            return [
                ["aps/Output", {
                    content: at(chargeReportModel, "report")
                }]
            ];
        },
        onContext: function() {
            var chargeReport = aps.context.params["report"];
            chargeReportModel.set("report",
               JSON.stringify(JSON.parse(chargeReport), null, '\t')
               .replace(/\n/gi, '<br>')
               .replace(/\t/gi, '&nbsp;&nbsp;&nbsp;&nbsp;')
            );
            aps.apsc.hideLoading();
        }
   });
});

Keynotes:

  • The init method defines the model and a widget to display the report details.

  • The onContext method receives a report as JSON data from the other view and updates the model synced with the widget.

  • The JSON.stringify and replace methods convert JSON data to a string and then make that string human readable.

Conclusion

This step completes the development of the demo project. Finally, package the project file:

$ aps build vrd/

If you encountered any issues with the project development process, feel free to download the sample package and then compare its files with your files.