This is the central phase of the APS application life cycle, where you actually develop all components of the APS application.
In this document:
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>
<vendor>
<name></name>
<homepage></homepage>
</vendor>
<packager>
<name></name>
<homepage></homepage>
</packager>
<presentation>
<summary>Demo package</summary>
<description>Demonstrates an APS project lifecycle</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.
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.
Application name, for example:
<name>Simplest demo project</name>
Application home page, for example:
<homepage>http://docs.cloudblue.com/cbc/sdk/apps/start/</homepage>
Main parameters of the application vendor and the packager, for example:
<vendor>
<name>APS team</name>
<homepage>http://docs.cloudblue.com/cbc/sdk/</homepage>
</vendor>
<packager>
<name>APS team</name>
<homepage>http://docs.cloudblue.com/cbc/sdk/</homepage>
</packager>
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>
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:
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.
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.
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 "";
}
}
?>
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;
}
?>
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 |
---|---|---|
|
String |
Host name |
According to the package structure, the following scripts define the application UI inside UX1.
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.
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.
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.
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/"
});
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");
};
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 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.
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.
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.
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.
Follow these steps to create a new script.
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.
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": ""
});
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.
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.
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.
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.