Application Packaging Standard

Last updated 18-Mar-2019

Development

This is the central phase of the APS application life cycle, where you actually develop all components of the APS application.

../../../_images/start-step-env.png ../../../_images/start-step-overview.png ../../../_images/start-step-develop-b.png ../../../_images/start-step-deploy.png ../../../_images/start-step-provisioning.png ../../../_images/start-step-update.png

Meta Declarations

The project template contains the following template for your APP-META.xml file:

<application xmlns="http://aps-standard.org/ns/2" version="2.0">
    <id></id>
    <name></name>
    <version>1.0</version>
    <release>0</release>
    <homepage></homepage>
    <vendor>
    	<name></name>
    	<homepage></homepage>
    </vendor>
	<packager>
        <name></name>
    	<homepage></homepage>
    </packager>
    <presentation>
    	<summary></summary>
    	<description></description>
        <categories>
           <category>Samples</category>
    	</categories>
        <navigation></navigation>
    </presentation>
    <license-agreement must-accept="true">
		<free/>
		<text>
			<name>End-User License Agreement</name>
			<file>http://opensource.org/licenses/bsd-license</file>
		</text>
    </license-agreement>
    <service>
    </service>
</application>

In APP-META.xml, declare the application components to comply with the resource model and the file structure defined earlier.

  1. Application ID:

    <id>http://aps-standard.org/samples/starter1p</id>
    

    Note

    Although it is an arbitrary unique URI, be aware that this must be the prefix in all APS type IDs of this application.

  2. Application name, for example:

    <name>Simplest demo project</name>
    
  3. Application home page, for example:

    <homepage>http://doc.apsstandard.org/start/demo/</homepage>
    
  4. Main parameters of the application vendor and the packager, for example:

    <vendor>
       <name>APS team</name>
       <homepage>http://doc.apsstandard.org/</homepage>
    </vendor>
    <packager>
       <name>APS team</name>
       <homepage>http://doc.apsstandard.org/</homepage>
    </packager>
    
  5. Summary and description elements in the presentation section, for example:

    <presentation>
       <summary>Simplest demo multi-tenant application</summary>
       <description>This is a simplified demo application to demonstrate how to get started
                   developing APS apps step by step.
       </description>
       ...
    </presentation>
    
  6. Navigation tree in the presentation section, just below the <categories> element:

    <presentation>
       ...
       <categories> ... </categories>
       <navigation id="ccp" label="VPS Management">
          <plugs-to id="http://www.parallels.com/ccp/2" />
          <var type-id="http://aps-standard.org/samples/starter1p/management/1.0"
                name="management" />
          <view id="servers" label="Servers" src="ui/servers.js">
             <view id="server-new" label="New VPS" src="ui/server-new.js">
                <controls>
                   <cancel />
                   <submit />
                </controls>
             </view>
          </view>
       </navigation>
    </presentation>
    

    The navigation tree contains the following key declarations:

    • The navigation tree plugs into the customer control panel UX1 as defined by the <plugs-to> element.
    • The tree contains two views nested into each other. The servers.js file is the source for the top-level servers view that must present a list of VPSes. The server-new.js file is the source for the server-new view that will be used to create a VPS. It contains the cancel and submit navigation controls.
    • A variable named management will bring the management resource JSON representation to all views of the tree.

    In the customer control panel, the views will look as follows:

    ../../../_images/start-vps-list.png ../../../_images/start-vps-new.png
  7. Three services, one per APS type, at the end of APP-META.xml:

    <service id="apps">
       <code engine="php" path="scripts/apps.php"/>
       <presentation>
          <name>VPS cloud globals</name>
          <summary>VPS cloud application global service</summary>
       </presentation>
    </service>
    <service id="managements">
       <code engine="php" path="scripts/managements.php"/>
       <presentation>
          <name>VPS Management</name>
          <summary>VPS management environment</summary>
       </presentation>
    </service>
    <service id="vpses">
       <code engine="php" path="scripts/vpses.php"></code>
       <presentation>
          <name>Virtual Private Server</name>
          <summary>Cloud virtual private server</summary>
       </presentation>
    </service>
    

    According to the declaration, the services are defined by the specified PHP scripts.

This completes the meta declarations. You can compare your file with the sample APP-META.xml file.

Provisioning Logic

Since we are not going to redefine the default provisioning operations, the only goal in this step is to define APS types as specified by the resource model. Every PHP script will define one APS type using the /**…*/ DocBlock before a defined object as explained in the APS PHP Framework documentation.

Application Type

The apps.php script must define the app type that implements the APS core Application type and has multiple links with the management resources.

<?php

require "aps/2/runtime.php";

/**
 * Class app presents application and its global parameters
 * @type("http://aps-standard.org/samples/starter1p/app/1.0")
 * @implements("http://aps-standard.org/types/core/application/1.0","http://odin.com/init-wizard/config/1.0")
 */
class app extends APS\ResourceBase {
# Link to collection of management contexts. Pay attention to [] brackets at the end.
	/**
	 * @link("http://aps-standard.org/samples/starter1p/management/1.0[]")
	 */
	public $managements;

