SlideShare a Scribd company logo
Copyright © 2016 M/Gateway Developments Ltd
EWD 3 Training Course
Part 30
Modularising
QEWD Applications
Rob Tweed
Director, M/Gateway Developments Ltd
Twitter: @rtweed
Copyright © 2016 M/Gateway Developments Ltd
Back-end Modules
• So far we've just saved our module files
directly as .js files into the node_modules
folder
– C:qewdnode_modules
– ~/qewd/node_module
• We used the convention:
– Application name = module file name
• So the back-end module for our demo1 application
was named demo1.js
Copyright © 2016 M/Gateway Developments Ltd
Fully-fledged Node.js Modules
• However, back-end QEWD modules can
be fully-fledged Node.js modules
– ie a complete module package directory,
including:
• index.js
• package.json
• README.md
• etc
– Can be published on NPM
– Can be installed from NPM
Copyright © 2016 M/Gateway Developments Ltd
Example
• Our demo1 application back-end module
• Instead of it just being in
C:ewd3node_modulesdemo1.js
• Have a directory C:ewd3node_modulesdemo1
– package.json
– index.js
– /lib folder
– Optional READme.md documentation file
Copyright © 2016 M/Gateway Developments Ltd
package.json
• As a minimum, specify these 3 properties:
{
"name": "demo1",
"version": "1.0.0",
"main": "index.js",
}
Copyright © 2016 M/Gateway Developments Ltd
package.json
• More typically and usefully:
{
"name": "demo1",
"version": "1.0.0",
"description": "Back end message handlers for demo app",
"main": "index.js",
"author": "Rob Tweed",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/robtweed/demo1.git "
}
}
Copyright © 2016 M/Gateway Developments Ltd
package.json
{
"name": "demo1",
"version": "1.0.0",
"description": "Back end message handlers for demo app",
"main": "index.js",
"author": "Rob Tweed",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/robtweed/demo1.git "
}
}
Tells Node.js
where to find
the main code/
script file
Copyright © 2016 M/Gateway Developments Ltd
index.js
• The main script file
• Can contain the code
• Usually just a pointer to the code
– Which is usually in a separate folder
• eg /lib
Copyright © 2016 M/Gateway Developments Ltd
index.js
'use strict';
module.exports = require('./lib/demo1');
So the main code is actually
in /lib/demo1.js
Copyright © 2016 M/Gateway Developments Ltd
/lib/demo1.js
module.exports = {
init: function() {
servicesAllowed: {
testService: true
},
handlers: {
login: function(messageObj, session, send, finished) {
// etc
},
// etc
}
};
So this is a standard QEWD back-end module
Copyright © 2016 M/Gateway Developments Ltd
Now a full Node.js/NPM module
• This demo1 module could now be
published to NPM if appropriate
• Others could then use it by installing it
– npm install demo1
Copyright © 2016 M/Gateway Developments Ltd
Module Name Mapping
• QEWD default back-end module name
mapping:
– Registered application name (eg demo1)
must be used as the name of either:
• The module file, eg:
– ~/qewd/node_modules/demo1.js
• The module directory, eg:
– ~/qewd/node_modules/demo1
» Within which is the package.json, etc
Copyright © 2016 M/Gateway Developments Ltd
Module Name Mapping
• However, by using the moduleMap QEWD
startup configuration array property, you
can specify any Node.js module to be the
back-end for a particular QEWD
application
– They don't have to have the same name
Copyright © 2016 M/Gateway Developments Ltd
Module Name Mapping
• Remember: the QEWD application name
is defined in its front-end app.js file when
you invoke the ewd-client start() function,
eg:
– EWD.start('test-app', $, io);
– This application will be registered on QEWD
as test-app
Copyright © 2016 M/Gateway Developments Ltd
Module Name Mapping
• But your QEWD startup file can define
back-end module mapping, eg:
– ~/qewd/qewd.js:
var config = {
managementPassword: 'keepThisSecret!',
serverName: 'My QEWD Server',
port: 8080,
poolSize: 2,
database: {
type: 'gtm'
},
moduleMap: {
test-app: '/path/to/myModule'
}
};
var qewd = require('qewd').master;
qewd.start(config);
Copyright © 2016 M/Gateway Developments Ltd
Module Name Mapping
• But your QEWD startup file can define
back-end module mapping, eg:
– ~/qewd/qewd.js:
var config = {
managementPassword: 'keepThisSecret!',
serverName: 'My QEWD Server',
port: 8080,
poolSize: 2,
database: {
type: 'gtm'
},
moduleMap: {
test-app: '/path/to/myModule'
}
};
var qewd = require('qewd').master;
qewd.start(config);
Your QEWD application
named test-app will use
/path/to/myModule
as its back-end module
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD Back-end Logic
• It's all too easy for the back-end module of
a QEWD application to become a huge file
– Ever increasing number of message handler
functions
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD Back-end Logic
• It's all too easy for the back-end module of
a QEWD application to become a huge file
– Ever increasing number of message handler
functions
• Each handler function can, itself, be
separated out into a module
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD Back-end Logic
• So instead of:
module.exports = {
handlers: {
test: function(messageObj, session, send, finished) {
var incomingText = messageObj.params.text;
finished({text: 'You sent: ' + incomingText});
}
}
};
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD Back-end Logic
• You could define each handler function in
its own sub-module:
var test = require('./handlers/test');
module.exports = {
handlers: {
test: test
}
};
function test(messageObj, session, send, finished) {
var incomingText = messageObj.params.text;
finished({text: 'You sent: ' + incomingText});
}
module.exports = test;
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD by using Services
• Further opportunities for modularisation
are provided by QEWD Services
– A Service Module is exactly like an
application's own back-end module
• Can contain one or more handler functions
– But Service Modules can be shared between
applications
– Can be used as the basis for a QEWD-based
Micro-Service architecture
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD by using Services
• QEWD Service Modules are good
candidates for packaging as fully-fledged
Node.js modules that can be published on
NPM
– That allows them to be re-used by other users
for their applications
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD by using Services
• QEWD Service Modules can also be
name-mapped, separating:
– The name you use to refer to the service
– The actual physical Node.js module
name/path used for the service
• For more details about EWD Services, see
Part 16 of this course
Copyright © 2016 M/Gateway Developments Ltd
Modularising QEWD Applications
• So the back-end logic of QEWD
applications can be modularised
• How about the front-end?
Copyright © 2016 M/Gateway Developments Ltd
Modularising Front-end Code
• Two options available
– Using "Fragments"
– Using a bundler such as Browserify or
WebPack
Copyright © 2016 M/Gateway Developments Ltd
Fragments
• Becoming a bit of an old-fashioned
approach
– Not appropriate for modern frameworks such
as React.js or Angular
• But for more conventional HTML
development, it can be an effective means
of modularising an application
– Can be part of a Service
Copyright © 2016 M/Gateway Developments Ltd
Fragments
• The concept:
– You load an initial HTML file (eg index.html)
which typically contains very little markup, but
loads all the JavaScript and CSS required by
the application
– Subsequently, fragments of HTML are loaded
dynamically into <div> or other tags, as a
result of events occurring in the browser
• Via Ajax or WebSockets
Copyright © 2016 M/Gateway Developments Ltd
Fragments
• Fragments can be included as part of a
QEWD Service Module
– Back-end message handler logic
– Associated fragment(s) of markup
– Publishable on NPM
• An application could therefore use
fragments and back-end logic from
multiple services
– ie: re-usable logic & markup / UI components
Copyright © 2016 M/Gateway Developments Ltd
EWD.getFragment()
• To fetch a fragment, in your browser-side
logic use:
– EWD.getFragment(argObj, callback);
Copyright © 2016 M/Gateway Developments Ltd
EWD.getFragment()
• To fetch a fragment, in your browser-side
logic use:
– EWD.getFragment(argObj, callback);
• argObj: object containing:
– name, eg: loginForm.html
» By default, will fetch from same directory as
index.html file
– targetId:
» id of the tag into which the markup will be inserted
» eg <div id="myTargetDiv"></div>
Copyright © 2016 M/Gateway Developments Ltd
EWD.getFragment()
• To fetch a fragment, in your browser-side
logic use:
– EWD.getFragment(argObj, callback);
• callback: invoked when fragment is loaded into
browser
– Single optional argument: name
» Filename of the fragment that was loaded
– Use callback to load handlers needed by fragment's
markup, eg button click handlers
Copyright © 2016 M/Gateway Developments Ltd
Example: index.html
<html>
<head>
<title>Fragment Demo</title>
<link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" />
</head>
<body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/ewd-client.js"></script>
<script src="app2.js"></script>
<div id="loginFormDiv"></div>
</body>
</html>
Copyright © 2016 M/Gateway Developments Ltd
Example: app2.js
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv'
};
EWD.getFragment(params, function(name) {
toastr.info('fragment loaded: ' + JSON.stringify(name));
});
});
EWD.start('demo1', $, io);
});
Copyright © 2016 M/Gateway Developments Ltd
Example
<html>
<head>
<title>Demo ewd-xpress application</title>
<link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" />
</head>
<body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/ewd-client.js"></script>
<script src="app2.js"></script>
<div id="loginFormDiv"></div>
</body>
</html>
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv'
};
EWD.getFragment(params, function(name) {
toastr.info('fragment loaded: ' + JSON.stringify(name));
});
Copyright © 2016 M/Gateway Developments Ltd
Example: loginForm.html
<table id="loginForm">
<tr>
<td>Username:</td>
<td><input type="text" id="username" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" id="password" /></td>
</tr>
<tr>
<td colspan="2">
<button id="loginBtn">Login</button>
</td>
</tr>
</table>
Just the markup
for the login form
Copyright © 2016 M/Gateway Developments Ltd
Try it out!
Copyright © 2016 M/Gateway Developments Ltd
Form won't do anything
eg nothing happens if you click this button
Copyright © 2016 M/Gateway Developments Ltd
Edit: app2.js
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv'
};
EWD.getFragment(params, function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val();
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();}
});
});
});
Copyright © 2016 M/Gateway Developments Ltd
Edit: app2.js
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv'
};
EWD.getFragment(params, function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val();
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();}
});
});
});
Now it will add a click
handler to the login button
Copyright © 2016 M/Gateway Developments Ltd
Edit: app2.js
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv'
};
EWD.getFragment(params, function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val();
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();}
});
});
});
Now it will add a click
handler to the login button
When clicked, the username
and password will be sent
as a message, with type
'login', to the
QEWD back-end
Copyright © 2016 M/Gateway Developments Ltd
Now it works
Copyright © 2016 M/Gateway Developments Ltd
Now it works
• But we've had to define the fragment
loader logic within our application's main
app.js logic
• It would be more modular if the logic
associated with the loginForm fragment
could be kept in a separate place
– Separately maintainable
– Re-usable
Copyright © 2016 M/Gateway Developments Ltd
Create loginForm.js
• In same directory as app.js
– Or in its own subdirectory under ~/qewd/www
Copyright © 2016 M/Gateway Developments Ltd
loginForm.js
var loginForm = {
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
Copyright © 2016 M/Gateway Developments Ltd
Example: index.html
<html>
<head>
<title>Fragment Demo</title>
<link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" />
</head>
<body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/ewd-client.js"></script>
<script src="app2.js"></script>
<script src="loginForm.js"></script>
<div id="loginFormDiv"></div>
</body>
</html>
Copyright © 2016 M/Gateway Developments Ltd
Example: app2.js
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv'
};
EWD.getFragment(params, loginForm.loader);
});
EWD.start('demo1', $, io);
});
Copyright © 2016 M/Gateway Developments Ltd
Example: app2.js
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv'
};
EWD.getFragment(params, loginForm.loader);
});
EWD.start('demo1', $, io);
});
var loginForm = {
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
loginForm.js
Copyright © 2016 M/Gateway Developments Ltd
What about the back-end code?
Copyright © 2016 M/Gateway Developments Ltd
What about the back-end code?
var loginForm = {
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
A login message will be sent to
the back-end. Currently QEWD
will expect to find its handler in
the main application module (demo1.js)
So let's modularise it instead…
Copyright © 2016 M/Gateway Developments Ltd
Create a Login Service
function checkLogin(username, password) {
// hard-coded version for now
if (username !== 'rob') return {error: 'Invalid username'};
if (password !== 'secret') return {error: 'Invalid password'};
return {ok: true};
}
module.exports = {
handlers: {
login: function(messageObj, session, send, finished) {
if (session.authenticated) {
finished({error: 'You are already logged in!'});
return;
}
var username = messageObj.params.username;
if (username === '') {
finished({error: 'You must enter a username'});
return;
}
var password = messageObj.params.password;
if (password === '') {
finished({error: 'You must enter a password'});
return;
}
var status = checkLogin(username, password);
if (status.ok) {
session.authenticated = true;
session.timeout = 3600;
session.updateExpiry();
finished({ok: true});
}
else {
finished({error: status.error});
}
}
}
};
Save into
node_modules/Login.js
Contains the logic for
handling the login
authentication at the
back-end
Copyright © 2016 M/Gateway Developments Ltd
Edit loginForm.js
var loginForm = {
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
service: 'Login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
Use the Login service
for the login message
Copyright © 2016 M/Gateway Developments Ltd
Edit demo1.js
module.exports = {
servicesAllowed: {
Login: true
}
};
It now just needs to be
a minimal module that
allows the demo1 app
to use the Login service
Copyright © 2016 M/Gateway Developments Ltd
Try it out!
• Restart QEWD to ensure that the new
back-end modules are loaded
– Alternatively use qewd-monitor to stop the
worker processes
• The demo application should run
identically, but this time it's using the Login
service
Copyright © 2016 M/Gateway Developments Ltd
Further Front-end Modularisation?
• loginForm.html fragment is currently in
www/demo1 directory
• Could we make the fragment part of the
Login service?
Copyright © 2016 M/Gateway Developments Ltd
Create Login Module
• Create a new folder:
– C:qewdnode_modulesLogin or
– ~/qewd/node_modules/Login
Copyright © 2016 M/Gateway Developments Ltd
Create Login Module
• ~/qewd/node_modules/Login
package.json:
{
"name": "Login",
"version": "1.0.0",
"description": "Modular login system",
"main": "index.js"
}
index.js
'use strict';
module.exports = require('./lib/Login');
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Create Login Module
• Move ~/qewd/node_modules/Login.js to:
– ~/qewd/node_modules/Login/lib/Login.js
- lib
- Login.js
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Create Login Module
• Move:
– ~/qewd/www/demo1/loginForm.html
• To:
– ~/qewd/node_modules/Login/fragments/loginForm.html
- fragments
- loginForm.html
- lib
- Login.js
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Example: app2.js
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, loginForm.loader);
});
EWD.start('demo1', $, io);
});
Load loginForm.html from
the Login Service module
ewd-xpress will look in
the Login/fragments
folder for the fragment
Copyright © 2016 M/Gateway Developments Ltd
Example: app2.js
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, loginForm.loader);
});
EWD.start('demo1', $, io);
});
Load loginForm.html from
the Login Service module
ewd-xpress will look in
the Login/fragments
folder for the fragment
- fragments
- loginForm.html
- lib
- Login.js
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Client-side: loginForm.js
var loginForm = {
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
service: 'Login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
Use the Login service
for the login message
Copyright © 2016 M/Gateway Developments Ltd
Include loginForm.js in Service?
• Since loginForm.js defines the handlers for
the loginForm.html fragment, it would be
sensible for it to be part of the Login
Service too
• However, web browsers can't load
JavaScript from Node.js modules
– Not directly
• Two approaches…
Copyright © 2016 M/Gateway Developments Ltd
Add to Login Module
• Copy:
– ~/qewd/www/demo1/loginForm.js
• To:
– ~/qewd/node_modules/Login/client/loginForm.js
- client
- loginForm.js
- fragments
- loginForm.html
- lib
- Login.js
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Option 1
• Can be published and installed/used by others, but must:
• Copy:
– ~/qewd/node_modules/Login/client/loginForm.js
• To:
– ~/qewd/www/{{application name}}/loginForm.js
- client
- loginForm.js
- fragments
- loginForm.html
- lib
- Login.js
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Option 2: the modern approach
• Can be published and installed/used by others
• Make use of a bundler (eg Browserify or WebPack)
– Define the front-end JavaScript as if you were using Node.js
• So you can use: var loginForm = require('Login/client/loginForm');
– Use the bundler to convert to a single JavaScript file that can be loaded by the
browser using a <script> tag
– However, loginForm.js would have to be rewritten as a Module
• See later…
- client
- loginForm.js
- fragments
- loginForm.html
- lib
- Login.js
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Modular Front-end Development
• Install Browserify
– cd ~/qewd (or cd C:qewd )
– npm install babelify
– npm install –g browserify
Copyright © 2016 M/Gateway Developments Ltd
NPM versions of client-side JS
• All the libraries loaded using <script> tags
need to be accessed as Node.js modules
instead
– Most are available in this format these days
cd ~/qewd
npm install toastr jquery socket.io-client
// ewd-client is already an installed as a module
Copyright © 2016 M/Gateway Developments Ltd
Edit loginForm.js
• The loginForm.js client-side script file in our Login
module needs to be changed:
– Needs to be a Node.js module
• Very simple change…
- client
- loginForm.js
- fragments
- loginForm.html
- lib
- Login.js
- index.js
- package.json
Copyright © 2016 M/Gateway Developments Ltd
Edit loginForm.js
var toastr = require('toastr');
var EWD;
module.exports = {
init: function(ewd) {
EWD = ewd;
},
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error('You must enter a password');
return;
}
var message = {
type: 'login',
service: 'Login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
Copyright © 2016 M/Gateway Developments Ltd
Edit loginForm.js
var toastr = require('toastr');
var EWD;
module.exports = {
init: function(ewd) {
EWD = ewd;
},
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error ('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error ('You must enter a password');
return;
}
var message = {
type: 'login',
service: 'Login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
Node.js modules
must be entirely
self-contained.
toastr and EWD
have to come from
somewhere
Copyright © 2016 M/Gateway Developments Ltd
Edit loginForm.js
var toastr = require('toastr');
var EWD;
module.exports = {
init: function(ewd) {
EWD = ewd;
},
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error ('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error ('You must enter a password');
return;
}
var message = {
type: 'login',
service: 'Login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
In the case of
toastr, we can
require() it
Copyright © 2016 M/Gateway Developments Ltd
Edit loginForm.js
var toastr = require('toastr');
var EWD;
module.exports = {
init: function(ewd) {
EWD = ewd;
},
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error ('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error ('You must enter a password');
return;
}
var message = {
type: 'login',
service: 'Login',
params: {
username: username,
password: password
}
};
EWD.send (message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
But the EWD object is
more tricky
loginForm.loader must
use the post-register
version of EWD so it
can access the send()
API and the session token.
So we can't just use
require('ewd-client') as
we'd have the pre-registered
version!
Copyright © 2016 M/Gateway Developments Ltd
Edit loginForm.js
var toastr = require('toastr');
var EWD;
module.exports = {
init: function(ewd) {
EWD = ewd;
},
loader: function(name) {
$('#loginBtn').on('click', function(e) {
var username = $('#username').val();
if (username === '') {
toastr.error ('You must enter a username');
return;
}
var password = $('#password').val()
if (password === '') {
toastr.error ('You must enter a password');
return;
}
var message = {
type: 'login',
service: 'Login',
params: {
username: username,
password: password
}
};
EWD.send(message, function(responseObj) {
if (!responseObj.message.error) {
$('#loginForm').hide();
}
});
});
}
};
One approach is to use
an init() function that will
allow us to pass the
post-register version of EWD
into the module
We'll see how it's used
in app.js
Copyright © 2016 M/Gateway Developments Ltd
Edit app.jsvar io = require('socket.io-client')
var jQuery = require('jquery');
window.$ = window.jQuery = jQuery;
var toastr = require('toastr');
var EWD = require('ewd-client').EWD;
var Login = require('Login/client/loginForm');
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
Login.init(EWD);
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, Login.loader);
});
EWD.start('demo1', $, io);
});
app.js now needs to
load everything it needs
by using require()
Copyright © 2016 M/Gateway Developments Ltd
Edit app.jsvar io = require('socket.io-client')
var jQuery = require('jquery');
window.$ = window.jQuery = jQuery;
var toastr = require('toastr');
var EWD = require('ewd-client').EWD;
var Login = require('Login/client/loginForm');
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
Login.init(EWD);
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, Login.loader);
});
EWD.start('demo1', $, io);
});
The jQuery $ object
needs to be instantiated
as a property of window
This gives it Global scope
so none of the other
modules that refer to $
need to require() it again
eg $ is referred to in
loginForm.js
Copyright © 2016 M/Gateway Developments Ltd
Edit app.jsvar io = require('socket.io-client')
var jQuery = require('jquery');
window.$ = window.jQuery = jQuery;
var toastr = require('toastr');
var EWD = require('ewd-client').EWD;
var Login = require('Login/client/loginForm');
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
Login.init(EWD);
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, Login.loader);
});
EWD.start('demo1', $, io);
});
The EWD object is loaded
from the ewd-client
module
Copyright © 2016 M/Gateway Developments Ltd
Edit app.jsvar io = require('socket.io-client')
var jQuery = require('jquery');
window.$ = window.jQuery = jQuery;
var toastr = require('toastr');
var EWD = require('ewd-client').EWD;
var Login = require('Login/client/loginForm');
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
Login.init(EWD);
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, Login.loader);
});
EWD.start('demo1', $, io);
});
And now we load the
loginForm code from
the Login service module
Copyright © 2016 M/Gateway Developments Ltd
Edit app.jsvar io = require('socket.io-client')
var jQuery = require('jquery');
window.$ = window.jQuery = jQuery;
var toastr = require('toastr');
var EWD = require('ewd-client').EWD;
var Login = require('Login/client/loginForm');
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
Login.init(EWD);
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, Login.loader);
});
EWD.start('demo1', $, io);
});
And now we load the
loginForm code from
the Login service module
And pass the post-register
EWD object into it
Copyright © 2016 M/Gateway Developments Ltd
Edit app.jsvar io = require('socket.io-client')
var jQuery = require('jquery');
window.$ = window.jQuery = jQuery;
var toastr = require('toastr');
var EWD = require('ewd-client').EWD;
var Login = require('Login/client/loginForm');
$(document).ready(function() {
EWD.log = true;
EWD.on('ewd-registered', function() {
EWD.on('error', function(responseObj) {
toastr.error(responseObj.message.error);
});
EWD.on('socketDisconnected', function() {
toastr.info('You have been logged out');
setTimeout(function() {
location.reload();
}, 1000);
});
Login.init(EWD);
var params = {
name: 'loginForm.html',
targetId: 'loginFormDiv',
service: 'Login'
};
EWD.getFragment(params, Login.loader );
});
EWD.start('demo1', $, io);
});
And now we load the
loginForm code from
the Login service module
And pass the post-register
EWD object into it
And then its loader function
can be used as the
getFragment callback,
and it now has
access to EWD to send
messages and handle
responses
Copyright © 2016 M/Gateway Developments Ltd
Edit index.html
• Leave the <link> tags that load the CSS
• Remove all the <script> tags but one:
– bundle.js
– Created from the modularised JavaScript by
Browserify
Copyright © 2016 M/Gateway Developments Ltd
Edit index.html
<html>
<head>
<title>Demo modularised ewd-xpress application</title>
<link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" />
</head>
<body>
<script src="bundle.js"></script>
<div id="loginFormDiv"></div>
</body>
</html>
Copyright © 2016 M/Gateway Developments Ltd
Now create the bundle file
• cd ~/qewd/www/demo1
• browserify -t [ babelify ] app.js -o bundle.js
• If no errors reported, you should now have
a bundle.js file in the demo1 directory
Copyright © 2016 M/Gateway Developments Ltd
Try it out
• The application should run as before
• Check in the JavaScript console that it's using
the bundle.js file
• You now have a fully modularised application
– Everything related to logging in is defined in its own
Login service module
• Both its front-end and back-end logic
• Could be re-used in any of your applications
Copyright © 2016 M/Gateway Developments Ltd
Bundling: things to note
• If you make any changes to front-end
JavaScript, in the application's own code
or any of the modules it uses, you MUST
re-run Browserify
• WebPack is an alternative to Browserify
– You may prefer it
• You may want to also minimise the
bundle.js file
– eg minify
Copyright © 2016 M/Gateway Developments Ltd
Automating
• Outside the scope of this training, but the
trend is to now automate the build chain
– When a file is changed, it triggers a re-build
• Bundles the file
• Minifies it
• Runs unit tests
• Updates the Git repository
– eg using tools such as Gulp

