Tutorial: User Browser Workshop App
This tutorial contains code snippets and descriptions that you can combine to build a complete application. It builds off the project skeleton code found at the github project repo.
By the end of this tutorial, you will:
- Know how to create an application that displays on the Zoweâ„¢ Desktop
- Know how to create a Dataservice which implements a simple REST API
- Be introduced to Typescript programming
- Be introduced to simple Angular web development
- Have experience in working with the Zowe Application Framework
- Become familiar with one of the Zowe Application widgets: the grid widget
warning
Before continuing, make sure you have completed the prerequisites for this tutorial:
- Setup up the zlux-app-server locally. :::
So, let's get started!
- Constructing an app skeleton
- Building your First Dataservice
- Adding your First Widget
- Adding Zowe App-to-App Communication
#
Constructing an App SkeletonDownload the skeleton code from the project repository. Next, move the project into the zlux
source folder created in the prerequisite tutorial.
If you look within this repository, you'll see that a few boilerplate files already exist to help you get your first application plug-in running quickly. The structure of this repository follows the guidelines for Zowe application plug-in filesystem layout, which you can read more about on the wiki.
#
Defining your first pluginWhere do you start when making an application plug-in? In the Zowe Application Framework, an application plug-in is a plug-in of type "Application". Every plug-in is bound by their pluginDefinition.json
file, which describes its properties.
Let's start by creating this file.
Create a file, pluginDefinition.json
, at the root of the workshop-user-browser-app
folder.
The file should contain the following:
A description of the values that are placed into this file can be found on the wiki.
Note the following attributes:
- Our application has the unique identifier of
org.openmainframe.zowe.workshop-user-browser
, which can be used to refer to it when running Zowe. - The application has a
webContent
attribute, because it will have a UI component that is visible in a browser.- The
webContent
section states that the application's code will conform to Zowe's Angular application structure, due to it stating"framework": "angular2"
- The application plug-in has certain characteristics that the user will see, such as:
- The default window size (
defaultWindowStyle
), - An application plug-in icon that we provided in
workshop-user-browser-app/webClient/src/assets/icon.png
, - That we should see it in the browser as an application plug-in named
User Browser
, the value ofpluginShortNameDefault
.
- The default window size (
- The
#
Constructing a Simple Angular UIAngular application plug-ins for Zowe are structured such that the source code exists within webClient/src/app
. In here, you can create modules, components, templates and services in any hierarchy. For the application plug-in we are creating however, we will add three files:
- userbrowser.module.ts
- userbrowser-component.html
- userbrowser-component.ts
At first, let's just build a shell of an application plug-in that can display some simple content. Fill in each file with the following content.
userbrowser.module.ts
userbrowser-component.html
userbrowser-component.ts
#
Packaging Your Web AppAt this time, we've made the source for a Zowe application plug-in that should open in the Zowe Desktop with a greeting to the planet. Before we're ready to use it however, we must transpile the typescript and package the application plug-in. This will require a few build tools first. We'll make an NPM package in order to facilitate this.
Let's create a package.json
file within workshop-user-browser-app/webClient
.
While a package.json
can be created through other means such as npm init
and packages can be added through commands such as npm install --save-dev typescript@2.9.0
, we'll opt to save time by just pasting these contents in:
Now we are ready to build.
Let's set up our system to automatically perform these steps every time we make updates to the application plug-in.
- Open a command prompt to
workshop-user-browser-app/webClient
. - Set the environment variable
MVD_DESKTOP_DIR
to the location of zlux-app-manager/virtual-desktop. For example, setMVD_DESKTOP_DIR=../../zlux-app-manager/virtual-desktop
. This is needed whenever building individual application web code due to the core configuration files being located in virtual-desktop. - Execute
npm install
. - Execute
npm run-script start
.
After the first execution of the transpilation and packaging concludes, you should have workshop-user-browser-app/web
populated with files that can be served by the Zowe Application Server.
#
Adding Your App to the DesktopAt this point, your workshop-user-browser-app folder contains files for an application plug-in that could be added to a Zowe instance. We will add this to our own Zowe instance. First, ensure that the Zowe Application Server is not running. Then, navigate to the instance's root folder, /zlux-app-server
.
Within, you'll see a folder, plugins
. Take a look at one of the files in the folder. You can see that these are JSON files with the attributes identifier and pluginLocation. These files are what we call Plugin Locators, since they point to a plug-in to be included into the server.
Let's make one ourselves. Make a file /zlux-example-server/plugins/org.openmainframe.zowe.workshop-user-browser.json
, with the following contents:
When the server runs, it will check for these types of files in its pluginsDir
, a location known to the server through its specification in the server configuration file. In our case, this is /zlux-app-server/deploy/instance/ZLUX/plugins/
.
You could place the JSON directly into that location, but the recommended way to place content into the deploy area is through running the server deployment process. Simply:
- Open up a (second) command prompt to
zlux-build
ant deploy
Now you're ready to run the server and see your application plug-in.
cd /zlux-example-server/bin
../nodeCluster.sh
.- Open your browser to
https://hostname:port
. - Login with your credentials.
- Open the application plug-in on the bottom of the page with the green 'U' icon.
Do you see the Hello World message from this earlier step?. If so, you're in good shape! Now, let's add some content to the application plug-in.
#
Building your first DataserviceAn application plug-in can have one or more Dataservices. A Dataservice is a REST or Websocket endpoint that can be added to the Zowe Application Server.
To demonstrate the use of a Dataservice, we'll add one to this application plug-in. The application plug-in needs to display a list of users, filtered by some value. Ordinarily, this sort of data would be contained within a database, where you can get rows in bulk and filter them in some manner. Retrieval of database contents, likewise, is a task that is easily representable through a REST API, so let's make one.
- Create a file,
workshop-user-browser-app/nodeCluster/ts/tablehandler.ts
Add the following contents:
This is boilerplate for making a Dataservice. We lightly wrap ExpressJS Routers in a Promise-based structure where we can associate a Router with a particular URL space, which we will see later. If you were to attach this to the server, and do a GET on the root URL associated, you'd receive the {"greeting":"hello"} message.
#
Working with ExpressJSLet's move beyond hello world, and access this user table.
- Within
workshop-user-browser-app/nodeCluster/ts/tablehandler.ts
, add a function for returning the rows of the user table.
Because we reference the usertable file through import, we are able to refer to its metadata and columns attributes here.
This respondWithRows
function expects an array of rows, so we'll improve the Router to call this function with some rows so that we can present them back to the user.
- Update the UserTableDataservice constructor, modifying and expanding upon the Router.
Zowe's use of ExpressJS Routers allows you to quickly assign functions to HTTP calls such as GET, PUT, POST, DELETE, or even websockets, and provides you with easy parsing and filtering of the HTTP requests so that there is very little involved in making a good API for users.
This REST API now allows for two GET calls to be made: one to root /, and the other to /filter/value. The behavior here is as is defined in ExpressJS documentation for routers, where the URL is parameterized to give us arguments that we can feed into our function for filtering the user table rows before giving the result to respondWithRows for sending back to the caller.
#
Adding your Dataservice to the Plugin DefinitionNow that the Dataservice is made, add it to our Plugin's definition so that the server is aware of it, and then build it so that the server can run it.
- Open a (third) command prompt to
workshop-user-browser-app/nodeCluster
. - Install dependencies,
npm install
. - Invoke the NPM build process,
npm run-script start
.- If there are errors, go back to [building the dataservice].(#building-your-first-dataservice) and make sure the files look correct.
- Edit
workshop-user-browser-app/pluginDefinition.json
, adding a new attribute which declares Dataservices.
Your full pluginDefinition.json should now be:
There's a few interesting attributes about the Dataservice we have specified here. First is that it is listed as type: router
, which is because there are different types of Dataservices that can be made to suit the need. Second, the name is table, which determines both the name seen in logs but also the URL this can be accessed at. Finally, fileName and routerFactory point to the file within workshop-user-browser-app/lib
where the code can be invoked, and the function that returns the ExpressJS Router, respectively.
- Restart the server (as was done when adding the application initially) to load this new Dataservice. This is not always needed but done here for educational purposes.
- Access
https://host:port/ZLUX/plugins/org.openmainframe.zowe.workshop-user-browser/services/table/
to see the Dataservice in action. It should return all of the rows in the user table, as you did a GET to the root / URL that we just coded.
#
Adding your first WidgetNow that you can get this data from the server's new REST API, we need to make improvements to the web content of the application plug-in to visualize this. This means not only calling this API from the application plug-in, but presenting it in a way that is easy to read and extract information from.
#
Adding your Dataservice to the AppLet's make some edits to userbrowser-component.ts, replacing the UserBrowserComponent Class's ngOnInit method with a call to get the user table, and defining ngAfterViewInit:
You might notice that we are referring to several instance variables that we have not declared yet. Let's add those within the UserBrowserComponent Class too, above the constructor.
Hopefully you are still running the command in the first command prompt, npm run-script start
, which will rebuild your web content for the application whenever you make changes. You might see some errors, which we will resolve by adding the next portion of the application.
#
Introducing Zowe Application Server GridWhen ngOnInit runs, it will call out to the REST Dataservice and put the table row results into our cache, but we haven't yet visualized this in any way. We need to improve our HTML a bit to do that, and rather than reinvent the wheel, we have a table visualization library we can rely on: ZLUX Grid.
If you inspect package.json
in the webClient folder, you'll see that we've already included @zlux/grid as a dependency (as a link to one of the Zowe github repositories) so it should have been pulled into the node_modules folder during the npm install
operation. We just need to include it in the Angular code to make use of it. To do so, complete these steps:
- Edit webClient/src/app/userbrowser.module.ts, adding import statements for the Zowe Application Server widgets above and within the @NgModule statement:
The full file should now be:
- Edit userbrowser-component.html within the same folder. Previously, it was just meant for presenting a Hello World message, so we should add some style to accommodate the zlux-grid element that we will also add to this template through a tag.
Note the key functions of this template:
- There is a button which when clicked will submit selected users (from the grid). We will implement this ability later.
- We show or hide the grid based on a variable
ngIf="showGrid"
so that we can wait to show the grid until there is data to present. - The zlux-grid tag pulls the Zowe Application Server Grid widget into our application, and it has many variables that can be set for visualization, as well as functions and modes.
- We allow the columns, rows, and metadata to be set dynamically by using the square bracket [ ] template syntax, and allow our code to be informed when the user selection of rows changes through
(selectionChange)="onTableSelectionChange($event)"
- We allow the columns, rows, and metadata to be set dynamically by using the square bracket [ ] template syntax, and allow our code to be informed when the user selection of rows changes through
- Small modification to userbrowser-component.ts to add the grid variable, and set up the aforementioned table selection event listener, both within the UserBrowserComponent Class:
The previous section, Adding your Dataservice to the application set the variables that are fed into the Zowe Application Server Grid widget, so at this point the application should be updated with the ability to present a list of users in a grid.
If you are still running npm run-script start
in a command prompt, it should now show that the application has been successfully built, and that means we are ready to see the results. Reload your browser's webpage and open the user browser application once more. Do you see the list of users in columns and rows that can be sorted and selected? If so, great, you've built a simple yet useful application within Zowe! Let's move on to the last portion of the application tutorial where we hook the Starter application and the User Browser application together to accomplish a task.
#
Adding Zowe App-to-App CommunicationApplications in Zowe can be useful and provide insight all by themselves, but a big advantage to using the Zowe Desktop is that applications can track and share context by user interaction. By having the foreground application request the application best suited for a task, the requested application can perform the task with context regarding the task data and purpose and you can accomplish a complex task by simple and intuitive means.
In the case of this tutorial, we are not only trying find a list of employees in a company (as was shown in the last step where the Grid was added and populated with the REST API), but to filter that list to find those employees who are best suited to the task we need to accomplish. So, our user browser application needs to be enhanced with two new abilities:
- Filter the user list to show only those users that meet the filter
- Send the subset of users selected in the list back to the application that requested a user list.
How do we do either task? Application-to-application communication! Applications can communicate with other applications in a few ways, but can be categorized into two interaction groups:
- Launching an application with a context of what it should do
- Messaging an application that is already open to a request or alert it of something
In either case, the application framework provides Actions as the objects to perform the communication. Actions not only define what form of communication should happen, but between which applications. Actions are issued from one application, and are fulfilled by a target application. But, because there might be more than one instance or window of an application open, there are Target Modes:
- Open a new application window, where the message context is delivered in the form of a Launch Context
- Message a particular, or any of the currently open instances of the target application
#
Adding the Starter AppIn order to facilitate app-to-app communication, we need another application with which to communicate. A 'starter' application is provided which can be found on github.
As we did previously in the Adding Your application to the Desktop section, we need to move the application files to a location where they can be included in our zlux-app-server
. We then need to add to the plugins
folder in the example server and re-deploy.
- Clone or download the starter application under the
zlux
folder
git clone https://github.com/zowe/workshop-starter-app.git
- Navigate to starter application and build it as before.
- Install packages with
cd webClient
and thennpm install
- Build the project using
npm start
- Next navigate to the
zlux-app-server
:
- create a new file under
/zlux-app-server/plugins/org.openmainframe.zowe.workshop-starter.json
- Edit the file to contain:
- Make sure the ./nodeCluster is stopped before running
ant deploy
underzlux-build
- Restart the ./nodeCluster under
zlux-app-server/bin
with the appropriate parameters passed in. - Refresh the browser and verify that the app with a Green S is present in Zowe Application Server.
#
Enabling CommunicationWe've already done the work of setting up the application's HTML and Angular definitions, so in order to make our application compatible with application-to-application communication, it only needs to listen for, act upon, and issue Zowe application Actions. Let's edit the typescript component to do that. Edit the UserBrowserComponent Class's constructor within userbrowser-component.ts to listen for the launch context:
Then, add a new method on the Class, provideZLUXDispatcherCallbacks, which is a web-framework-independent way to allow the Zowe applications to register for event listening of Actions.
At this point, the application should build successfully and upon reloading the Zowe page in your browser, you should see that if you open the Starter application (the application with the green S), that clicking the Find Users from Lookup Directory button should open the User Browser application with a smaller, filtered list of employees rather than the unfiltered list we see if opening the application manually.
We can also see that once this application has been opened, the Starter application's button, Filter Results to Those Nearby, becomes enabled and we can click it to see the open User Browser application's listing become filtered even more, this time using the browsers Geolocation API to instruct the User Browser application to filter the list to those employees who are closest to you!
#
Calling back to the Starter AppWe are almost finished. The application can visualize data from a REST API, and can be instructed by other applications to filter that data according to the situation. But, to complete this tutorial, we need the application communication to go in the other direction - inform the Starter application which employees you have chosen in the table!
This time, we will edit provideZLUXDispatcherCallbacks to issue Actions rather than to listen for them. We need to target the Starter application, since it is the application that expects to receive a message about which employees should be assigned a task. If that application is given an employee listing that contains employees with the wrong job titles, the operation will be rejected as invalid, so we can ensure that we get the correct result through a combination of filtering and sending a subset of the filtered users back to the starter application.
Add a private instance variable to the UserBrowserComponent Class:
Then, create the Action template within the constructor:
So, we created an Action which targets an open window of the Starter application, and provides it with an Object with a data attribute.
We'll populate this object for the message to send to the application by getting the results from Zowe Application Server Grid (this.selectedRows
will be populated from this.onTableSelectionChange
).
For the final change to this file, add a new method to the Class:
And we'll invoke this through a button click action, which we will add into the Angular template, userbrowser-component.html
, by changing the button tag for "Submit Selected Users" to:
Check that the application builds successfully, and if so, you've built the application for the tutorial! Try it out:
Open the Starter application.
Click the "Find Users from Lookup Directory" button.
- You should see a filtered list of users in your user application.
Click the "Filter Results to Those Nearby" button on the Starter application.
- You should now see the list be filtered further to include only one geography.
Select some users to send back to the Starter application.
Click the "Submit Selected Users" button on the User Browser application.
- The Starter application should print a confirmation message that indicates success.
And that's it! Looking back at the beginning of this document, you should notice that we've covered all aspects of application building - REST APIs, persistent settings storage, Creating Angular applications and using Widgets within them, as well as having one application communicate with another. Hopefully you have learned a lot about application building from this experience, but if you have questions or want to learn more, please reach out to those in the Foundation so that we can assist.