What do you want to learn?
Skip to main content
by Kevin Murray
Resume CourseBookmarkAdd to Channel
Table of contents
Creating a Footer Module
Introduction to RequireJS
Refactoring Language Library
Refactoring Toolbar Library
Alternate Module Patterns
Unit Testing Modules
Using Legacy Library as Module
Using 'baseUrl' and 'paths'
Placement of Configuration Properties
Placing the configuration logic in the top of the start file like we've done here works in most cases. If you have a project that is extremely complicated, there might actually be more than one entry point. Complicated and dynamic AJAX pages can actually have different entry points based on permissions, URL parameters, or some other business need. It's possible that a different entry point will execute before RequireJS gets to the startup code with the configuration settings. If code in an alternate entry point expects RequireJS to be configured, you will encounter errors. This is an unusual circumstance, but it can happen. As an alternate to placing the configuration data in our startup code file, we can place it in our main HTML page. This page demonstrates how to do that. We're using the same configuration object as before, only this time we don't call require.config. That wouldn't work because RequireJS hasn't been loaded yet. That happens right here. Instead of calling a config function, we just define a variable named require. Once the actual RequireJS library is loaded, the previously defined variable called require will be recognized as a configuration object and made use of during initialization. If we place this code block in our master HTML file, we can count on RequireJS being configured properly regardless of which process actually runs first in the application. Remember though, we only have to do this if our project has multiple entry points. I do have one word of warning, though. Be sure to use the pattern of var require = configuration object, instead of using window.require = configuration object. According to the RequireJS documentation, using window.require can cause problems when using Internet Explorer. Hmmm. Let's take a quick look at the HTML file with embedded configuration code. As we can see, it behaves in exactly the same manner. Placing configuration data in the HTML file is helpful if you have a very complex and dynamic website. Otherwise you should keep it in your startup file. For the rest of our samples we'll be using a configuration block in the startup code. Our processes don't have multiple entry points so this pattern will work just fine for us. Look at the key value pairs for each of our modules. The key holds the module reference name. The value holds the actual location of the module. That means we can create an alias for the module by changing the key. We can use a moniker of footer instead of the actual file name of _KSM_FooterAMD as the key. Then any place we want to call the footer we could just use the generic moniker of footer instead of the formal file name. Why is this even worth mentioning? Why not just keep referencing the module by the file name like we have been so far? Imagine getting a new module that needs to be tested. In traditional environments, rolling out a new module would involve either replacing the module file with something new or changing code to reference the new module. Both of these approaches carry significant risks. We're using the footer module as a simple example, but we could have an extensive set of modules that need to be tested in tandem with each other. File swapping and code changing are very risky indeed. If we use an alias of footer for RequireJS to resolve, we can easily point to a new module by just changing the value of our footer key. If our new footer module is located in a different location, even with a different name, we make a simple configuration change and the new footer is loaded instead of the old. Here's the result of our change. We can now see the results of the new footer module. Of course, with this approach, the new footer gets propagated throughout the application. If we're not really sure that we're ready for our users to see this new functionality yet, we can make one more tweak that will let us present it conditionally. We've got some conditional logic here that will set a footer library based on some variable value. Right now the value is hard coded. If we imagine some logic that checks the query string parameters for our web page, we can use that to allow us to specify the new footer module by adjusting the URL for our testing. Once we're satisfied with the result, we can make the new footer permanent by removing the conditional check at the top of the file. The point I'm trying to make is, using a module alias allows for additional abstraction. Whether this is necessary or even useful will depend on your project environment. At least now we know the option exists. I won't bother showing the results of our latest code. It looks exactly the same as the previous result.
Configuring for Legacy Libraries
Understanding the 'shim' Properties
Using the 'init' Function
Integrating a UI library
In this chapter we'll take a look at using modules for more than just a static page. I've decided to spruce up the page we've been using. As we do this we'll encounter some real-world situations that occur on projects all the time. First, we'll integrate a user interface library. There are many options out there. We'll use jQWidgets as our guinea pig. So far we've been using a fairly static HTML page. We'll simulate an AJAX process to dynamically add menu items to the list. Segmenting code into modules is great, but what happens when we need to pass values from one module to another? Placing data on the global namespace is not an option, right? We'll look at a better way. We're going to cover a lot of concepts as we look through multiple code files so get ready. Remember our last sample page? Here's what it looked like before we commented out the items to make room for our debug information. The list was getting quite long. That's often the case with actual projects, too. What starts out simple can become complex very quickly. As users make more demands of our websites, we end up adding more buttons, links, and controls. Project complexity can grow with even the simplest request from a user. Let's make our website look better and provide better functionality at the same time. Instead of having a long list of menu items, let's make it an interactive collapsed list. This will make the screen less cluttered. The end result will be that the user won't be bombarded with more information than they can consume. The user interface library we'll use is jQWidgets. It's available for free under the Creative Commons Attribution - Non-Commercial 2.0 license. That's a mouthful, just to say it's free to use for personal use. Making use of this library will give us a chance to mimic a real-life need to integrate user interface libraries into our modular design. It will also serve as a stepping stone to our next enhancements. Although jQWidgets has a pretty long list of user interface controls to choose from, we'll just focus on one: the list menu. This will allow us to present the list items on our page in an expandable and collapsible menu structure that we can interact with to see more or less information. Here's the HTML file that we'll be using for our sample page. We have fewer list items than before, but we still have some nested lists. We've added two style sheets to support jQWidgets. One is used to stylize the controls, the other sets the color scheme or theme for the controls. The plan is to make the user interaction more intuitive and let the user decide what information, if any, they want to consume. Here's the start file we'll use. We've got a reference to the jQWidgets library in the configuration properties. We're using a file that includes all the widgets, but that may not be the best choice in all instances. As with many of the user interface libraries, jQWidgets allows us to load files specifically for the user controls we want to use. For the sake of simplicity we're loading the entire library. After our standard toolbar logic, we use require to load the jQWidgets library. I chose not to include the jQWidgets library in the module dependency list because there's no need to delay toolbar and footer processing while waiting for jQWidgets to load. Since the start page is the main entry point for our website, I want to give the most rapid response possible. The first time this page loads, there could be a delay. Once the widget library is loaded, any future requests for it will be served up immediately from the cache. We're going to make use of jQWidgets, but I'm going to dive very deeply into how to use it. I'll explain what's going on as we encounter options, but I'll leave it up to you to either learn more about jQWidgets or apply the concepts to the user interface library of your choice. Within the callback function for the jQWidgets dependency set we walk through any unordered list elements in the page and add a list menu attribute to them. This is an attribute that jQWidgets needs to properly set up the list menu. Any elements with this attribute will participate in the list menu control. Once all the unordered list have the list menu attribute, we find the first unordered list on the page, and tell jQWidgets to use it as the master list menu item. We pass two parameters to the jQWidgets library. The first one indicates the theme we want to use. This matches the CSS file we loaded in the HTML page. The next parameter tells jQWidgets to disable scrolling. This means our page will expand to accommodate as many list items as we have instead of making the user scroll through the list menu. Let's take a look at this in the browser. After a brief pause, we see the HTML list elements are converted into a more pleasant list menu that we can interact with. This arrow indicates there is a sublevel. It's pretty intuitive to just click on the item to expand it. We can drill down to see the items for AMD modules and then navigate back up to the root level by clicking the back button. The page works well, but I don't like the brief flicker before the HTML list items are converted to a jQWidgets list menu. Let's refresh the page and watch that flicker again. It's short, but it's distracting. Let's address that before moving on, okay? How should we hide the original HTML unordered list items until they are converted to a jQWidgets list menu? The obvious answer is to use CSS to hide the HTML list, then use jQuery to make it visible once the list menu has been created. We'll look at another idea in a moment, but this is a good way to ensure that there is no flicker of the display when it converts from one format to another. In our HTML page, all we have here is add a hidden class to the master unordered list. I use this class all the time in my own projects. Within the CSS file there's a style called hidden that specifies a display type of none. Any tag that has the hidden class will not be shown in the browser. Now all we have to do is get jQuery to remove the hidden class from the list, once jQWidgets has finished turning our unordered list into a list menu. We're using a chaining technique to continue working with the same element that we used to make the list menu. Once the jQWidgets library is finished converting the list to a list menu we'll get a reference back that we can use to remove the hidden class. Let's bring this up in the browser and watch for that flicker. There was no flicker. I assure you, it's not a trick of video editing. I'll refresh the page by pressing F5. Ready? Here it comes. Excellent! Now we can refresh the page as many times as we want and we'll never see a flicker. All of this wasn't in vain.
Loading Dynamic Content
Let's review what we've done. We've integrated a non-modular library into our website and used it to decorate the user interface. We had to make adjustments to our code and HTML to provide for the asynchronous manner in which our script files are loaded and processed. That's a key point to consider so let's talk about that next. Using RequireJS as a script loader means our scripts can be loaded in any order and at various times. If we have any dependencies to consider we must either configure RequireJS to be aware of those dependencies or alter our programming logic flow. We're using the second option with the jQWidgets library. We placed our decoration code within the callback function that executes once the library is loaded. Let's imagine for a moment that the actual content of the page is dynamic instead of having static HTML on the page. What if we used AJAX of some asynchronous process to dynamically load content on the page? How would we decorate content that arrives in that manner? Let's take a quick look at that. Again, this is a step further along our path of discovery, hang in there. Instead of having a static HTML page, we're going to load the page contents from data. The first thing we need to do is get some data. To that end we have a data proxy module that will provide the list items for our page. This is intended to approximate an AJAX call to a web service or some other asynchronous data feed. We're not going to get into how the data proxy module works. If you're interested, the code in the exercise files is heavily commented to explain what's going on. Feel free to peruse it to see how it's constructed. For our purposes we only need to know that our data proxy provides an interface to get data that we will use on our page. Scrolling down through the file we see some default data laid out with titles and children properties. The last item in the default data array describes that dynamic data is being used. We should see that in our browser page. The final thing we need to notice is the method called loadAsObject. That's the method we will use to load the data for use in the web page. The setup for our main entry point is the same as we've seen before, only now we have an additional module to configure and we also have some additional logic that loads the data as an object. We'll reference the data module object with the callback parameter value called data. We'll make use of the data object by accessing the loadAsObject method we noticed earlier. As a final step, we hide the data object. This is to eliminate the flicker that we previously talked about. The loadAsObject method will return a jQuery object containing a master unordered list with embedded list items as appropriate. All we have to do is append it some place in the document. We do that right here. We're finding the parent of the one and only h4 tag on our page and appending this object to the end of that. Let's take a look at our HTML page. We see that we have no unordered lists in our static page. Our content will be completely supplied by data. I could've used any number of selectors to tell jQuery where to place the list items. I chose the h4 selector for no particular reason. Let's take a look at it in the browser. Our page looks and behaves just as it has before, which is exactly what we want. If we expand the first list menu item we do see that the last item in the list indicates that the data was loaded dynamically. Our list menu is being loaded from a data source and is still being decorated properly by the jQWidgets library. Excellent! Now what? All this time we've been staring at the toolbar and hovering our mouse, but it hasn't been used for anything. Let's change that and make use of one of the buttons for some user interaction. Here's some code that will listen for one and only one click event on the publish button. When that happens, we'll add an item to the list from code. Let's test this in the browser. When we click the publish button, we see that a list item is indeed added to the list menu, but it's not decorated. That's because the data was added after the decoration logic was called. We requested the new item in a click event, but imagine if the data was coming from an RSS feed or some long-running asynchronous process that periodically provides data? We need to tell the list menu to refresh itself whenever new data arrives. The obvious place to do this is in the same place that adds the list item. In this case it's the click event. We'll add one more line to tell the list menu to refresh and everything should work properly now. This additional command tells jQWidgets to refresh the list menu. This will cause it to redraw the list menu with all subordinate elements properly decorated. Let's see how it works. When we click the publish button now we see that we get a new decorated list menu item. What we have so far is great. We get a new data item when the user clicks the toolbar button. Let's take another look at what we really have, though.
So far we've been so focused on getting modules to work and getting the user interaction to work that we mixed our user interaction with the data that's presented on the page. Look at this block of code. We're setting up some logic for a user interaction, namely the publish button click event. When that happens we begin working on the user interface and append some data to it. We then make sure the list menu displays properly with the refresh command. That's a mixture of logic that affects user interaction, data retrieval, and the user interface. We've got all the logic used by this page in a single file, but what about reusability? That's a big aspect of modular programming. One of the primary purposes of modular programming is reusability. The click event processing and data retrieval may be useful on other pages, too. Perhaps other pages will present the data in a different user control. In the spirit of modular programming, let's take a step toward modular logic by separating that logic flow into related chunks. As a first step, here's the same logic broken into two different segments. We are still listening for a user click event, but this time we're calling our data proxy module to retrieve a new data item to display. Once we have the new list menu item, we use jQuery to trigger an event on the default context to let the website know that new data has arrived. We attach the new menu item as a property of the event for some other process to handle. The other process is defined next, although it doesn't necessarily need to be. For this example I chose to keep them side by side. This second process could be in a different file or even in a module. We'll get that working in a moment. We're using jQuery to set up an event listener. We use the default context that's defined in our configuration module as the listener. This was also used as the trigger point in the code above. Think about that for a second. By using the configuration module we can change the event listener easily in one place and the rest of the website will respond appropriately. When this event is processed, we use jQuery to find the first unordered list on the page and append the item that was passed along with the event. Now this is a very rudimentary example and will not work properly if we get an element other than a list item. If this were real production code we'd need to verify the new item before we blindly attach it to a user interface element. Let's look at this in the browser as it is, though. All we're looking for is what happens when we click the publish button. We see that when it's clicked, we do in fact get a new list item in the page and it is properly decorated. This is a very simplified example of how we can make use of events to overcome timing issues related to segregated code. If we need one module to notify another module about the arrival of data, modification of data, or really any type of event at all, we can make use of events to keep the modules decoupled, but still able to interact. The tendency for legacy programmers it to place all the code relating to a page in a single file such as we've done so far. That's fine in many cases, but falls short when the complexity of the page grows or we ask the page to present multiple options to the user. The concept of a page ultimately morphs into a single wrapper around a variety of capabilities. Those capabilities may have no direct relation to each other, except for the fact that they all reside within the same presentation for the user to see. Separating code into smaller chunks of related logic is what modular programming is all about. Let's refactor what we have so far and make use of modules to handle each of the separate areas of concern.
Refactoring Footer Module
The first thing we're going to do is take a look at another way to make use of modules. Let's look at our pattern so far. The configuration module does not return a value; it just defines a static object with numerous key value pairs. The toolbar module follows a different pattern. There's a callback function that RequireJS invokes after the module is loaded. We use that callback function to do some initialization, build an object, then return an object for RequireJS to pass along to the calling program. What if we need to pass parameters to the initialization process for the object? Is there a way to pass parameters to the callback function that RequireJS uses? As of the publication of this course the answer is, no. RequireJS is designed to pass references to dependent modules to the callback function. There is no provision to pass initialization parameters. That isn't nearly as inhibiting as it sounds, though. Let's refactor our footer module to demonstrate how to satisfy the need of passing parameters to initialization function. Here's the footer module as we left it from very early in the course. It's using four different string literals that just beg to be parameterized. At a minimum, wouldn't it be useful to let the calling program override the text that's displayed in the footer? While we're dealing with this topic, where would be a better place to store those literal values? If your answer was in the configuration module, good for you. You should reward yourself with some ice cream or something. Here's an updated configuration module with four new properties for use by the footer module. They are the same values we had before, but now they exist in the a central configuration module that is easy to modify without breaking program logic. We've got the footer options wrapped in an object by that name and our four values are named wrapperTag, footerClass, insertAfter, and footerText. Now let's take a look at how we can refactor the footer module. Wow! There's a lot more to the footer module now. Let's take it from the top. Our footer has a dependency on the configuration module and we're getting a reference to it with the config parameter to the callback function. This allows us to access the values we just added to the configuration module. We'll use those values to control the behavior of the footer. We also set up some default values within this module, just in case. Although we have values that are identical to the ones we placed in the configuration file, they don't have to be. These are fallback values to make this module operable even if there are no footer values in the configuration module. There are a few situations where these values would come into play. Imagine a configuration file that's dynamically created, say, during a build process. If something happened in that process to eliminate the presence of our footer values, we would still have default values to work with. How about if a later version of our website changed the way the configuration file is used or even removed it completely? Our footer would still have a basic display capability. It's even possible that some future developer may rearrange or split the config file into multiple segments. In any of these cases, our footer would not operate properly without the default values. This is an example of defensive programming. To me, it's worth the extra effort to keep future changes from breaking the code. The flip side of this is that it may not be readily apparent that some future modification actually changed the behavior of the footer. Since the footer is not part of the page content, very few people even pay attention to it. The text of the footer could change and it might be months before anyone even notices, if anyone ever notices at all. Okay, back to the code. In addition to the internal properties, we have an init function that accepts a single parameter called options. Keep in mind, however, that this function is not the same as the init function we used in the previous chapter. This is an initialization function that is only used by this module, not by the RequireJS configuration routine. The intent is to allow an object containing a group of options to be passed to this function. This allows our init function to receive a single parameter instead of having a list of parameters for each new option we want to support. I prefer to code this way because it makes future changes much easier. When defining a function that needs parameters, I find it easier to allow a single object that contains multiple properties instead of having a function with multiple parameters. Functional parameters are positional and are difficult to omit or skip over. Future programmers need to know the order and meaning of each parameter to make use of the function. Using an object allows new values to be passed to the function without breaking the legacy function calls. I did a segment in a previous Pluralsight course on this topic. Feel free to check out that course for more information on this coding pattern. There is some special processing going on to allow a string value to be passed instead of an object. If we get a parameter of type string, we assume the calling program is just passing a footer text value. We'll use that string to populate the footer text property of a new options object. If we get something other than an object for the options parameter we'll just pretend the calling program didn't pass us any options at all and we'll end up with the default values. We use jQuery to merge the default properties with the options passed to the init function. Values in the options object will take priority over the default values. We use jQuery again to store the options value onto this footer object. This isn't strictly required for this type of module, but it's a habit I formed a long time ago. I've always found it useful to have the initialization properties stored for future use within the module. We don't have any getter or setter functions, but they would be very easy to include if we needed access to these local values. Finally, we see the same code we used to have, only this time we are using the values stored in the footer object instead of string literals. Let's take a look at how to make use of this new pattern.
Using New Footer Module Pattern
Up until now we've only asked RequireJS to load the footer. Since we haven't been returning anything, there was no need for a callback function. Now that we're returning a reference to the footer, we want to include a callback function with a reference to the footer as a parameter. We use that reference to call the init function and pass a value. We're taking advantage of the fact that a string value can be passed instead of an object so we're passing a new string to use as the footer text. Let's see this in the browser. We see the footer has changed to the text value we set in code. It may not seem like much reward for the effort, but this pattern of module definition and use sets the stage for our final project. Before we get there I have a question for you to ponder. What happens to legacy code now that we've changed our footer module? None of our legacy projects ever called the init function. Is there a way that we can have our new module work with legacy code as well as future code that uses the init function? Yes, there is, but we'll need to make a couple of tweaks. Let's do that really quickly, then we'll move on. Let's revert our codes the way we used to call the footer module. I've just commented out the new way for now. Let's take a look at that in the browser and verify that the footer is broken for legacy code. As expected, our new footer module is no longer compatible with older code; we're not getting a footer. We can fix that very easily, however. A simple answer is to automatically call the init function before returning a reference to the footer object. Our legacy code isn't expecting a return value, but it won't break if one is passed. By adding this line we have provided backward compatibility for legacy code. Let's check it in the browser now. Alright, we're getting a footer again. We're done, right? Not quite. What did this new change do to our new code that knows about the init function and makes a call to it? If our module calls the init function and the calling program calls the init function, what will happen? Let's comment out the legacy call and uncomment the new init pattern. We're back to using the init function of our new footer module. What do you think will happen? Yes, we get two footer entries. That's definitely not what we want. There are a few ways to approach this, but I prefer a very simple solution. Within the init function we can just use jQuery to remove any existing footer before we process a new one. If jQuery doesn't find a footer as will be the case for legacy code, nothing happens. If there is a footer, as will be the case for new code, the default footer added by the automatic init call will be removed and a footer using the new logic supporting parameters will be displayed. Let's see the result of our latest changes. Now we're back to a single footer. We have a module that will work with legacy code as well as newly written modules. Our simple footer module has evolved into something that we can mimic in other modules. It maintains local properties. While the values we are using are very simplistic, other modules may require more complicated data structures. The footer module also contains an initialization method. This method is used to resolve parameters passed as an object, as well as options stored in a configuration module. That's another key point. The values that control the behavior of the footer module can be managed externally by changing configuration properties. The footer module is self-initializing. It will make use of default values to support legacy code and allows new code to make use of the init method to alter that behavior. The footer module provides new capabilities while still supporting legacy code. That's a pattern worth repeating. Yes, there is a lot going on with this module. If there's anything in the footer module discussion that isn't quite clicking for you, I encourage you to rewind and go through it again or better yet, open up the exercise files on your own machine and play with the code. Our next topic will build upon what we just did to the footer module.
This is going to be our final project setup and it will be the most complicated one we've done so far. From outward appearances we won't see much difference than what we've seen up to this point. We will, however, make significant changes to the code. Take a look at the last startup file we used. We have configuration code, user interface setup, data loading code, and then more user interface code. We finish up with some event handling code. All this code evolved throughout this course. That's just what happens in real life, too. Why is all this code in this one file? Does it really all belong in the startup file? We're just dealing with the toolbar, a single list menu, and a simple footer, and we have this much code. It started out very simple and has grown in complexity and length. Modular programming is supposed to help with both of these problems, right? Let's take a look at what we have with a fresh set of eyes. We have various modules that could be better organized. We already have a components folder that holds all of these modules. That flat structure makes it more difficult to maintain as more modules are added in the future. Here's the configuration object we're going to use for our startup code. Notice how easy it is to discern the organization of the files by the grouping we've used. The user interface modules are grouped together and we see they all reside in the UI folder. The same is true for the toolbar language and data modules. The configuration module is also placed in its own folder. We'll talk about the loader modules in a moment. As we scroll down to the see the startup code, we see that we just have a single line. We require KSM_UI and don't even have a callback function. This pattern helps keep the focus on the startup configuration and demonstrates that all our previous code was related to the user interface in some manner. For other projects we might need to initialize security, database connections, local storage management, or any number of other tasks that aren't directly related to the user interface. Those aspects of the app could have separate modules responsible for each of those areas and be initiated with a single line require function call just like we're doing with the user interface. So what does the KSM_UI module look like? If you're thinking we just copied the code from the original startup logic to a new UI module, that's a bit short-sighted. If that's all we're going to do, we might as well leave all that logic in the startup file. Here's what the UI module looks like. It has a dependency on jQuery and the configuration module. We're using the pattern for the footer that we recently established. We have a new concept for the toolbar and language libraries called a loader. We'll take a look at that in a moment. We're also using a DataLoader, but in this case we've got a callback function. Why? The reason is because RequireJS loads modules in an asynchronous manner. Even though I've said that numerous times it still bears repeating. We can't be sure that the modules will load in the order they are listed here. It's completely possible that the footer could load after the data. It's very unlikely, but it's possible. Since we have processing that is dependent upon the loading of data, we need to use the callback function to encapsulate that dependent logic. Encapsulate dependent logic in a callback function. The rest of the logic in this module resides in the callback function because it is all dependent upon the data being loaded. The data we're loading is what is used to populate the list menu. We use this as an approximation of a web service call. We call an init function on the DataLoader module reference returned to us by RequireJS. Then we require a new module called UI_Decorations. Before digging into the UI decorator let's look at the DataLoader module.
Data Loader Module
The DataLoader module is new for this project. We haven't used it before now. It's responsible for loading the DataProxy module and providing a wrapper around it. Why would we need to add this extra layer to the data proxy module? Why not just use it directly as we have before? When we used the data proxy module before we were calling it from within the startup code. We loaded the data and manipulated the web page directly in the startup code. That created a tight coupling between the startup code and the data module. A change to either one could break the logic flow. Using the DataLoader provides a loose connection that can be used between UI logic and data logic. The init function of the DataLoader takes a single parameter that specifies what element on the web page the data should be appended to. This is the only connection to the UI module or modules that need to interact with the data. This data module makes only one assumption about the user interface. It assumes the appendToTag has a parent that is used to append the data to. For our purposes this works, but the code could be even more decoupled to remove even this minor assumption. Another function this DataLoader module provides is the singleItemListener. This function when invoked will set up an even listener on the default context for our web page. When it receives an addItem event, it will get a single record from the data proxy object and trigger a subsequent event on the default context called appendRefresh. The single data record will be included as a property of the event call. Think about what we're doing here. This single function acts as a listener as well as a trigger. It listens for a request for a new record, retrieves that record, then passes it along to anything else that is listening for the appendRefresh event. This allows us to keep the data retrieval isolated from the user interface module. The user interface module just sends a message asking for data and listens for the result in an asynchronous manner. There is no tight coupling between the two modules. The data proxy object is exactly as it was before, except for the addition of the loadOneItem function that we just saw. Since we're approximating a web service with the data proxy module, this function just returns a hard-coded list item value. Now that we've looked at the DataLoader module, let's take a look at the UI decorations module.
UI Decorations Module
In this context the term decorations refers to the way we are transforming our unordered list into a prettier list menu with jQWidgets. The reason we have a separate decorations module is to allow us to swap out the jQWidgets library more easily in the future. Perhaps we'd rather use jQuery UI. Placing all decorations of user interface elements in one place makes migrating to the next great user control library much easier. Also, if we decide to make this website mobile aware, we may choose to decorate the user interface with a completely different library, or maybe not decorate the controls at all. Perhaps mobile CSS rules would sufficient. One more reason. If we integrate the decorations into the UI code, that creates a tight coupling to the jQWidgets library. Are you starting to see a theme here? Proper modular programming allows for decoupled connections. The UI decorator module uses the same pattern as before. We are creating a return object with an init function for initialization. It also maintains a reference to the list control that we're decorating. If we were decorating numerous user interface controls they could each be retained for future reference. The init function performs the same logic we've seen before to decorate the unordered list as a list menu. Additionally, it sets up an event listener on the default context to respond to the appendRefresh event. When that is received, it calls the locally defined appendRefresh function. The reason there's a separate append and refresh function is to allow external access to this logic. In my experience, if I code for only one way to add data, the users will expose the need for three other ways to add data. The separate append and refresh function provides an externally available method for adding an item to the list without having to make a trip through the data module. It's coded this way just due to my own battle scars. I'll come back to this point with a simple demonstration in a little bit. Back in the user interface module the last step is to load a listener's module and call the init function for it. Let's take a quick look at that module. We only have one listener here. Before Harold or Doug jump in to question why, I'll explain. For our simple example here we could very easily have the click listener directly in the UI module. It is after all a user interface event. What about listening for system generated events? Perhaps SignalR is used to receive events from a server. Maybe events are attached to file objects, in memory objects, local storage, or any number of other non-user interface objects. Where would you place handlers for those events? We have the listeners module for that purpose. Even though the only event we currently have is related to the user interface. Again, it's the result of my own battle scars. Every project is different, that's for sure. For me, organizing code into meaningful groups that can be easily maintained, trained, and explained is very important. The programmer that has to make changes to the code in 6 months may be me. I want to give that future me a fighting chance. One last thing that's worth mentioning is the configuration file. Maybe you've noticed that that we're referencing the config file throughout most of the modules for access to event types, footer options, and other literal values. The behavior of the code can be changed easily by making simple configuration changes. To me that's much better than having to crack open code to make changes. Unit testing is usually sufficient to validate simple configuration changes. Full regression testing is advisable whenever actual code is changed. Even though a code change may be simple, you never know when a programmer may introduce an extra semicolon, parentheses, or a comma that inadvertently changes the actual logic flow. Configuration changes generally introduce less risk than actual code changes. Shall we take a look at the result in the browser? It looks and behaves just like it has before so there doesn't seem to be much to get excited about. We can click the publish button and get a decorated item added to the list menu, but we had that capability before. What's the point of all the separation of modules then? The point is, we have decoupled the modules by using events to manage the interactions instead of hard-coding logic-based connections. This project is ready to expand to make use of additional pages, user controls, event types, data sources, or even be ported to a mobile environment without breaking the architecture. Various team members could work concurrently on different modules. The event rules would need to be agreed upon, then different programmers could code to the event rules without regard for how other modules are actually put together. The modular pattern offers plenty of benefits to outweigh any additional effort required to implement it. Since you've made it this far into the course, I'm probably not convincing you of anything you don't already believe so I'll move on.
Earlier I said I'd demonstrate how a user might request functionality that would warrant a separate append and refresh function in the declaration module. Let's do that and wrap up this project. While we're at it, let's turn it into a learning opportunity. Here we have a new click event added to our UI module. Yes, yes, it could be in the listeners, but I need a reference to the decorator module and the listener module doesn't currently have that reference. Instead of making a change to the listener module by adding a reference to the decorator object, I'm placing the code here for a simple example. This brings up a very important point, though. Modules can be modified to reference each other. That's easy. Just add a dependency. The question is though, should that be done? Do we really want the listener module to depend on the decorator module? What if we choose not to do decoration? That coupling may not be consistent with the needs for mobile support. It's worth some deliberation before automatically adding module dependencies. Okay, we had our learning moment. Let's see what the event does. We're listening for the click event on the save button. This literal value should ultimately be placed in the configuration module, but we're just doing a quick demonstration. Up until this point we've only had interaction with the publish button. This will be new behavior. Within the event handler we call the append and refresh method on the decorator object and pass it another literal string value. Let's see this in action. We can now click both the publish and the save buttons. The save button doesn't get disabled even though we only allow a single click. That's because we didn't specifically disable it. The save button is making use of the DataLoader module to gain access to a single list item. Without being coupled to the DataLoader and without accessing the underlying data module. The button click just sends out an event that causes a new item to be added to the list. Let's quickly review all the aspects of this page as a reminder and also for those who like to skip to the end of the course to see the final result. The toolbar is a module that works with the translation module to provide tool tips in a language other than English, but currently only French. The footer is a module that has grown to include an initialization function that allows us to specify alternate behavior for the footer, namely the text that is displayed. The list items are loaded from a data module and the DataLoader module is used to decouple the data from the user interface. The user interface is decorated with a third-party library through a decorations module. That module keeps the third-party library decoupled from the user interface logic. The data is loaded in such a manner to prevent flickering when decorations happen. We saw that there is no flicker when we refresh the page. We covered a lot of ground in this module. If anything isn't clear, run through the examples and try them on your own machine. Rewind and watch the chapter again if you need to. We integrated jQWidgets into our web page. This could've been any number of other libraries that exist to enhance the user interface. It just served as a guinea pig for our needs. We also provided content to the page dynamically as if from an AJAX process and used jQWidgets to decorate the dynamic data, too. We made use of an initialization function on a few modules to allow us to pass values to our modules. This is a much better pattern than placing values on the global namespace for shared access. We discussed decoupling modules and keeping code grouped by function. Even though there is sometimes overlap, it's usually pretty clear what code should be together. Remember, write your code so that it can be easily maintained, trained, and explained. In our next and final chapter we'll talk about optimizing the modules for easier distribution to client machines.
Optimizing Modules for Distribution
Using the Optimizer
Using a Single Configuration Object
Kevin is a lifelong learner with over 30 years in the IT industry. He works
on web and mobile applications as well as the databases and web services to
support them. With a gift for learning new...
Released13 Jan 2017