Forums >> Programming >> Open Source
My IBM i node.js Journey
by: bvstone

Jump to: 






bvstone

My IBM i node.js Journey

Posted:
My IBM i node.js Journey

I decided to keep a log of my trials and tribulations while working with node.js on the IBM i.

I have worked with it a little on a unix box and duplicated a few things I have done with RPG (non-DB related, though).  So I thought I'd get things going on our new V7R2 machine and see just how powerful node could be.

The articles in this post will keep track of the journey from start to where I am now.  I hope to update it frequently.






bvstone

Installing node.js

Posted:
Installing node.js

The first step of course is to install the node.js product.

I know I've seen pages that deal with this, and the first one I found was the following:

https://www.ibm.com/developerworks/ibmi/library/i-native-js-app-ibmi-with-nodejs/

It seemed to be pretty inclusive, including how to connect to your IBM i database as well as run commands.   What else could someone want?  

I rummaged through my DVDs and CDs that came with our machine and found the 5733OPS disc and stuck it in the optical drive of our 41A machine.  I then installed the licensed program and all the options:

  • GO LICPGM
  • Take option 11
  • On the top line, enter "1" for the Option, Enter 5733OPS for the Licensed Program, and *ALL for the Product Option. Press your Enter key twice and things should start installing.

Next is to install the required PTFs.  Never fun, especially if you're not a operations/hardware guy like me.  I actually spent 3 hours trying to figure out why my PTFs weren't applying (after countless IPLs) only to realize I was booting to the "A" side instead of the "B" side.  New hardware... so much fun!

I then continued following the instructions in the above page and everything seemed to look fine.


Last edited 03/27/2016 at 17:43:14





bvstone

Running the Example Programs

Posted:
Running the Example Programs

The example programs listed at https://www.ibm.com/developerworks/ibmi/library/i-native-js-app-ibmi-with-nodejs/ seemed to start of with "Hello World" and then from there continue on with very general, yet useful, examples.

Now, by "general" I mean just that.  The example has an input to enter an SQL statement (which the results of are returned as JSON) as well as a input to run a system command (not sure how safe that is...  yikes!)

First, I created a local IP address that I could use to run my node.js applications on (using CFGTCP option 1).  This IP address will be used in the applications to come.  The port number that is used is also specified in the node source so for this example I just chose to use a standard port of 80.

We also need a place that will hold the sample JS file and HTML file.  I decided to create a directory on my system named /nodejs that would be for all my node.js applications.  This of course should be easily changed if needed.  

I then created a subdirectory with my userid to hold my node.js source.  So, I had /nodejs/bvstone.  

Next I created the first node.js application from the sample and started up the application using Pase  (Pase is started using CALL QP2TERM.  I actually created a command named SSH that calls this to make it easier to remember).

node /nodejs/bvstone/sample.js           
Server running at http://192.168.1.39:80 

Once up and running I opened my browser and pointed it to http://192.168.1.39 and low and behold, I saw "Hello World."

I then continued on through the examples and even got the SQL example to work.  But, when I started on the CL command example I started getting some errors.  I did some searching and found I could turn on more debugging for my application using the following on my connection:

var conn = new xt.iConn(DBname);
conn.debug(true);

I then saw that I was getting the following error when trying to run a CL command:

CL statement : wrksyssts
[Error: SQLSTATE=57017 SQLCODE=-332 Character conversion between CCSID 1208 and CCSID 65535 not valid.]

If I ran it again I received a different error:

CL statement : WRKSYSSTS
[Error: SQLSTATE=HY010 SQLCODE=-99999 Error occurred in SQL Call Level Interface]  
 

I did a little digging and found that my user ID was set up to use *SYSVAL as the job CCSID.  The system value for QCCSID was 65535.  Well, that makes sense, I guess.  So I changed the system value for QCCSID to 37, signed off, signed back on, started my node job again and things were working great.

One important (and annoying) thing to remember is, if you make changes to your JavaScript code you will need to stop and restart your node instance to get the changes to load.  I only mention this because the instructions mentioned it only after the first change.  The next change it said to "refresh your web page" instead of restart your node instance.  Probably an oversight.  But, restarting each time seems a little tedious.  Maybe in this journey I'll find an answer or even a solution for this.