    /**
     * @verb(GET)
     * @path("/getInitWizardConfig")
     * @access(admin, true)
     * @access(owner, true)
     * @access(referrer, true)
     */ 
    public function getInitWizardConfig()
    {
        $myfile = fopen("./wizard_data.json", "r") or die("Unable to open file!");
        $data = fread($myfile,filesize("./wizard_data.json"));
        fclose($myfile);
	   	return json_decode($data);
    }
		
    /**
     * @verb(GET)
     * @path("/testConnection")
     * @param(object,body)
     * @access(admin, true)
     * @access(owner, true)
     * @access(referrer, true)
     */ 
    public function testConnection($body)
    {
        return "";
    }
    	
}

?>

Management Type

The managements.php script must define the management type that implements the APS core SubscriptionService type. The type must have a strong link with the app resource and multiple links with the vps resources.

<?php
# It is the management context of the subscription, in which a customer can manage its VPSes.
# It must correspond to a tenant created for the subscriber in the remote application system.
require "aps/2/runtime.php";

/**
* Class management
* @type("http://aps-standard.org/samples/starter1p/management/1.0")
* @implements("http://aps-standard.org/types/core/subscription/service/1.0")
*/

class management extends \APS\ResourceBase
{
## Strong relation (link) to the application instance
		
	/**
	* @link("http://aps-standard.org/samples/starter1p/app/1.0")
	* @required
	*/
	public $app;
	
## Weak relation (link) to collection of VPSes
	/**
	 * @link("http://aps-standard.org/samples/starter1p/vps/1.0[]")
	 */
	public $vpses;
	
}
?>

VPS Type

The vpses.php script must define the vps type that implements the APS core Resource type. The type must have a strong link with the management resource.

<?php

require "aps/2/runtime.php";


// Main class
/**
 * @type("http://aps-standard.org/samples/starter1p/vps/1.0")
 * @implements("http://aps-standard.org/types/core/resource/1.0")
 */
class vps extends APS\ResourceBase {

   ## Relationship with the management context

   /**
    * @link("http://aps-standard.org/samples/starter1p/management/1.0")
    * @required
    */
   public $management;

   ## VPS properties

   /**
    * @type("string")
    * @title("name")
    * @description("Server Name")
    */
   public $name;
}
?>

The vps type also declares VPS properties. In the above code, we assume a server needs to have only a name assigned.

Property Type Description
name String Host name

Presentation Logic

According to the package structure, the following scripts define the application UI inside UX1.

Server List

The ui/servers.js file contains the JavaScript code for the servers view. It must present a list of all provisioned VPSes and allow adding more VPSes. For simplicity, it does not expose any other functions.

../../../_images/start-vps-list.png

Follow these steps to create a new script for UX1 based on the Single Page Application technology.

Note

In a single-page application, all application views are loaded into the same page dynamically, thus improving the UI performance by decreasing the number of page loads and transfers from page to page. To create a single page, a special HTML file is added automatically to the APS package during the package compilation (building) process.

  1. Replace the contents of the file with the following JavaScript structure based on the APS JS modules and Dojo API:

    define([
       "dojo/_base/declare",
    
       "aps/View",
       "aps/ResourceStore"
    ], function (
       declare,
    
       View,
       Store
    ) {
       return declare(View, {
          init: function() {
    
             /* Define the data store */
    
             /* Define a handler for the *New* button click */
    
             /* Define and return widgets */
    
          }, // End of Init
    
          onContext: function() {
    
          },
    
          onHide: function() {
    
          }
    
       });
    });
    

    Note

    The order of modules in the first array of the define function and the order of arguments presenting those modules in the main call-back function must correspond to each other.

    The above code creates a new custom module based on the aps/View module. Inside it, you should define the init and other methods that follow the latter as explained in the Single Page Application document.

    • The init method is called only one time, namely when loading the view into the page. It must draw all necessary visual elements and controls.
    • We do not redefine the onShow method that follows the init method when opening the view for the first time and that is called each time the UX1 opens the view after that.
    • The onContext method follows the onShow method and allows processing the data that were not available for the previous methods yet.
    • The onHide method is called by the UX1 just before closing the view and moving a user to another view.
  2. In the init function, define the data store that will bring the JSON representation of the provisioned VPSes to the view. You should create the store from the aps/ResourceStore module. In the store, specify the APS type ID of the required VPSes and the APS controller endpoint (practically always /aps/2/resources/) to send REST requests to.

    var vpsStore = new Store({
       apsType: "http://aps-standard.org/samples/starter1p/vps/1.0",
       target: "/aps/2/resources/"
    });
    
  3. In the init function, define the handler of the button that will start creating a VPS. The handler must call the server-new view that must actually do the required operation.

    var add = function() {
       /* Start the process of creating a VPS by going to the relevant view */
       aps.apsc.gotoView("server-new");
    };
    
  4. In the init function, define and return the widget hierarchy. To show a list of VPSes with their properties, use the aps/Grid container. In this simplified project, we use only one property name and one button New.

    return ["aps/Grid", {
       id: this.genId("srv_grid"),
       store: vpsStore,
       columns: [{
             field: "name",
             name: "Name"
       }]}, [
          ["aps/Toolbar", [
             ["aps/ToolbarButton", {
                id: this.genId("srv_new"),
                iconClass:"fa-plus",
                type: "primary",
                label: "New",
                onClick: add
             }]
          ]]
    ]];
    

    Key points:

    • The genId method generates a unique widget ID. Later, its counterpart byId will be used to find out the grid.
    • The grid columns are filled in from the vpsStore data source defined earlier.
    • The field property must refer to the respective property in the vps type.
    • In the toolbar, a button with the New label will be handled by the add handler defined earlier.
  5. The onContext method must refresh the grid, when a user comes back to the view after a new VPS is provisioned.

    onContext: function() {
       this.byId("srv_grid").refresh();
       aps.apsc.hideLoading();
    },
    

    Typically, you need to hide the “Loading..” state after completion of the required operations. The aps.apsc.hideLoading method does it.

  6. The onHide method is called before leaving the view for another view. Make the New button available after it is clicked and the user is forwarded to the server-new view. If you don’t do it, the button will be inactive when the user comes back to the servers view.

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