More Related Content

EWD 3 Training Course Part 30: Modularising QEWD Applications

  • 1. Copyright © 2016 M/Gateway Developments Ltd EWD 3 Training Course Part 30 Modularising QEWD Applications Rob Tweed Director, M/Gateway Developments Ltd Twitter: @rtweed
  • 2. Copyright © 2016 M/Gateway Developments Ltd Back-end Modules • So far we've just saved our module files directly as .js files into the node_modules folder – C:qewdnode_modules – ~/qewd/node_module • We used the convention: – Application name = module file name • So the back-end module for our demo1 application was named demo1.js
  • 3. Copyright © 2016 M/Gateway Developments Ltd Fully-fledged Node.js Modules • However, back-end QEWD modules can be fully-fledged Node.js modules – ie a complete module package directory, including: • index.js • package.json • README.md • etc – Can be published on NPM – Can be installed from NPM
  • 4. Copyright © 2016 M/Gateway Developments Ltd Example • Our demo1 application back-end module • Instead of it just being in C:ewd3node_modulesdemo1.js • Have a directory C:ewd3node_modulesdemo1 – package.json – index.js – /lib folder – Optional READme.md documentation file
  • 5. Copyright © 2016 M/Gateway Developments Ltd package.json • As a minimum, specify these 3 properties: { "name": "demo1", "version": "1.0.0", "main": "index.js", }
  • 6. Copyright © 2016 M/Gateway Developments Ltd package.json • More typically and usefully: { "name": "demo1", "version": "1.0.0", "description": "Back end message handlers for demo app", "main": "index.js", "author": "Rob Tweed", "license": "Apache-2.0", "repository": { "type": "git", "url": "git+https://github.com/robtweed/demo1.git " } }
  • 7. Copyright © 2016 M/Gateway Developments Ltd package.json { "name": "demo1", "version": "1.0.0", "description": "Back end message handlers for demo app", "main": "index.js", "author": "Rob Tweed", "license": "Apache-2.0", "repository": { "type": "git", "url": "git+https://github.com/robtweed/demo1.git " } } Tells Node.js where to find the main code/ script file
  • 8. Copyright © 2016 M/Gateway Developments Ltd index.js • The main script file • Can contain the code • Usually just a pointer to the code – Which is usually in a separate folder • eg /lib
  • 9. Copyright © 2016 M/Gateway Developments Ltd index.js 'use strict'; module.exports = require('./lib/demo1'); So the main code is actually in /lib/demo1.js
  • 10. Copyright © 2016 M/Gateway Developments Ltd /lib/demo1.js module.exports = { init: function() { servicesAllowed: { testService: true }, handlers: { login: function(messageObj, session, send, finished) { // etc }, // etc } }; So this is a standard QEWD back-end module
  • 11. Copyright © 2016 M/Gateway Developments Ltd Now a full Node.js/NPM module • This demo1 module could now be published to NPM if appropriate • Others could then use it by installing it – npm install demo1
  • 12. Copyright © 2016 M/Gateway Developments Ltd Module Name Mapping • QEWD default back-end module name mapping: – Registered application name (eg demo1) must be used as the name of either: • The module file, eg: – ~/qewd/node_modules/demo1.js • The module directory, eg: – ~/qewd/node_modules/demo1 » Within which is the package.json, etc
  • 13. Copyright © 2016 M/Gateway Developments Ltd Module Name Mapping • However, by using the moduleMap QEWD startup configuration array property, you can specify any Node.js module to be the back-end for a particular QEWD application – They don't have to have the same name
  • 14. Copyright © 2016 M/Gateway Developments Ltd Module Name Mapping • Remember: the QEWD application name is defined in its front-end app.js file when you invoke the ewd-client start() function, eg: – EWD.start('test-app', $, io); – This application will be registered on QEWD as test-app
  • 15. Copyright © 2016 M/Gateway Developments Ltd Module Name Mapping • But your QEWD startup file can define back-end module mapping, eg: – ~/qewd/qewd.js: var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' }, moduleMap: { test-app: '/path/to/myModule' } }; var qewd = require('qewd').master; qewd.start(config);
  • 16. Copyright © 2016 M/Gateway Developments Ltd Module Name Mapping • But your QEWD startup file can define back-end module mapping, eg: – ~/qewd/qewd.js: var config = { managementPassword: 'keepThisSecret!', serverName: 'My QEWD Server', port: 8080, poolSize: 2, database: { type: 'gtm' }, moduleMap: { test-app: '/path/to/myModule' } }; var qewd = require('qewd').master; qewd.start(config); Your QEWD application named test-app will use /path/to/myModule as its back-end module
  • 17. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD Back-end Logic • It's all too easy for the back-end module of a QEWD application to become a huge file – Ever increasing number of message handler functions
  • 18. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD Back-end Logic • It's all too easy for the back-end module of a QEWD application to become a huge file – Ever increasing number of message handler functions • Each handler function can, itself, be separated out into a module
  • 19. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD Back-end Logic • So instead of: module.exports = { handlers: { test: function(messageObj, session, send, finished) { var incomingText = messageObj.params.text; finished({text: 'You sent: ' + incomingText}); } } };
  • 20. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD Back-end Logic • You could define each handler function in its own sub-module: var test = require('./handlers/test'); module.exports = { handlers: { test: test } }; function test(messageObj, session, send, finished) { var incomingText = messageObj.params.text; finished({text: 'You sent: ' + incomingText}); } module.exports = test;
  • 21. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD by using Services • Further opportunities for modularisation are provided by QEWD Services – A Service Module is exactly like an application's own back-end module • Can contain one or more handler functions – But Service Modules can be shared between applications – Can be used as the basis for a QEWD-based Micro-Service architecture
  • 22. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD by using Services • QEWD Service Modules are good candidates for packaging as fully-fledged Node.js modules that can be published on NPM – That allows them to be re-used by other users for their applications
  • 23. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD by using Services • QEWD Service Modules can also be name-mapped, separating: – The name you use to refer to the service – The actual physical Node.js module name/path used for the service • For more details about EWD Services, see Part 16 of this course
  • 24. Copyright © 2016 M/Gateway Developments Ltd Modularising QEWD Applications • So the back-end logic of QEWD applications can be modularised • How about the front-end?
  • 25. Copyright © 2016 M/Gateway Developments Ltd Modularising Front-end Code • Two options available – Using "Fragments" – Using a bundler such as Browserify or WebPack
  • 26. Copyright © 2016 M/Gateway Developments Ltd Fragments • Becoming a bit of an old-fashioned approach – Not appropriate for modern frameworks such as React.js or Angular • But for more conventional HTML development, it can be an effective means of modularising an application – Can be part of a Service
  • 27. Copyright © 2016 M/Gateway Developments Ltd Fragments • The concept: – You load an initial HTML file (eg index.html) which typically contains very little markup, but loads all the JavaScript and CSS required by the application – Subsequently, fragments of HTML are loaded dynamically into <div> or other tags, as a result of events occurring in the browser • Via Ajax or WebSockets
  • 28. Copyright © 2016 M/Gateway Developments Ltd Fragments • Fragments can be included as part of a QEWD Service Module – Back-end message handler logic – Associated fragment(s) of markup – Publishable on NPM • An application could therefore use fragments and back-end logic from multiple services – ie: re-usable logic & markup / UI components
  • 29. Copyright © 2016 M/Gateway Developments Ltd EWD.getFragment() • To fetch a fragment, in your browser-side logic use: – EWD.getFragment(argObj, callback);
  • 30. Copyright © 2016 M/Gateway Developments Ltd EWD.getFragment() • To fetch a fragment, in your browser-side logic use: – EWD.getFragment(argObj, callback); • argObj: object containing: – name, eg: loginForm.html » By default, will fetch from same directory as index.html file – targetId: » id of the tag into which the markup will be inserted » eg <div id="myTargetDiv"></div>
  • 31. Copyright © 2016 M/Gateway Developments Ltd EWD.getFragment() • To fetch a fragment, in your browser-side logic use: – EWD.getFragment(argObj, callback); • callback: invoked when fragment is loaded into browser – Single optional argument: name » Filename of the fragment that was loaded – Use callback to load handlers needed by fragment's markup, eg button click handlers
  • 32. Copyright © 2016 M/Gateway Developments Ltd Example: index.html <html> <head> <title>Fragment Demo</title> <link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" /> </head> <body> <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script src="/ewd-client.js"></script> <script src="app2.js"></script> <div id="loginFormDiv"></div> </body> </html>
  • 33. Copyright © 2016 M/Gateway Developments Ltd Example: app2.js $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); var params = { name: 'loginForm.html', targetId: 'loginFormDiv' }; EWD.getFragment(params, function(name) { toastr.info('fragment loaded: ' + JSON.stringify(name)); }); }); EWD.start('demo1', $, io); });
  • 34. Copyright © 2016 M/Gateway Developments Ltd Example <html> <head> <title>Demo ewd-xpress application</title> <link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" /> </head> <body> <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script src="/ewd-client.js"></script> <script src="app2.js"></script> <div id="loginFormDiv"></div> </body> </html> var params = { name: 'loginForm.html', targetId: 'loginFormDiv' }; EWD.getFragment(params, function(name) { toastr.info('fragment loaded: ' + JSON.stringify(name)); });
  • 35. Copyright © 2016 M/Gateway Developments Ltd Example: loginForm.html <table id="loginForm"> <tr> <td>Username:</td> <td><input type="text" id="username" /></td> </tr> <tr> <td>Password:</td> <td><input type="password" id="password" /></td> </tr> <tr> <td colspan="2"> <button id="loginBtn">Login</button> </td> </tr> </table> Just the markup for the login form
  • 36. Copyright © 2016 M/Gateway Developments Ltd Try it out!
  • 37. Copyright © 2016 M/Gateway Developments Ltd Form won't do anything eg nothing happens if you click this button
  • 38. Copyright © 2016 M/Gateway Developments Ltd Edit: app2.js var params = { name: 'loginForm.html', targetId: 'loginFormDiv' }; EWD.getFragment(params, function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val(); if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide();} }); }); });
  • 39. Copyright © 2016 M/Gateway Developments Ltd Edit: app2.js var params = { name: 'loginForm.html', targetId: 'loginFormDiv' }; EWD.getFragment(params, function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val(); if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide();} }); }); }); Now it will add a click handler to the login button
  • 40. Copyright © 2016 M/Gateway Developments Ltd Edit: app2.js var params = { name: 'loginForm.html', targetId: 'loginFormDiv' }; EWD.getFragment(params, function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val(); if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide();} }); }); }); Now it will add a click handler to the login button When clicked, the username and password will be sent as a message, with type 'login', to the QEWD back-end
  • 41. Copyright © 2016 M/Gateway Developments Ltd Now it works
  • 42. Copyright © 2016 M/Gateway Developments Ltd Now it works • But we've had to define the fragment loader logic within our application's main app.js logic • It would be more modular if the logic associated with the loginForm fragment could be kept in a separate place – Separately maintainable – Re-usable
  • 43. Copyright © 2016 M/Gateway Developments Ltd Create loginForm.js • In same directory as app.js – Or in its own subdirectory under ~/qewd/www
  • 44. Copyright © 2016 M/Gateway Developments Ltd loginForm.js var loginForm = { loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } };
  • 45. Copyright © 2016 M/Gateway Developments Ltd Example: index.html <html> <head> <title>Fragment Demo</title> <link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" /> </head> <body> <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script src="/ewd-client.js"></script> <script src="app2.js"></script> <script src="loginForm.js"></script> <div id="loginFormDiv"></div> </body> </html>
  • 46. Copyright © 2016 M/Gateway Developments Ltd Example: app2.js $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); var params = { name: 'loginForm.html', targetId: 'loginFormDiv' }; EWD.getFragment(params, loginForm.loader); }); EWD.start('demo1', $, io); });
  • 47. Copyright © 2016 M/Gateway Developments Ltd Example: app2.js $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); var params = { name: 'loginForm.html', targetId: 'loginFormDiv' }; EWD.getFragment(params, loginForm.loader); }); EWD.start('demo1', $, io); }); var loginForm = { loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; loginForm.js
  • 48. Copyright © 2016 M/Gateway Developments Ltd What about the back-end code?
  • 49. Copyright © 2016 M/Gateway Developments Ltd What about the back-end code? var loginForm = { loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; A login message will be sent to the back-end. Currently QEWD will expect to find its handler in the main application module (demo1.js) So let's modularise it instead…
  • 50. Copyright © 2016 M/Gateway Developments Ltd Create a Login Service function checkLogin(username, password) { // hard-coded version for now if (username !== 'rob') return {error: 'Invalid username'}; if (password !== 'secret') return {error: 'Invalid password'}; return {ok: true}; } module.exports = { handlers: { login: function(messageObj, session, send, finished) { if (session.authenticated) { finished({error: 'You are already logged in!'}); return; } var username = messageObj.params.username; if (username === '') { finished({error: 'You must enter a username'}); return; } var password = messageObj.params.password; if (password === '') { finished({error: 'You must enter a password'}); return; } var status = checkLogin(username, password); if (status.ok) { session.authenticated = true; session.timeout = 3600; session.updateExpiry(); finished({ok: true}); } else { finished({error: status.error}); } } } }; Save into node_modules/Login.js Contains the logic for handling the login authentication at the back-end
  • 51. Copyright © 2016 M/Gateway Developments Ltd Edit loginForm.js var loginForm = { loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', service: 'Login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; Use the Login service for the login message
  • 52. Copyright © 2016 M/Gateway Developments Ltd Edit demo1.js module.exports = { servicesAllowed: { Login: true } }; It now just needs to be a minimal module that allows the demo1 app to use the Login service
  • 53. Copyright © 2016 M/Gateway Developments Ltd Try it out! • Restart QEWD to ensure that the new back-end modules are loaded – Alternatively use qewd-monitor to stop the worker processes • The demo application should run identically, but this time it's using the Login service
  • 54. Copyright © 2016 M/Gateway Developments Ltd Further Front-end Modularisation? • loginForm.html fragment is currently in www/demo1 directory • Could we make the fragment part of the Login service?
  • 55. Copyright © 2016 M/Gateway Developments Ltd Create Login Module • Create a new folder: – C:qewdnode_modulesLogin or – ~/qewd/node_modules/Login
  • 56. Copyright © 2016 M/Gateway Developments Ltd Create Login Module • ~/qewd/node_modules/Login package.json: { "name": "Login", "version": "1.0.0", "description": "Modular login system", "main": "index.js" } index.js 'use strict'; module.exports = require('./lib/Login'); - index.js - package.json
  • 57. Copyright © 2016 M/Gateway Developments Ltd Create Login Module • Move ~/qewd/node_modules/Login.js to: – ~/qewd/node_modules/Login/lib/Login.js - lib - Login.js - index.js - package.json
  • 58. Copyright © 2016 M/Gateway Developments Ltd Create Login Module • Move: – ~/qewd/www/demo1/loginForm.html • To: – ~/qewd/node_modules/Login/fragments/loginForm.html - fragments - loginForm.html - lib - Login.js - index.js - package.json
  • 59. Copyright © 2016 M/Gateway Developments Ltd Example: app2.js $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, loginForm.loader); }); EWD.start('demo1', $, io); }); Load loginForm.html from the Login Service module ewd-xpress will look in the Login/fragments folder for the fragment
  • 60. Copyright © 2016 M/Gateway Developments Ltd Example: app2.js $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, loginForm.loader); }); EWD.start('demo1', $, io); }); Load loginForm.html from the Login Service module ewd-xpress will look in the Login/fragments folder for the fragment - fragments - loginForm.html - lib - Login.js - index.js - package.json
  • 61. Copyright © 2016 M/Gateway Developments Ltd Client-side: loginForm.js var loginForm = { loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', service: 'Login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; Use the Login service for the login message
  • 62. Copyright © 2016 M/Gateway Developments Ltd Include loginForm.js in Service? • Since loginForm.js defines the handlers for the loginForm.html fragment, it would be sensible for it to be part of the Login Service too • However, web browsers can't load JavaScript from Node.js modules – Not directly • Two approaches…
  • 63. Copyright © 2016 M/Gateway Developments Ltd Add to Login Module • Copy: – ~/qewd/www/demo1/loginForm.js • To: – ~/qewd/node_modules/Login/client/loginForm.js - client - loginForm.js - fragments - loginForm.html - lib - Login.js - index.js - package.json
  • 64. Copyright © 2016 M/Gateway Developments Ltd Option 1 • Can be published and installed/used by others, but must: • Copy: – ~/qewd/node_modules/Login/client/loginForm.js • To: – ~/qewd/www/{{application name}}/loginForm.js - client - loginForm.js - fragments - loginForm.html - lib - Login.js - index.js - package.json
  • 65. Copyright © 2016 M/Gateway Developments Ltd Option 2: the modern approach • Can be published and installed/used by others • Make use of a bundler (eg Browserify or WebPack) – Define the front-end JavaScript as if you were using Node.js • So you can use: var loginForm = require('Login/client/loginForm'); – Use the bundler to convert to a single JavaScript file that can be loaded by the browser using a <script> tag – However, loginForm.js would have to be rewritten as a Module • See later… - client - loginForm.js - fragments - loginForm.html - lib - Login.js - index.js - package.json
  • 66. Copyright © 2016 M/Gateway Developments Ltd Modular Front-end Development • Install Browserify – cd ~/qewd (or cd C:qewd ) – npm install babelify – npm install –g browserify
  • 67. Copyright © 2016 M/Gateway Developments Ltd NPM versions of client-side JS • All the libraries loaded using <script> tags need to be accessed as Node.js modules instead – Most are available in this format these days cd ~/qewd npm install toastr jquery socket.io-client // ewd-client is already an installed as a module
  • 68. Copyright © 2016 M/Gateway Developments Ltd Edit loginForm.js • The loginForm.js client-side script file in our Login module needs to be changed: – Needs to be a Node.js module • Very simple change… - client - loginForm.js - fragments - loginForm.html - lib - Login.js - index.js - package.json
  • 69. Copyright © 2016 M/Gateway Developments Ltd Edit loginForm.js var toastr = require('toastr'); var EWD; module.exports = { init: function(ewd) { EWD = ewd; }, loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error('You must enter a password'); return; } var message = { type: 'login', service: 'Login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } };
  • 70. Copyright © 2016 M/Gateway Developments Ltd Edit loginForm.js var toastr = require('toastr'); var EWD; module.exports = { init: function(ewd) { EWD = ewd; }, loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error ('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error ('You must enter a password'); return; } var message = { type: 'login', service: 'Login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; Node.js modules must be entirely self-contained. toastr and EWD have to come from somewhere
  • 71. Copyright © 2016 M/Gateway Developments Ltd Edit loginForm.js var toastr = require('toastr'); var EWD; module.exports = { init: function(ewd) { EWD = ewd; }, loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error ('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error ('You must enter a password'); return; } var message = { type: 'login', service: 'Login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; In the case of toastr, we can require() it
  • 72. Copyright © 2016 M/Gateway Developments Ltd Edit loginForm.js var toastr = require('toastr'); var EWD; module.exports = { init: function(ewd) { EWD = ewd; }, loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error ('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error ('You must enter a password'); return; } var message = { type: 'login', service: 'Login', params: { username: username, password: password } }; EWD.send (message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; But the EWD object is more tricky loginForm.loader must use the post-register version of EWD so it can access the send() API and the session token. So we can't just use require('ewd-client') as we'd have the pre-registered version!
  • 73. Copyright © 2016 M/Gateway Developments Ltd Edit loginForm.js var toastr = require('toastr'); var EWD; module.exports = { init: function(ewd) { EWD = ewd; }, loader: function(name) { $('#loginBtn').on('click', function(e) { var username = $('#username').val(); if (username === '') { toastr.error ('You must enter a username'); return; } var password = $('#password').val() if (password === '') { toastr.error ('You must enter a password'); return; } var message = { type: 'login', service: 'Login', params: { username: username, password: password } }; EWD.send(message, function(responseObj) { if (!responseObj.message.error) { $('#loginForm').hide(); } }); }); } }; One approach is to use an init() function that will allow us to pass the post-register version of EWD into the module We'll see how it's used in app.js
  • 74. Copyright © 2016 M/Gateway Developments Ltd Edit app.jsvar io = require('socket.io-client') var jQuery = require('jquery'); window.$ = window.jQuery = jQuery; var toastr = require('toastr'); var EWD = require('ewd-client').EWD; var Login = require('Login/client/loginForm'); $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); Login.init(EWD); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, Login.loader); }); EWD.start('demo1', $, io); }); app.js now needs to load everything it needs by using require()
  • 75. Copyright © 2016 M/Gateway Developments Ltd Edit app.jsvar io = require('socket.io-client') var jQuery = require('jquery'); window.$ = window.jQuery = jQuery; var toastr = require('toastr'); var EWD = require('ewd-client').EWD; var Login = require('Login/client/loginForm'); $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); Login.init(EWD); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, Login.loader); }); EWD.start('demo1', $, io); }); The jQuery $ object needs to be instantiated as a property of window This gives it Global scope so none of the other modules that refer to $ need to require() it again eg $ is referred to in loginForm.js
  • 76. Copyright © 2016 M/Gateway Developments Ltd Edit app.jsvar io = require('socket.io-client') var jQuery = require('jquery'); window.$ = window.jQuery = jQuery; var toastr = require('toastr'); var EWD = require('ewd-client').EWD; var Login = require('Login/client/loginForm'); $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); Login.init(EWD); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, Login.loader); }); EWD.start('demo1', $, io); }); The EWD object is loaded from the ewd-client module
  • 77. Copyright © 2016 M/Gateway Developments Ltd Edit app.jsvar io = require('socket.io-client') var jQuery = require('jquery'); window.$ = window.jQuery = jQuery; var toastr = require('toastr'); var EWD = require('ewd-client').EWD; var Login = require('Login/client/loginForm'); $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); Login.init(EWD); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, Login.loader); }); EWD.start('demo1', $, io); }); And now we load the loginForm code from the Login service module
  • 78. Copyright © 2016 M/Gateway Developments Ltd Edit app.jsvar io = require('socket.io-client') var jQuery = require('jquery'); window.$ = window.jQuery = jQuery; var toastr = require('toastr'); var EWD = require('ewd-client').EWD; var Login = require('Login/client/loginForm'); $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); Login.init(EWD); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, Login.loader); }); EWD.start('demo1', $, io); }); And now we load the loginForm code from the Login service module And pass the post-register EWD object into it
  • 79. Copyright © 2016 M/Gateway Developments Ltd Edit app.jsvar io = require('socket.io-client') var jQuery = require('jquery'); window.$ = window.jQuery = jQuery; var toastr = require('toastr'); var EWD = require('ewd-client').EWD; var Login = require('Login/client/loginForm'); $(document).ready(function() { EWD.log = true; EWD.on('ewd-registered', function() { EWD.on('error', function(responseObj) { toastr.error(responseObj.message.error); }); EWD.on('socketDisconnected', function() { toastr.info('You have been logged out'); setTimeout(function() { location.reload(); }, 1000); }); Login.init(EWD); var params = { name: 'loginForm.html', targetId: 'loginFormDiv', service: 'Login' }; EWD.getFragment(params, Login.loader ); }); EWD.start('demo1', $, io); }); And now we load the loginForm code from the Login service module And pass the post-register EWD object into it And then its loader function can be used as the getFragment callback, and it now has access to EWD to send messages and handle responses
  • 80. Copyright © 2016 M/Gateway Developments Ltd Edit index.html • Leave the <link> tags that load the CSS • Remove all the <script> tags but one: – bundle.js – Created from the modularised JavaScript by Browserify
  • 81. Copyright © 2016 M/Gateway Developments Ltd Edit index.html <html> <head> <title>Demo modularised ewd-xpress application</title> <link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet" /> </head> <body> <script src="bundle.js"></script> <div id="loginFormDiv"></div> </body> </html>
  • 82. Copyright © 2016 M/Gateway Developments Ltd Now create the bundle file • cd ~/qewd/www/demo1 • browserify -t [ babelify ] app.js -o bundle.js • If no errors reported, you should now have a bundle.js file in the demo1 directory
  • 83. Copyright © 2016 M/Gateway Developments Ltd Try it out • The application should run as before • Check in the JavaScript console that it's using the bundle.js file • You now have a fully modularised application – Everything related to logging in is defined in its own Login service module • Both its front-end and back-end logic • Could be re-used in any of your applications
  • 84. Copyright © 2016 M/Gateway Developments Ltd Bundling: things to note • If you make any changes to front-end JavaScript, in the application's own code or any of the modules it uses, you MUST re-run Browserify • WebPack is an alternative to Browserify – You may prefer it • You may want to also minimise the bundle.js file – eg minify
  • 85. Copyright © 2016 M/Gateway Developments Ltd Automating • Outside the scope of this training, but the trend is to now automate the build chain – When a file is changed, it triggers a re-build • Bundles the file • Minifies it • Runs unit tests • Updates the Git repository – eg using tools such as Gulp