Next I will need to make my own example that works with real data to see how things really work in the real world.  That is what I'm most interested in.  No matter how much we hate to start with "Hello World" and other simple examples they are a necessary evil.

 


Last edited 03/27/2016 at 17:43:48





bvstone

Making My Own Example (That Does a POST)

Posted:
Making My Own Example (That Does a POST)

Now that we had the sample provided by IBM working, I decided I wanted to test out my own application that I had running on my linux machine.

This process was created to duplicate our key generation software already written in RPG.  I figured it might be a good idea to have a backup on another box to be able to generate keys if I needed to.  So a few months ago I wrote a package that did just that.

First I created my HTML page.  It looks like the following:

Very simply, it accepts some data to generate the key.  The HTML looks like the following:

<!DOCTYPE HTML> 
<html lang="en-US">
	<head> 
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>BVSTools Key Generation</title>
	</head> 
<style> 
input {
	height:30px;
	border:#ccc solid 1px 
}
input[type="text"] {
	width:500px 
}
input[type="submit"] {
	margin:1em; 
	width:120px 
} 
</style>
<body> 
	<form name="input" action="query" method="post">
		<div>Software</div>
		<input type="text" name="software"/>
		<div>Logical Serial Number</div>
		<input type="text" name="lsn"/>
		<div>Model</div>
		<input type="text" name="model"/>
		<div>Days</div>
		<input type="number" name="days"/>
		<div>Month</div>
		<input type="number" name="month"/>
		<div>Day</div>
		<input type="number" name="day"/>
		<div>Year</div>
		<input type="number" name="year"/>
		<input type="submit" value="Query"/>
	</form> 
</body>
</html>

Next we created the node.js source.  This source would only accept POST as the request type, which we will see in the following code:

var http = require('http');
var qs = require('querystring');
var fs = require('fs');
var url = require('url');
var keys = require('software');

var DBname = "*LOCAL"; 
var ip = "192.168.1.39";
var port = 80;

function padding_right(s, c, n) {
  if (! s || ! c || s.length >= n) {
    return s;
  }
  var max = (n - s.length)/c.length;
  for (var i = 0; i < max; i++) {
    s += c;
  }
  return s;
}

var webserver = http.createServer(function (req, res) { 
	var realPath = __dirname + url.parse(req.url).pathname;
	fs.exists(realPath,function(exists){
		if(!exists){
			
			if (req.method == 'POST') {
				var body = '';
				
				req.on('data', function(data) {
					body += data;
		            // Too much POST data, kill the connection!
					// 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
					if (body.length > 1e6)
						req.connection.destroy();
				});
				
				req.on('end', function() {
					res.writeHead(200, {'Content-Type': 'text/plain'}); 
					var post = qs.parse(body);
					var results = genKeys(post);
					res.write(results[0] + ':' + results[1]);
					res.end();
				});
			} else {
				res.writeHead(405, {'Content-Type': 'text/plain'});
				res.end('405 Method Not Allowed');
			}

		} else { 
			var file = fs.createReadStream(realPath);
			res.writeHead(200, {'Content-Type': 'text/html'}); 
			file.on('data',	res.write.bind(res)); 
			file.on('close', res.end.bind(res));
			file.on('error', function(err){ 
				res.writeHead(500, {'Content-Type': 'text/plain'}); 
				res.end("500 Internal Server Error"); 
			}); 
		}
	});
});

function genKeys(post) {
	var software = post['software'].toUpperCase();
	var lsn =  post['lsn'].toUpperCase();
	var model = post['model'].toUpperCase();
	var days = post['days'];
	var month = post['month'];
	var day = post['day'];
	var year = post['year'];
	var key;
	var dateKey;
	
	if(software && software.length > 0) {
		var serialModel = padding_right(lsn, ' ', 10) + padding_right(model, ' ', 4);
		var code = keys.getCode(serialModel, software);
		key = keys.getKey(code);
		dateKey = keys.getTempKey(software, year, month, day, days);
	} 

	return [key, dateKey];
}

webserver.listen(port, ip); 
console.log('Server running at http://' + ip + ':' + port);

