In this step, you will design and develop a view source that allows customers to assign service resources to the new users.
In this document:
In meta description, you added the add-user-service.js
file as the source
for the service assignment step in the system User Creation Wizard.
When designing the view, you should issue requirement for the view development similar to
those we will follow in this project:
newvps.json
file and then allow a customer
to select one of the available offers and to change properties before proceeding to the next step.onNext
and onPrev
button handlers to direct a customer to the respective wizard step.
The former handler must call the aps.apsc.next()
method to forward the customer to the next wizard step.
The wizard will call the aps.biz.commit
method to complete the APS Biz requests created by the view.In this project, let us expect the following user experience (UX) for customers:
The following view layout complies with the UX requirements:
This section continues the demo project from the previous step.
When creating the ui/add-user-service.js
script from scratch, follow these steps.
Create the script skeleton:
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dojox/mvc/getPlainValue",
"dojox/mvc/at",
"dojo/promise/all",
"dojox/mvc/getStateful",
"dojox/mvc/StatefulArray",
"aps/ResourceStore",
"aps/Memory",
"aps/TextBox",
"aps/json!./wizard/newvps.json",
"aps/View"
],
function (declare, lang, getPlainValue, at, all, getStateful, StatefulArray,
Store, Memory, TextBox, newVPS, View) {
/* Declare global variables */
/* Define a global function to request the platform for creation of new VPSes */
function resourceRequest(tileId, offerById, view, subscriptionId) {
// Send a Biz API request for resource usage change
}
return declare(View, {
init: function() {
/* Render a gird cell to allow entering a host name */
/* Set a watcher for selection of a tile */
/* Define and return widgets */
}, // End of Init
/* Process data available at the onContext phase */
onContext: function() {
/* Define data for the APS Biz API */
/* Split into 2 cases */
/* First case - I'm back from another wizard step */
if (aps.context.wizardData[
"http://aps-standard.org/samples/suwizard1p#add-user-service"
]) {
all({
// Retrieve only a list of users
}).then(function(data) {
// Update only the grid with users and host names
}).then(function() {
aps.apsc.hideLoading();
});
}
/* Second case - I'm here for the first time. */
else {
all({
// Retrieve all resources to be processed
}).then(function(data) {
/* Update the model with a list of users */
/* Prepare the dictionary for offers */
/* Define the offer selection list */
/* Define the tiles presenting offers */
/* Check if the default Offer is available */
})
/* Hide loading once all async operations are completed */
.then(function() {
aps.apsc.hideLoading();
});
}
}, // End of onContext
onNext: function() {
// Go to the next wizard step
},
onPrev: function() {
// Return to the previous step
}
}); // End of Declare
}); // End of Define
Keynotes:
resourceRequest
function to operate the
Biz API requests.init
method sets a watcher for the selection of an offer tile and defines the screen general layout
containing a grid with the user list and a set of tiles with offers to select.onContext
method will fill in the data sources with actual data from the APS controller and from the
the User Creation wizard. It must analyze the offers available in the subscription and then
build the list of offers as a set of tiles with the respective descriptions.Declare the global variables, the view itself and the Offer-by-ID dictionary that makes easy to find an offer by its APS ID:
var self,
offerById = {};
Define the global resourceRequest
function:
function resourceRequest(tileId, offerById, view, subscriptionId) {
var offerId = view.byId(tileId).get('offerId');
var offer = offerById[offerId];
var vpsModel = getStateful({"data": newVPS}); // Default VPS properties
vpsModel.data.set({
offer: {aps: {id: offerId}},
platform: offer.platform,
hardware: offer.hardware
});
var usersQuery = view.byId("addUsers_grid").get("store").query();
all({
users: usersQuery,
total: usersQuery.total
}).then(function(data) {
var users = data.users,
usersTotal = data.total;
var deltas = [
{
apsType: "http://aps-standard.org/samples/suwizard1p/vps/1.0",
delta: usersTotal
},
{
apsType: "http://aps-standard.org/samples/suwizard1p/offer/1.0",
apsId: offerId,
delta: usersTotal
}
];
/* Call the APS Biz API to provision VPSes for the new users*/
var request = {};
request[subscriptionId] = {
deltas: deltas,
operations: users.map(function(user) {
return {
provision: lang.mixin(getPlainValue(vpsModel.data), {
name: user.hostName,
user: {aps: {id: user.id}},
userName: user.name
})
};
})
};
aps.biz.requestUsageChange(request).then(null, function (err) {
console.error(err); // For debugging only
});
});
} // End of resourceRequest() definition
Keynotes:
aps.biz.requestUsageChange
request
to order the offer for all new users.
On receiving that request, the platform must identify if the subscription limits allow the customer to increase
the resource usage. If there are not enough available resources, the platform must prompt the customer to buy
additional resources as illustrated in the Add Users provisioning step.In the init
method, define the renderCell
handler called renderHostName
here and used to customize a
grid column. The grid with the new users must allow a customer to enter host names.
Use the aps/TextBox
widget for this:
self = this;
var renderHostName = function(row) {
return new TextBox({
value: at(row, "hostName"),
required: true
});
};
Each row of the grid is based on its own model created by the getStateful
method inside the onContext
method
and passed as the input argument row
when calling the handler. The latter synchronizes the
cell with that model using the at
method.
In the init
method, set a watcher for a stateful array. This array will be assigned to the selectionArray
of the set of tiles. When a customer selects an offer tile, the watcher will call the resourceRequest
function
to send an APS Biz request to the platform.
/* Set a watcher for selection of a tile */
var selectedTiles = new StatefulArray();
selectedTiles.watchElements(function(index, removals, adds){
if(adds.length == 1) resourceRequest(
adds[0],
offerById,
self,
aps.context.subscriptionId
);
});
In the init
method, define the page layout by a set of widgets returned by the method:
return [
["aps/Grid",{
id: this.genId("addUsers_grid"),
store: new Memory({data: [], idProperty: "id"}),
columns: [
{ name: "User Name", field: "name" },
{ name: "Host Name", field: "hostName", renderCell: renderHostName }
]
}],
["aps/Tiles", {
id: this.genId("addUsers_offerSet"),
title: "Select an offer",
selectionMode: "single",
required: true,
store: new Memory({data: []}),
selectionArray: selectedTiles
}, [
[ "aps/Tile", {
offerId: at("rel:", "id"),
id: this.genId("${offerId}"),
gridSize: "md-4 xs-12",
title: at("rel:", "name")
}, [
[ "aps/Output", {
value: at("rel:", "os"),
content: "Operating System: ${value}"
}],
[ "aps/Output", {
value: at("rel:", "cpu"),
content: "CPU cores: ${value}"
}],
[ "aps/Output", {
value: at("rel:", "memory"),
content: "Memory: ${value} MB"
}],
[ "aps/Output", {
value: at("rel:", "diskspace"),
content: "Disk space: ${value} GB"
}],
[ "aps/Output", {
value: at("rel:", "priceText")
}],
[ "aps/Output", {
usage: at("rel:", "usage"),
limit: at("limit"),
content: "${usage} of ${limit} servers are used."
}]
]]
]]
];
In accordance with the design requirements, there are two top-level containers:
onContext
method later.selectionArray
property refers to the selectedTiles
array declared earlier.In the onContext
method, define data necessary to build the widgets and to call the Biz API requests.
Specify the current subscription (find its ID in the input variable context
) as the one used by default by
all requests to the APS controller:
aps.context.subscriptionId = aps.context.vars.context.aps.subscription;
Prepare a store to retrieve all offers of the application:
var offerStore = new Store({
target: "/aps/2/resources",
apsType: "http://aps-standard.org/samples/suwizard1p/offer/1.0"
});
This will allow the view module to get any technical details of the offers.
Prepare a filter to retrieve all subscribed offers of the application:
var offerFilter = { filter:
[{ apsType: "http://aps-standard.org/samples/suwizard1p/offer/1.0" }]
};
This will allow the view to propose the proper offers and retrieve their commercial data.
Define the parent container, where the method will update the offer tiles:
var parentContainer = self.byId("addUsers_offerSet"); // ``aps/Tiles``
In the onContext
method, define the data processing when the customer re-enters the view from another
wizard step:
/* I'm back from another wizard step */
if (aps.context.wizardData[
"http://aps-standard.org/samples/suwizard1p#add-user-service"
]) {
all({
newUsers: aps.biz.getResourcesToBind(),
oldUsers: self.byId("addUsers_grid").get("store").query()
}).then(function(data) {
var newUsers = data.newUsers.users,
oldUsers = data.oldUsers;
/* User processing */
var users = newUsers.map(function(user) {
var oldUser = oldUsers.find(function(oldUser) {
return oldUser.id === user.aps.id;
});
return getStateful({
id: user.aps.id,
name: user.fullName,
hostName: oldUser ? oldUser.hostName : ""
});
});
var usersStore = new Memory({data: users, idProperty: "id"});
self.byId("addUsers_grid").set("store", usersStore);
/* Check if the default Offer is available for the updated user list */
resourceRequest(parentContainer.get(
"selectionArray")[0],
offerById,
self,
aps.context.subscriptionId
);
}).then(function() {
aps.apsc.hideLoading();
});
}
The above code anticipates some changes in the list of new users. It restores the host names for those users that remained in the list. This makes the UI more friendly for the customer. In this case, there is no need to change anything in the set of offers saved from the previous visit of the view.
In the onContext
method, define the data processing when the customer enters the view for the first time:
/* I'm here for the 1st time */
else {
/* Retrieve the resources to be processed */
all({
newUsers: aps.biz.getResourcesToBind(), // New users
subscribedOffers: aps.biz.getResourcesInfo(offerFilter), // Subscribed offers
allOffers: offerStore.query() // All offers of the application
}).then(function(data) {
var newUsers = data.newUsers,
subscribedOffers = data.subscribedOffers,
allOffers = data.allOffers;
if(subscribedOffers.length === 0) return; // No offers in the subscription
/* User processing */
var users = newUsers.users.map(function(user) {
return getStateful({
id: user.aps.id,
name: user.fullName,
hostName: ""
});
});
var usersStore = new Memory({data: users, idProperty: "id"});
self.byId("addUsers_grid").set("store", usersStore);
/* Fill in the Offer-by-ID dictionary */
allOffers.forEach(function(offer) {
offerById[offer.aps.id] = offer;
});
/* Define the offer selection list */
var offers = subscribedOffers.map(function(offer) {
var offerApsResource = offerById[offer.apsId];
return {
id: offer.apsId,
name: offerApsResource.name,
os: offerApsResource.platform.OS.name,
cpu: offerApsResource.hardware.CPU.number,
memory: offerApsResource.hardware.memory,
diskspace: offerApsResource.hardware.diskspace,
limit: offer.limit,
usage: offer.usage,
priceText: offer.priceText
};
});
var offerCache = new Memory({data: offers, idProperty: "id"});
/* Define the tiles presenting the subscribed offers */
parentContainer.removeAll(); // Remove all child tiles
parentContainer.set('store', offerCache);
/* Check if the default Offer is available for all new users */
resourceRequest(
parentContainer.get("selectionArray")[0],
offerById,
self,
aps.context.subscriptionId
);
})// End of all-then callback function
/* Hide loading once all async operations are completed */
.then(function() {
aps.apsc.hideLoading();
});
}
The above code consists of the following sections:
init
method.resourceRequest
function to verify if the default offer is available for all
new users.In the onNext
navigation handler, validate the current view and then forward the customer to the next wizard step
passing any data to the wizard. The passed data allows the onContext
method to identify if the customer enters
the view for the first time or re-enters the view from another wizard step.
/* Clear out all messages and validate the data */
var page = this.byId("apsPageContainer");
page.get("messageList").removeAll();
if (!page.validate()) {
aps.apsc.cancelProcessing();
return;
}
aps.apsc.next("I was here"); // Flag for the case I'll be back
In the onPrev
navigation handler, validate the current view and then forward the customer to the previous wizard
step passing any data to the wizard.
aps.apsc.prev("I was here"); // Flag for the case I'll be back
You have completed the development of a view source code in the ui/add-user-service.js
file.
Note
All service resources (VPSes) created in this case will have identical properties. To change them, the customer will need to edit them after creation of the resources.
The project file you have created is similar to the respective file in the
sample package
.