This completes the development of the view. You can compare the created source with the sample servers.js file.

Server Creation

The server-new.js file contains the source code for the server-new view used to create a VPS. It must present a set of VPS properties with default values and allow a customer to change the properties. A customer will be able to cancel the operation or commit creation of the VPS. In either case, the view will return the customer back to the servers view.

../../../_images/start-vps-new.png

Follow these steps to create a new script.

  1. Use the following template for the script:

    define([
          "dojo/_base/declare",
          "dojox/mvc/getPlainValue",
          "dojox/mvc/at",
          "dojox/mvc/getStateful",
          "dojo/when",
    
          "aps/View",
          "aps/ResourceStore"
       ],
       function (
          declare,
          getPlainValue,
          at,
          getStateful,
          when,
    
          View,
          Store
       ) {
          return declare(View, {
             init: function() {
                /* Declare the data sources */
    
                /* Define and return widgets */
    
             }, // End of Init
    
             /* Create handlers for the navigation buttons */
    
             onCancel: function() {
    
             },
    
             onSubmit: function() {
    
             }
    
          });   // End of Declare
       });      // End of Define
    

    The declared view contains definition of the init method and two navigation handlers.

  2. In the init function, create a VPS model that you will sync with the VPS properties defined through the widgets. For the object containing only one property, it looks as follows:

    this.vpsModel = getStateful({
       "aps": {
          "type": "http://aps-standard.org/samples/starter1p/vps/1.0"
       },
       "name": ""
    });
    
  3. In the init function, define and return the widgets that customers will use to assign technical parameters to a VPS. We will use only one input widget aps/TextBox to enter the VPS name. In accordance with the recommended Widget Hierarchy, let us wrap it into aps/FieldSet and wrap the latter into aps/Panel.

    return ["aps/Panel", {
          id: this.genId("srvNew_form")
       }, [
          ["aps/FieldSet", {
                id: this.genId("srvNew_properties"),
                title: "General"
             },
             [
                ["aps/TextBox", {
                   id: this.genId("srvNew_name"),
                   label: "Server Name",
                   value: at(this.vpsModel, "name"),
                   required: true
                }]
             ]
          ]
    ]];
    

    The widget used to assign a VPS property is synced with the vpsModel model by means of the dojox/mvc/at method as explained in the Model section.

  4. Define the onCancel handler:

    onCancel: function() {
       aps.apsc.gotoView("servers");
    },
    

    The handler is called when a user clicks on the Cancel button. It returns the user back to the servers view.

  5. Define the onSubmit handler:

    onSubmit: function() {
       aps.context.subscriptionId = aps.context.vars.management.aps.subscription;
    
       var vpsStore = new Store({
           apsType: "http://aps-standard.org/samples/starter1p/vps/1.0",
           target: "/aps/2/resources/" + aps.context.vars.management.aps.id + "/vpses"
       });
       when(vpsStore.put(getPlainValue(this.vpsModel)),
           function() {
               aps.apsc.gotoView("servers");
           }
       );
    }
    

    The handler is called when a user clicks on the Submit button. It must request the APS controller to provision the new VPS and then return the user back to the servers view.

    Key points:

    • Since a customer may have many subscriptions, the first action identifies the current subscription through the aps.subscription property of the management variable declared in metadata.
    • The vpsStore object defines the data store to save a new VPS. The target is the URI of the vpses link collection of the management resource as specified by the resource model. The URI consists of the base APS controller endpoint, APS ID of the management context (presented in the management variable), and the link collection name vpses.
    • The dojo/when method calls the put operation of the data store to provision the required resource and then, on completion of the operation, calls the gotoView method to return the user back to the servers view.

This completes the view development. You can compare the created source with the sample server-new.js file.

Conclusion

You have completed the project development phase. Your project files should look similar to the files inside the sample package.

Now, you can proceed to the project implementation on your test platform.