This is a little more involved than the simple example we did before that only would spit out the results of an SQL statement or CL command.  Instead, we're seeing how we can import pieces of code that may have been developed on another machine, even running on a different OS.  

This was done using the following line of code:

var keys = require('software');

If we remember, I had set up a directory to hold my node.js applications.  It was /nodejs/bvstone.  I also decided to set up a subdirectory named "node_modules" under this directory where I could place my node modules.  Think of these, if you're an RPG programmer, as a module or service program.  The require() function pulls in the functionality of that node module so it is available in our application.

Update:  On a whim I decided to try and test if I needed to specify the qualified path to the software.js module  It appears I don't!  This Article explains where node.js looks for modules used on the require() function.  So instead of specifying var keys = require('./node_modules/software');, I only need to specify var keys = require(''software');

The main functions we use are getCode() and getTempKey().  The first accepts a string which is the logical serial number and model number of the machine the key is for as well as the software.

The getTempKey() function accepts a software name, year, month, day and number of days to generate a temporary key for.  So, in our code we see that after we've determined there is a POST request being made, we call a function named genKeys() that is part of our main node source.  This code performs the following functions:

  • Reads in the form data from the POST (which is passed in as a parameter)
  • Concatenates the serial number and model number together into one string that will be used to pass into our getCode() function.  This is done using a home-built function that allows us to pad the values with spaces (or any character) so the string is formatted as 10 characters that contain the logical serial number followed by 4 characters that contains the model number.  
  • This value, along with the name of the software, is passed into the getCode() function.  Remember, this is brought in using the require() function pulling in the available functions from our software.js node module.  (I won't post this since it's proprietary code)
  • We then pas in the year, month, day, number of days and software name so we can generate temporary key if we need one.
  • Finally, we simply exit the getKeys() function and return an array.  The first element being the license key and the second element being the temporary date key and display these in the browser.

It was neat seeing how I could pull in a node module that I had created before without changing a single character and having it work just fine on a different piece of hardware and OS.


Last edited 03/29/2016 at 08:03:48





bvstone

Trying Out the Express, Koa and Hapi Frameworks

Posted:
Trying Out the Express, Koa and Hapi Frameworks

When it comes to node.js I'm finding it's like an onion.  But, not in the sense of peeling back layers, but adding layers on top of node.js.

Take for example the many frameworks available.  Express seems to be the most popular.  Koa seems to be a close 2nd and Hapi, while not as popular now, seems to be the new up and comer.

What is a Framework?

Think of a framework as a big group of functions to make your life easier.  Just like in the RPG world we have the eRPG SDK and CGIDEV2 that make creating dynamic web applications with RPG super easy, node.js also has a few different packages, mentioned above, that also try to help make things easier (and, from what I've seen, seem to make everyone's web pages look the same as well).

What Does the Code Look Like for These Frameworks?

As of now I have only set up the basic "Hello World" applications for each of these frameworks.  Here they are:

Express (works fine):

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World from Express!');
});

app.listen(8080, '192.168.1.39', function () {
  console.log('Example app listening on port 8080!');
});

Koa (works fine):

var koa = require('koa');
var app = koa();

app.use(function *(){
  this.body = 'Hello World from KOA';
});

app.listen(8080, '192.168.1.39');

Hapi (doesn't work):

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server();
server.connection({ address: '192.168.1.39', port: 8080 });

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {
        reply('Hello, world from Hapi!');
    }
});

server.route({
    method: 'GET',
    path: '/{name}',
    handler: function (request, reply) {
        reply('Hello, ' + encodeURIComponent(request.params.name) + '!');
    }
});

server.start((err) => {

    if (err) {
        throw err;
    }
    console.log('Server running at:', server.info.uri);
});

Do These Frameworks Work on the IBM i?

From what I can tell, Express and Koa (with an exception explained later) seem to work fine.  But, the version of Hapi I installed seems to require node v4 or higher.  Checking the version of node installed on the IBM i we're seeing it v0.12.9 right now.  Hopefully that will be updated soon.

With Hapi we just get the following error when trying to start the server:

