The first SPA feature module we’ll create will be the chat feature module, which we’ll refer to as Chat for the remainder of the chapter. We chose this feature because we already have completed significant work on it in chapter 3, and because the conver- sion helps highlight the defining characteristics of a feature module.
4.2.1 Plan the file structure
We recommend you copy the whole directory structure you created in chapter 3 into a new “chapter_4” directory so we may update them there. Let’s review our file struc- ture as we left it in chapter 3 as shown in listing 4.1:
spa +-- css
| +-- spa.css
| `-- spa.shell.css +-- js
| +-- jq
| | +-- jquery-1.9.1.js
| | `-- jquery.uriAnchor-1.1.3.js
| +-- spa.js
| `-- spa.shell.js +-- layout.html
`-- spa.html
Here are the changes we wish to make:
■ Create a namespaced stylesheet for Chat.
■ Create a namespaced JavaScript module for Chat.
■ Create a stub for the browser Model.
■ Create a utility module that provides common routines for use by all other modules.
■ Modify the browser document to include the new files.
■ Delete the file we used to develop the layout.
When we’re finished, our updated files and directories should look like listing 4.2. All the files we’ll have to create or modify are shown in bold:
spa +-- css
| +-- spa.chat.css
| +-- spa.css
| `-- spa.shell.css Listing 4.1 File structure from chapter 3
Listing 4.2 Revised file structure for Chat
Add the stylesheet for Chat.
+-- js
| +-- jq
| | +-- jquery-1.9.1.js
| | `-- jquery.uriAnchor-1.1.3.js
| +-- spa.chat.js
| +-- spa.js
| +-- spa.model.js
| +-- spa.shell.js
| `-- spa.util.js
`-- spa.html
Now that we’ve identified the files we want to add or modify, let’s fire up our trusty text editor and get the job done. We’re going to consider each file exactly in the order we’ve presented it.
4.2.2 Populate the files
Our first file to consider is the Chat stylesheet, spa/css/spa.chat.css. We’ll create a file and populate it with the contents shown in listing 4.3. Initially, it will be a stub:4
/*
* spa.chat.css
* Chat feature styles
*/
Next let’s create our Chat feature module, spa/js/spa.chat.js, as shown in listing 4.4, using our module template from appendix A. This is just the first pass, and we’ll have it fill the chat slider container with some trivial HTML:
/*
* spa.chat.js
* Chat feature module for SPA
*/
/*jslint browser : true, continue : true, devel : true, indent : 2, maxerr : 50, newcap : true, nomen : true, plusplus : true, regexp : true, sloppy : true, vars : false, white : true
*/
/*global $, spa */
spa.chat = (function () {
Listing 4.3 Our stylesheet (stub)—spa/css/spa.chat.css
4 A stub is purposely incomplete or a placeholder resource. For example, in chapter 5 we create a “stub” data module that fakes communication with the server.
Listing 4.4 Our Chat module, with limited capability—spa/js/spa.chat.js Add the JavaScript for Chat.
Add the JavaScript for the Model.
Modify the Shell to use Chat.
Add our new utility module.
Modify the browser document to include the new files.
Delete the layout development file, spa/
layout.html.
Create namespace of this module, spa.chat.
103 Set up feature module files
//--- BEGIN MODULE SCOPE VARIABLES --- var
configMap = {
main_html : String()
+ '<div style="padding:1em; color:#fff;">' + 'Say hello to chat'
+ '</div>', settable_map : {}
},
stateMap = { $container : null }, jqueryMap = {},
setJqueryMap, configModule, initModule
;
//--- END MODULE SCOPE VARIABLES --- //--- BEGIN UTILITY METHODS --- //--- END UTILITY METHODS --- //--- BEGIN DOM METHODS --- // Begin DOM method /setJqueryMap/
setJqueryMap = function () {
var $container = stateMap.$container;
jqueryMap = { $container : $container };
};
// End DOM method /setJqueryMap/
//--- END DOM METHODS --- //--- BEGIN EVENT HANDLERS --- //--- END EVENT HANDLERS --- //--- BEGIN PUBLIC METHODS --- // Begin public method /configModule/
// Purpose : Adjust configuration of allowed keys // Arguments : A map of settable keys and values // * color_name - color to use
// Settings :
// * configMap.settable_map declares allowed keys // Returns : true
// Throws : none //
configModule = function ( input_map ) { spa.util.setConfigMap({
input_map : input_map,
settable_map : configMap.settable_map, config_map : configMap
});
return true;
};
// End public method /configModule/
// Begin public method /initModule/
// Purpose : Initializes module // Arguments :
// * $container the jquery element used by this feature // Returns : true
// Throws : none
Store HTML template for chat slider in configMap. Feel free to replace our inane stock message with your own.
Create configModule method. Whenever a feature module accepts settings, we always use the same method name and the same spa.util .setConfigMap utility.
Add initModule method.
Almost all our modules have this method. It starts the module execution.
//
initModule = function ( $container ) {
$container.html( configMap.main_html );
stateMap.$container = $container;
setJqueryMap();
return true;
};
// End public method /initModule/
// return public methods return {
configModule : configModule, initModule : initModule };
//--- END PUBLIC METHODS --- }());
Now let’s create our Model as shown in listing 4.5. This is also a stub. Like all our mod- ules, the file name (spa.model.js) indicates the namespace it provides (spa.model):
/*
* spa.model.js
* Model module
*/
/*jslint browser : true, continue : true, devel : true, indent : 2, maxerr : 50, newcap : true, nomen : true, plusplus : true, regexp : true, sloppy : true, vars : false, white : true
*/
/*global $, spa */
spa.model = (function (){ return {}; }());
Let’s create a general utility module so we may share common routines across all mod- ules as shown in listing 4.6. The makeError method can be used to easily create error objects. The setConfigMap method provides an easy and consistent way to change set- tings for modules. Because these are public methods, we detail their use for the bene- fit of other developers:
/*
* spa.util.js
* General JavaScript utilities
*
* Michael S. Mikowski - mmikowski at gmail dot com
* These are routines I have created, compiled, and updated
* since 1998, with inspiration from around the web.
*
* MIT License
Listing 4.5 Our model (stub)—spa/js/spa.model.js
Listing 4.6 Common utilities—spa/js/spa.util.js
Fill chat slider container with our HTML template.
Export module methods, configModule and initModule. These are standard methods for nearly all feature modules.
105 Set up feature module files
*
*/
/*jslint browser : true, continue : true, devel : true, indent : 2, maxerr : 50, newcap : true, nomen : true, plusplus : true, regexp : true, sloppy : true, vars : false, white : true
*/
/*global $, spa */
spa.util = (function () { var makeError, setConfigMap;
// Begin Public constructor /makeError/
// Purpose: a convenience wrapper to create an error object // Arguments:
// * name_text - the error name // * msg_text - long error message
// * data - optional data attached to error object // Returns : newly constructed error object
// Throws : none //
makeError = function ( name_text, msg_text, data ) { var error = new Error();
error.name = name_text;
error.message = msg_text;
if ( data ){ error.data = data; } return error;
};
// End Public constructor /makeError/
// Begin Public method /setConfigMap/
// Purpose: Common code to set configs in feature modules // Arguments:
// * input_map - map of key-values to set in config // * settable_map - map of allowable keys to set // * config_map - map to apply settings to // Returns: true
// Throws : Exception if input key not allowed //
setConfigMap = function ( arg_map ){
var
input_map = arg_map.input_map, settable_map = arg_map.settable_map, config_map = arg_map.config_map, key_name, error;
for ( key_name in input_map ){
if ( input_map.hasOwnProperty( key_name ) ){
if ( settable_map.hasOwnProperty( key_name ) ){
config_map[key_name] = input_map[key_name];
} else {
error = makeError( 'Bad Input',
'Setting config key |' + key_name + '| is not supported'
);
throw error;
} } } };
// End Public method /setConfigMap/
return {
makeError : makeError, setConfigMap : setConfigMap };
}());
Finally, we can tie all of these changes together by modifying our browser document to load the new JavaScript and CSS files. First we’ll load our stylesheets and then our JavaScript. JavaScript library inclusion order is important: third-party libraries should be loaded first as they’re often a prerequisite, and this practice also helps overcome occasional bone-headed third-party namespace snafus (see the sidebar “Why our libraries are loaded last”). Our libraries come next, and must be ordered by namespace hierarchy—for example, modules that supply the namespaces of spa, spa.model, and spa.model.user must be loaded in that order. Any ordering beyond that is convention and isn’t a requirement. We like this convention: root -> core utili- ties -> Model -> browser utilities -> Shell -> feature modules.
Let’s update our browser document as shown in listing 4.7. Changes from chapter 3 are shown in bold:
<!doctype html>
<!-- spa.html
spa browser document -->
<html>
<head>
<!-- ie9+ rendering support for latest standards -->
Listing 4.7 Changes to the browser document—spa/spa.html Why our libraries are loaded last
We like our libraries to have final claim on namespaces, and so we load them last. If some rogue third-party library claims the spa.model namespace, our libraries will
“take it back” when they load. If this happens, our SPA has a good chance to continue functioning, although the third-party feature probably wouldn’t work. If the library order were reversed, our SPA would almost certainly be completely hosed. We’d rather fix a problem with, say, a third-party comments feature than explain to the CEO why our website completely stopped working at midnight.
Add headers to allow IE9+ to work.
107 Set up feature module files
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>SPA Chapter 4</title>
<!-- third-party stylesheets -->
<!-- our stylesheets -->
<link rel="stylesheet" href="css/spa.css" type="text/css"/>
<link rel="stylesheet" href="css/spa.chat.css" type="text/css"/>
<link rel="stylesheet" href="css/spa.shell.css" type="text/css"/>
<!-- third-party javascript -->
<script src="js/jq/jquery-1.9.1.js"></script>
<script src="js/jq/jquery.uriAnchor-1.1.3.js"></script>
<!-- our javascript -->
<script src="js/spa.js" ></script>
<script src="js/spa.util.js" ></script>
<script src="js/spa.model.js"></script>
<script src="js/spa.shell.js"></script>
<script src="js/spa.chat.js" ></script>
<script>
$(function () { spa.initModule( $('#spa') ); });
</script>
</head>
<body>
<div id="spa"></div>
</body>
</html>
Now let’s have the Shell configure and initialize Chat as shown in listing 4.8. All changes are shown in bold:
...
// configure uriAnchor to use our schema
$.uriAnchor.configModule({
schema_map : configMap.anchor_schema_map });
// configure and initialize feature modules spa.chat.configModule( {} );
spa.chat.initModule( jqueryMap.$chat );
// Handle URI anchor change events ...
We’re now finished with our first pass. Although this is a fair amount of work, many of these steps won’t be needed for future feature modules. Now let’s take a look at what we’ve created.
4.2.3 What we’ve wrought
When we load our browser document (spa/spa.html), the chat slider should look like figure 4.8.
Listing 4.8 Shell revision—spa/js/spa.shell.js
Change title to reflect new chapter. Sorry Toto, we’re not in chapter 3 anymore.
Add a third-party stylesheet section.
Include our stylesheets.
Mirror the JavaScript inclusion order to ease maintenance.
Load feature modules after the Shell.
Include third-party JavaScript first. See the sidebar on why this is a good practice.
Include our libraries in the order of namespace. At minimum, the spa namespace must be loaded first.
Include our utility library, which shares routines with all modules.
Include the browser Model, which is currently a stub.
The Sayhellotochat text shows that Chat was configured and initiated properly and that it has provided the chat slider content. But this presentation is far from impres- sive. In the next section, we’ll significantly improve the chat interface.