bash-4.2$ node app.js
/nodejs/bvstone/hapi/app.js:3
const Hapi = require('hapi');
^^^^^
SyntaxError: Use of const in strict mode.
    at exports.runInThisContext (vm.js:73:16)
    at Module._compile (module.js:443:25)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

Doing some searching I found this link that explains why:

https://github.com/hapijs/hapi/issues/2925

So, I guess for now Hapi is on hold for the IBM i.

With Koa we get this error:

bash-4.2$ node app.js
/nodejs/bvstone/koa/app.js:4
app.use(function *(){
                 ^
SyntaxError: Unexpected token *
    at exports.runInThisContext (vm.js:73:16)
    at Module._compile (module.js:443:25)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

Again, a search was found to explain this error:

https://github.com/koajs/koa/issues/179

Adding the --harmony flag to the node command seems to let the server run just fine.  I have no idea what that flag does, though.  :)

Express seems to work fine without any special needs.

I haven't gotten far enough into these frameworks yet, but I imagine trying to write applications without using one would be like writing CGI applications with RPG without using something like the eRPG SDK or CGIDEV2... only calling the HTTP APIs.  Even with wrappers, that gets really ugly, really fast.  So, I'm going to put together a simple application and try it with Express and Koa to see how they differ.


Last edited 03/29/2016 at 08:58:57





bvstone

My First Express App

Posted:
My First Express App

Express comes with an application generator that you can use to create the skeleton for an application.  Before we can use it, though, it needs to be installed (separate from Express itself).  That is done with the following command:

npm install express-generator -g

Once this is complete we can use the express command to create the skeleton of our application.  For this we chose to simply list printers available from our Google Cloud Print application.  (We actually have a file in a library named G4GPRTPF in our GreenTools for Google Apps library that contains this).

express listPrinters

This command produces the following results showing that the base skeleton for the application is created:

bash-4.2$ express listPrinters

   create : listPrinters
   create : listPrinters/package.json
   create : listPrinters/app.js
   create : listPrinters/public
   create : listPrinters/routes
   create : listPrinters/routes/index.js
   create : listPrinters/routes/users.js
   create : listPrinters/public/images
   create : listPrinters/views
   create : listPrinters/views/index.jade
   create : listPrinters/views/layout.jade
   create : listPrinters/views/error.jade
   create : listPrinters/public/javascripts
   create : listPrinters/public/stylesheets
   create : listPrinters/public/stylesheets/style.css
   create : listPrinters/bin
   create : listPrinters/bin/www

   install dependencies:
     $ cd listPrinters && npm install

   run the app:
     $ DEBUG=listPrinters:* npm start

We can see there are also a few extra instructions given to us just in case we forget.  So, we better run those as well (the && between the command simply means to run both commands in succession... pretty handy feature!):

bash-4.2$ cd listPrinters && npm install
debug@2.2.0 node_modules/debug
└── ms@0.7.1

serve-favicon@2.3.0 node_modules/serve-favicon
├── fresh@0.3.0
├── parseurl@1.3.1
├── etag@1.7.0
└── ms@0.7.1

cookie-parser@1.3.5 node_modules/cookie-parser
├── cookie-signature@1.0.6
└── cookie@0.1.3

morgan@1.6.1 node_modules/morgan
├── on-headers@1.0.1
├── basic-auth@1.0.3
├── depd@1.0.1
└── on-finished@2.3.0 (ee-first@1.1.1)

express@4.13.4 node_modules/express
├── escape-html@1.0.3
├── array-flatten@1.1.1
├── utils-merge@1.0.0
├── cookie-signature@1.0.6
├── merge-descriptors@1.0.1
├── fresh@0.3.0
├── methods@1.1.2
├── range-parser@1.0.3
├── vary@1.0.1
├── path-to-regexp@0.1.7
├── parseurl@1.3.1
├── cookie@0.1.5
├── content-type@1.0.1
├── etag@1.7.0
├── content-disposition@0.5.1
├── serve-static@1.10.2
├── depd@1.1.0
├── on-finished@2.3.0 (ee-first@1.1.1)
├── finalhandler@0.4.1 (unpipe@1.0.0)
├── qs@4.0.0
├── proxy-addr@1.0.10 (forwarded@0.1.0, ipaddr.js@1.0.5)
├── send@0.13.1 (destroy@1.0.4, statuses@1.2.1, ms@0.7.1, mime@1.3.4, http-errors@1.3.1)
├── type-is@1.6.12 (media-typer@0.3.0, mime-types@2.1.10)
└── accepts@1.2.13 (negotiator@0.5.3, mime-types@2.1.10)

body-parser@1.13.3 node_modules/body-parser
├── content-type@1.0.1
├── bytes@2.1.0
├── depd@1.0.1
├── on-finished@2.3.0 (ee-first@1.1.1)
├── http-errors@1.3.1 (inherits@2.0.1, statuses@1.2.1)
├── qs@4.0.0
├── type-is@1.6.12 (media-typer@0.3.0, mime-types@2.1.10)
├── iconv-lite@0.4.11
└── raw-body@2.1.6 (unpipe@1.0.0, bytes@2.3.0, iconv-lite@0.4.13)

jade@1.11.0 node_modules/jade
├── character-parser@1.2.1
├── void-elements@2.0.1
├── commander@2.6.0
├── mkdirp@0.5.1 (minimist@0.0.8)
├── jstransformer@0.0.2 (is-promise@2.1.0, promise@6.1.0)
├── constantinople@3.0.2 (acorn@2.7.0)
├── with@4.0.3 (acorn@1.2.2, acorn-globals@1.0.9)
├── clean-css@3.4.10 (commander@2.8.1, source-map@0.4.4)
├── transformers@2.1.0 (promise@2.0.0, css@1.0.8, uglify-js@2.2.5)
└── uglify-js@2.6.2 (uglify-to-browserify@1.0.2, async@0.2.10, source-map@0.5.3, yargs@3.10.0)

At this point I really have no idea what all this means.  But, let's move on anyhow.

What IP Address and Port Will My New App Run On?

With Apache most of us are used to specifying at least the port in the configuration file.  Because the IBM i allows you to create multiple IP addresses on one NIC, I feel it's always best to supply the IP address and the port to use.

With node, though, it seems they aren't set up for this type of environment.  So, I am going to do some modifications and set the IP address and port myself.

By default this new server will run on the IP address and Port specified by the environment variables process.env.IP and prcess.env.PORT OR localhost and port 3000 if these aren't set.  Because I have an IP address set up specifically for my node testing I want to make sure to use that one.  But how is this done?  The app.js file that is created doesn't show anything about what port and or IP will be used.

So, I did some searching and I found that the file ../<appname>/bin/www (no extension) is what is used to set the port.  Here is the code in question:

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);

What this is saying is, use the process.env.PORT environment variable.  If that isn't available, then use port 3000.  It doesn't even mention what IP address to use (again, because most PCs or servers don't have the neat feature the IBM i does of being able to create multiple IP addresses/interfaces).

So, lets modify this to set the IP address and the port we want to use:

/**
 * Get port from environment and store in Express.
 */

//var port = normalizePort(process.env.PORT || '3000');
//app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(8080, '192.168.1.39');

So, all we did was comment out a couple lines and then on the server.listen() function code in the port and IP address we specifically wanted to use.

If we then visit http://192.168.1.39:8080/ we will see the default Welcome message from express.  Next, we'll look at trying to update this app so that we can list the printers available to us via Google Cloud Print.


Last edited 03/29/2016 at 09:50:46





bvstone

Updating My First Express App to Do Something

Posted:
Updating My First Express App to Do Something

So, we've installed node.js.  We've installed Express.  We've created the skeleton for our first application.  Now, let's make it actually do something by using not only the database retrieval methods from the first sample (provided by IBM) but to also use Express and Jade to display some data.

What is Jade?

You probably caught the reference to Jade in the last sentence.  Remember when I said that node.js application development is like an onion, only in reverse?  Jade is just another layer that we need to worry about.

Jade is a template engine used to make creating your web pages more simple and less verbose.  Similar to how templates are used in the eRPG SDK and CGIDEV2, Express uses Jade by default as it's template engine.  

Now, Jade is also more complex and "alive" than templates used in RPG packages.  RPG templates used are simply text with placeholders to put data.  Jade's templates also have some structure and have the ability to use things such as loops and conditional statements.  While this is great, you now get to decide if you want your logic to be in your node application and/or your Jade template.  Again, the layers just keep getting more complex (however neat they are).

The List Printers Application

Let's get back to our application used to list printers we have available through Google Cloud Print.  This data is stored in a file on our IBM i in our G4G library named G4GPRTPF.  The file layout looks like the following:

 3/27/16             List File Field Description (LSTFFD)   
13:25:45                                                    
                                                            
File Name . . . . G4GPRTPF                                  
  Library . . . .   G4GBVS                                  
Format Descr  . .                                           
Format Name . . . RG4GPRT                                   
File Type . . . . PF            Unique Keys - N             
                                                            
Field Name FMT Start Lngth Dec Key Field Description        
GGPID       A      1   256         Google ID                
GGPPRTKEY   A    257    64         Printer Key              
GGPPRTID    A    321    64         Printer ID               
GGPSTATUS   A    385    64         Status                   
GGPNAME     A    449   256         Name  

For this application we will worry about the ID (GGPID) and the Printer Name (GGPNAME).

First we need to create a route.  That means, when someone goes to our site with the listPrinters path, we want that specific application to be run.  This most likely can be done with dynamic routing (as it is in Apache) but at this point, we haven't gotten to that yet so first we will update the app.js application inside the listPrinters directory that was created when we created our application skeleton.

...
var routes = require('./routes/index');
var users = require('./routes/users');
var listPrinters = require('./routes/listPrinters');

...

app.use('/', routes);
app.use('/users', users);
app.use('/listPrinters', listPrinters);

The above shows the modified code.  The two lines we added were:

var listPrinters = require('./routes/listPrinters');

and

app.use('/listPrinters', listPrinters);

This says when a user visits http://oursite.com/listPrinters to fire off the node module named listPrinters.js which should be located in the ../listPrinters/routes directory.  (You should also see the other two modules named index.js and users.js as well in this directory).

The node module code for listPrinters.js is as follows:

var express = require('express');
var db = require('/QOpenSys/QIBM/ProdData/Node/os400/db2i/lib/db2');
var router = express.Router();

var DBname = "*LOCAL";

/* GET home page. */
router.get('/', function(req, res, next) {

	var sql = 'select GGPID, GGPNAME from g4gbvs.g4gprtpf';
	console.log("SQL statement : " + sql);
	db.init();
	db.conn(DBname);
	db.exec(sql, function(rs) {
		console.log(rs.length);
		res.render('listPrinters', { title: 'List Printers', printerList: rs});
	});
	db.close();
	
});

module.exports = router;

This looks pretty simple, doesn't it!  Well, it sort of is.  

First we require express module, since we're running this node application in the express framework.

Next we require the database module that is specific to the IBM i.  This allows us to access IBM i DB2 data using SQL statements.

Next we run a simple SQL statement and render the result set using the render() function.  Now, this is where Jade comes in.  The render() function is in the documentation.

The first parameter is the name of the template to load, and following that we list variable names and data that we want to replace in the template.

In our example you'll notice we're replacing two things:

  • title - The title of the page
  • printerList - The list of printers we received tin the result set of our SQL statement.

Yes, we choose to pass an entire list into our template as a whole.  The other option would be to loop through the result set and try to render each item list 1 by 1.  But with the Jade template engine we can move that processing to the template.

So lets take a look at this template we keep hearing so much about:

extends layout

block content
	html
	head
		style
			table, th, td {
			border: 1px solid black;}
		h1= title
		
	body
		table
			tr
				th ID
				th Printer Name
			each printer in printerList
				tr
					td= printer.GGPID 
					td= printer.GGPNAME  


Yep, that's it.  Pretty simple, but then again so is our application.  

As you can probably tell, each of the HTML DOM items are created using keywords.  Those keywords are normally the same as the HTML tag, just without the <> brackets around them (ie, html produces <html>, body produces <body>, etc).

The indentation also plays an important role.  Everything indented after a specific keyword will be included inside of that specific container.  It is also important to note that indentation can be done with TABs or spaces, but NOT both.  I haven't decided which is best yet, though.  So for now I'm using tabs.

We then get to the table portion of our application that is used to list the printers.  And that part coincides with the render() function from our node application:

res.render('listPrinters', { title: 'List Printers', printerList: rs});  table
   tr
     th ID
     th Printer Name
     each printer in printerList
       tr
         td= printer.GGPID 
         td= printer.GGPNAME 

Remember in our node application where we passed the result set (rs) to a variable in our template named printerList?  Well, where's where see see how this takes place.

Notice the line:

each printer in printerList​

What this does is tell template to take the list of printers (printerList) and for each item in it, loop through and output a table row (each containing two table cells, one for the ID and one for the Printer Name).

It's actually pretty slick, especially since we can pass the result set directly to the template instead of having to convert it to a string, or convert it from JSON to XML, etc.  The less massaging we have to do the better.

So What Does the End Result Look Like?

Well, I thought you'd never ask.  After using navigating in PASE (or your favorite SSH terminal) to the ./listPrinters directory we issue the command:

npm start

or, if you wish to turn on debugging for the job, you can use this command:

DEBUG=listPrinters:* npm start

We then tell our browser to visit http://yourserver/listPrinters and are greeted with the following results (assuming everything works ok):

List Printers

ID Printer Name
bvstone@gmail.com Dell 2135cn MFP PCL6
bvstone@gmail.com Save to Google Docs
bvstone@gmail.com Fax (redirected 1)
bvstone@gmail.com Microsoft XPS Document Writer (redirected 1)
bvstone@gmail.com ABS PDF Driver v400 (redirected 1)
bvstone@gmail.com PrimoPDF (redirected 1)
bvstone@gmail.com Dell 2135cn MFP PCL6 (redirected 1)
bvstone@bvstools.com Save to Google Drive
bvstone@bvstools.com Dell 2135cn MFP PCL6
bvstone@bvstools.com HPCE924F (HP ENVY 7640 series)

Pretty slick, huh?  Yes it is.  But, I have a feeling as we move on there will be much more complexity that until we are comfortable with it may be a bit confusing.  Also, we don't know all the neat little tricks and features available to us yet either (for example, I have always designed my websites using Server Side Includes...I hope this is a possibility as it really helps create a web page built from smaller pieces).

You see, as a web application programmer that traditionally used RPG, I only had a couple things to worry about.  

  • My RPG programs
  • My Templates

Pretty straightforward.  Pretty easy.  The Apache config did all the "routing" with one simple command (well, two if you count the redirect directive and the container that allowed access to the RPG program in question).

With this node.js example using express we are starting to see the layers of complexity add up.  And at first it may be overwhelming, but I'm hoping in the end that is only because of the unfamiliarity with it.                           

                              


Last edited 03/29/2016 at 14:12:28





bvstone

Jade is Now Known as Pug?

Posted:
Jade is Now Known as Pug?

I know, it's been a while since I posted an update but I've actually been busy duplicating an application written in eRPG using node.js.

The application in question is how we keep track of our customers, their license keys, and what software they have installed.  It's not super complicated, yet still a good exercise.

During this project I have created my own modules for retrieving data and being able to easily display it using Express and Jade.  Well, today I decided I wanted to put together a short demo of these updates and while setting up my test environment I get this message:

npm WARN deprecated jade@1.11.0: Jade has been renamed to pug, please install the latest version of pug instead of jade

Well, this is great <tic>.  Just when I'm getting comfortable with Jade they go and change the name.  Talk about confusing.  

It turns out that the name is trademarked by someone else and they were forced to change.  

On one hand I understand why someone would want to protect their trademark, but I also would have hoped that before naming something the name would be researched for availability as well.  I understand most of these open source projects start out with mainly the hope of becoming something large, but still...  :)

I'll update this post in the future when I get around to researching how easy or hard this change is going to be.

 


Last edited 05/09/2016 at 09:00:31




Reply




Copyright 1983-2017 BVSTools
GreenBoard(v3) Powered by the eRPG SDK, MAILTOOL Plus!, GreenTools for Google Apps, jQuery, jQuery UI, BlockUI, CKEditor and running on the IBM i (AKA AS/400, iSeries, System i).