What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Increase Productivity: Add a Dev Console to Your App with JavaScript
by Carlos Saloio
Consoles can provide a faster way to code and execute site functionality. In this course, you'll learn to build a console for your web application using JavaScript.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Recommended
Course Overview
Course Overview
Hi everyone. My name is Carlos Saloio, and welcome to my course Increase Productivity by Adding a Deloper Console to your Application with JavaScript. I'm a software developer that's been working with a line of business web applications for much of my career. This course was inspired by the consoles used in video games, such as Quake and the Elder Scrolls. Creating a console is a lot of fun and incorporating it into my applications has been a huge time saver allowing me to develop and execute site functionality in a fraction of the time it would have taken otherwise. In this course, we're going to walk through the creation of a reusable console that could be integrated into your new or existing web applications. Some major topics we'll cover include designing a console control, processing commands, setting up a server with either Node.js or ASP.NET, as well as implementing help, a command history, and more. By the end of this course, you'll have a console control that can be dropped into any web project and know how to extend with new commands for your application in a matter of minutes. Before beginning the course, you should have a basic familiarity with HTML, CSS, JavaScript, and a server-side web technology. I hope you'll join me on this journey to learn how to incorporate a developer console into your web projects with the added dev console to your app with JavaScript course at Pluralsight.
What Is a Dev Console?
What Is a Dev Console?
Hello. My name is Carlos Saloio, and in this course, we're going to be walking through the creation of a developer console that could be added to your existing or new web applications. Adding a console into a web project can be a lot of fun and a huge time saver both during development and after the project is completed. But before I get into the reasons why I think having a developer console is a good idea, let's first make sure we're on the same page as to what I mean by the term, developer console. By a developer console, I mean a CLI, or a Command Line Interface, that you can bring up at any time from within your web application and that can be used to enter commands to have your application perform various tasks. Now the user experience that I have in mind is very similar to that of the consoles found in video games. So if you've ever played around with the consoles available inside of a game series such as Quake, The Elder Scrolls, or The Witcher, then you have a good idea of what I'm talking about. But in case you haven't, let's take a look at an example so that you'll have a clearer idea of what we'll be looking to achieve. Unfortunately, due to copyrights, I'm unable to show you an example from a real game. So, what I'm going to do is attempt to simulate the experience here of a fake game that I've thrown together using a web-browser. Now because video games are pretty sophisticated pieces of software, trying to test and debug them can be challenging. And one of the tools used to do this is a console. Or what I like to call a developer console. By convention, consoles are activated by pressing the Backquote key on the keyboard, it shares the same key as the tilde and is located in the top-left portion of the keyboard just below the Escape key. And you'll notice that when I press that key, that the lower portion of the screen has darkened giving me a rectangular area to enter in text commands and receive output. And then I can close the console and resume the game simply by pressing the Backquote key again. So if I wanted to, I could now enter in a command, such as help, which lists all the commands available, and we've got a few to choose from in this exam. Let's begin with the weather command. The sky actually does look a bit threatening in this scene. So how about I use the weather command and pass in an argument of rain. And just like that it's now raining. If you look in the top left corner of the screen, you'll notice that I have a Health and Magic bar as a part of my hud. Now let's say that I'd like to similate having low health. So, I can use the health command to set my health down to maybe 10% and after that maybe I'd like to cast a powerful spell, but don't have quite enough magic available to do it. So I can use the magic command to boost that up to 60. And there's a basic command history here, so by pressing up on the keyboard I can bring up a prior command so maybe I'd rather my health be around 80. One of my favorite things to do with a video game console is to instansiate objects into the game world which we can do in this demo with the add command. So let's say I wanted to add an archer or perhaps a mage into the world. And then it might be interesting to also have maybe a knight and then finally let's add in a dragon. And after playing with these 3 a bit, maybe I'd like to just focus in on the knight. So, I'll use the kill command to remove the dragon object, and then I'll use it again to remove the mage as well. leaving us with just the knight alone in the rain. And of course if I were to enter in a bogus command, I'll get an error message back. And while--you get the idea. So a developer console allows you to give commands to your software at runtime via a command line interface. Even though the application may in fact be graphical in nature.
Why Create a Console for Your App?
So hopefully, you now have a decent mental image of what a developer console is and what the user experience is like. But you may not be convinced that your application would benefit from one, after all, it's entirely possible that the software you're working on doesn't involve either mages or knights. In fact, I would guess that for many of the people watching this video, the dragons in your apps tend to look a bit more like this. Now that's still pretty scary, for sure, but obviously it is a bit different. So how does a console like what we just saw translate into a web application? Well let's take a look at a slightly more mundane example and see. Let's say you're developing a line of business application for the company you work for and it's called The CRUD Master 2000 because it essentially performs a lot of different data access operations, such as creating, reading, updating, and deleting various records in the database, and as a part of this application, you have a login screen, and because you care a lot about security and want to prevent brute force attacks, you really want to implement a feature into the login process that locks the account of any user that enters their password incorrectly more than say five times. Once that happens, an administrator needs to unlock the account before they can sign in with it again. Now it may be a while before it happens, which you know eventually someone, we'll say it's Jane in accounting, is going to forget their password and will need to have their account unlocked. So obviously at some point during the development process, you'll need to write the code to unlock an account. And taking a look at this problem with your developer hat on, you realize that this is really a very simple thing to do. It really only requires a single line of code to unlock an account and probably looks something like the following in SQL. So here we're saying that we're going to update the account table and set the IsLocked field to 0 or possibly false depending on your system, where the username is jane, and that's it. Piece of cake, right. Probably could execute that command on the database in less than 30 seconds from beginning to end, but then you put on your designer hat and start working through what that translates into in terms of a web user interface and all of a sudden it doesn't seem quite as simple anymore, so let's walk through it. Alright, so let's imagine we've got an administrator working with the application and they're carting along when all of a sudden they receive a phone call telling them that Jane needs her account unlocked, so how do they do that? Well, we could have a header area on the site, and in that header, we might have a link called admin that when clicked would cause the user to navigate away from what they were doing and switch over to a screen for performing administrative operations, and by the way, this admin link wouldn't appear for all users. We'd want to code it so that it would only be visible if the user was, in fact, an administrator for this application. And from the admin screen, there's likely to be a bunch of different administrative subsections that they could go to, but amongst them, there would likely be one called something like accounts, right, so they click on the account link and that would take them to yet another screen that had a bunch of account-related options in it and you can imagine it might have operations for maybe adding a new account or what not, but somewhere in here, there's got to be a way to identify a particular user account that we want to work with. So maybe there's a search box where we can type in Jane's username and that might send off an AJAX request to the server returning a list of users with a username matching that search text and then we can choose Jane from amongst those results. And now, we go to yet another screen, which shows us the account or profile page for Jane and now somewhere on this page will be a link to unlock Jane's account. So now we can click on that link and Jane's account should be unlocked. So great. We've done it, but that did require the admin user to navigate away from what they had been doing and search through various options across several pages in order to perform the unlock operation. Now let's take a look at what the same situation might look like with a dev console in place. So once again, our administrator user is CRUDing away when they get a call saying that Jane needs the account unlocked, but this time, they hit the Backquote key on their keyboard, up pops their development console, and then they type in the command unlock jane, and they press Enter, and just like that, Jane's account is now unlocked. They hit the Backquote key one more time to hide the console and they're right back where they had left off. It works out pretty well I think. Okay, so a few points I'd like to make in favor of having a developer console. First, the command-line interface can provide a faster workflow for the user performing the action than a GUI version can. As we saw in the last example, the amount of network traffic required to perform an operation can be made significantly less. Now, I think the graphical UI we saw for unlocking an account is fine, but keep in mind that all input lends itself to a graphical user interface. There's a reason we still use programming languages to create software after all. They're kind of fun to implement. To be honest, the original reason I started experimenting with a console was because I thought it would be cool to do. It's kind of novel and you don't see it done very often. But most important, I think, is that the development time needed to implement an operation is dramatically reduced. With a console, adding a new command such as this account unlock would probably take me less than three minutes to do. Remember, the operation being performed by the server is essentially just this one line of code, but if I had to create a graphical user interface for the unlock command, there's no way I could do it in three minutes. I probably couldn't do it in three hours. Maybe I could pull it off in three days, but depending on the complexity of the UI in question, who knows, it could end up being more like three weeks. My point is that when you implement a new feature, often the work required on the server is kind of trivial, but can still end up taking a long time to get anything usable up and running because of the disproportional amount of effort required to create a graphical user interface. Now I want to be clear. I'm not saying that you shouldn't create a graphical interface for a commands unlocking a user account. There are many users that will be best served by a graphical interface, and personally, I love creating graphically-rich UIs and I think it's great that today you can create web apps that rival even the most engaging desktop software. So I'm certainly not suggesting that you turn your beautiful web application into some sort of Return to Zorch text adventure game, but what I am saying is that there are some compelling reasons to incorporate a developer console as a feature within your web application, particularly if time is a factor. So if you have a feature such as an account unlock to implement and you're running up against a deadline requiring you to have something usable up and running quickly, you may want to consider implementing that feature using a command line interface, and then once things are up and running and you have a bit more time on your hands, well then go ahead and create the graphical interface for it. But even once you have the graphical interface in place, you may still decide to keep the console implementation around, maybe just for developers or maybe for a subset of your user base, and the reason why is because it can often provide a faster workflow for those comfortable using a CLI and because it's kind of cool.
Required Skills / Project Setup
I'm hopeful at this point that you are interested in the idea of having a console as a part of your toolkit for creating web applications, which you may be wondering how much work does it take to set one up and how skilled must I be in order to do this. Well the good news is that a console that handles the most common needs can be built extremely quickly. It doesn't need to be complicated at all. The goal of all of the code written in this course is to keep the implementation as simple as possible while still creating great results. I don't like to write complex code, so following along should not be a problem. But I do want to go over the tools that we'll be using before we dive in. You don't need to be an expert at any of them, but you'll get the most out of this course if you have some basic familiarity with the following. The first technology we'll be using is HTML. Obviously, HTML needs to be involved with any application that uses a web page for its user interface. We won't be using it a lot, though. If you're familiar with what a div tag is, then you likely know all you'll need to in terms of HTML for this course. Next, we'll be writing some basic CSS to style our HTML. There won't be anything too advanced going on here either. Mostly what we'll be doing is using it do things such as setting what colors we want elements to be and generally trying to make things look reasonably attractive. The code should all be pretty self-explanatory though. The final required piece of technology we'll need is JavaScript. We'll be using it to implement all of the client side functionality for the console. We won't be doing anything too tricky here either, but we will be using it much more extensively than either HTML or CSS, so it would be best if you have some prior experience with JavaScript. And that takes care of all the tools that you have to use and all of the tools involved with the client side of the application. Now in terms of the technology used on the server, anything that can respond to a request from a web browser will work just fine, so that means basically anything can be used. It would be nice if I could give an explicit example of every server platform out there, but there's just too many of them for that to be practical. So I'm going to give an example of a server implementation using two platforms that are fairly common. First, I'll create an example of a server using Node.js, and then we'll go over how a server might be implemented using ASP.NET. I want to stress though that you don't have to be using Node or .NET to use the console that we'll be building. The console will work just as well with the server that was written using Java, Ruby, or just about anything you can imagine. Regardless of what server technology you use, I would encourage you to watch how both of the server examples are put together. They'll be done slightly differently and will show the important concepts needed for a server implementation. If you can grasp those concepts, then it should be easy to transfer what is shown in the examples to whatever server technology your application is written with and customize the implementation for your specific needs. So I think we're ready to get started. Let's begin setting up our project. I'd like to begin by first creating a skeleton project and establishing the workflow I'll be using as we move forward building our client side code. To start with, I'm going to create a folder for this project called demo and then I'm going to open that folder using Visual Studio code, which is a nice editor, but certainly not required, so feel free to use whatever text editor you like best. Now inside of here, let's create an index.html file and this index page is going to serve, temporarily at least, as the web application that we would like to add a console to. So let's begin framing it out by giving it an HTML5 DOCTYPE and defining the start and end of the HTML document. And inside of here, we can give it a head section and a body. Now inside of the head, we can specify a title for this page. I'll just call it Dev Console for now. Inside of the body, we can create maybe a heading and just say Welcome! Okay. Good start. Now every application is going to want to have some type of styling applied to it, so let's create a Style Sheet file that will be for the application itself and we'll call it app.css. And we can link that Style Sheet into our document by creating a link tag right here and by having it refer to our app.css Style Sheet file. Okay, so that looks pretty good. We've now got a very basic, but functional HTML document for our application. So at this point, I'd kind of like to view the page. So we now need to start thinking about how best to serve this document as we proceed with development. Obviously, there are many options available for viewing our page during development. For example, one of the simplest things to do would be to open the page directly from a web browser, such as Firefox and then point it at the file system. If you're using an IDE, such as Visual Studio, another option might be to have it launch the page for you using an integrated development server, such as IIS Express. And really either of these options will work out just fine. But I'm going to suggest using a small node program called Live Server. Live Server is not a server you would ever want to use in production, but it can be really handy during development of client side code. I like it because it's lightweight and will refresh the browser automatically when a file used by the page is modified. This allows you to get immediate feedback as you make changes to your code. Live Server is a node application, so if you don't already have Node installed, please navigate to the nodejs.org website and install it on your system. Once that's been done, we can use npm, which is the Node Package Manager, to install Live Server by typing in npm install live-server, and because we want to have the server available globally, I'm going to add the -g flag, so that will begin downloading and installing Live Server on our system. Now that we have that installed, we can start the web server just by typing in live-server inside the folder we wish to host and a web browser should now open and connect to our server showing our index page. And if I now bring up my editor alongside the browser, I can start making changes and quickly see what the results are going to be, so I'll just add some exclamation marks here, and once the file has been saved, you'll see the changes appear inside the browser. And because I've enabled AutoSave, I can make more changes and every time I stop typing, the file will save and the browser will reflect the changes and this saves me from constantly having to switch over to the browser and hit refresh. This works particularly well when you have a large screen display or a dual monitor setup. It just creates a really nice workflow, I think, for editing client side code. Before writing the console, I'd like to spend just a little time improving the way this page looks. In fact, I would suggest that you consider searching for a desktop wallpaper and setting it as the background image for the page. Now there's no technical reason for doing this, so you can certainly skip this step if you prefer. But I find myself a lot more motivated to work on a piece of software when it's engaging and attractive to look at and staring into the great white void there on the left doesn't inspire me that much. So seriously, if you have a moment, go ahead and search for an image that you wouldn't mind staring at for a bit and just drag the image into the Demo folder. In my case, I have this Pluralsight wallpaper that looks like this. Now no offense to the Pluralsight art team, but I do hope your image has a bit more going on than this one. Still, this will work fine for an example. So let's head over to our app.css file and start making some style rules and I think I'm also going to give myself a bit more room to work with here. Yeah, that looks good. So let's add some rules to the page body. I'm going to set a background that has the URL of the image that I just dragged in and then I want to specify that I'd like it to be centered both horizontally and vertically, and finally say that I'd like it to be fixed so that it doesn't scroll with the page. It's also a good idea to give it a background size of cover so that it will scale better as the browser window resizes. I'm putting it on a separate line because many browsers still do not support this property using the shortcut syntax. Now if you decided to not use a background image, then perhaps I can interest you in setting a radial gradient, which we can put in with a single line of code. The center will have a medium blue and I'll have it radiate out to a darker shade of blue. So that gave us our radial gradient. But instead of a single gradient that takes up the whole page, it is small and repeating. The problem is that by default the height of the document is based on its content, which in this case is the single H1 element that we added. So the gradient only extends by that far before ending and repeating. This can be easily fixed though by adding a rule to the HTML element and setting its height to 100%. So that looks a bit better. It's possible the gradient won't look amazing once this video is compressed, but on my screen, the background looks reasonably good. One problem about changing the background to be darker though is that the text is no longer very easy to read, so we're going to want to make that a lot brighter. So if you've chosen a dark background like I have, you may want to set the body's color to be white. So that's a lot easier to read now and I think I'm also going to change the font. I kind of like Verdana. So that stands out just a little more. And I think just to give it a little more pop, I'm going to give it a text shadow and I'm going to have it offset horizontally and vertically by one pixel and give it an 8px blur and have the blur be black. And finally, I'd like to center this text. So let's add a rule to our H1 element that says that text-align should be center. Okay, so this design may not win us any awards, but it didn't take a lot of CSS code to implement and it does give us a little nicer workspace to use as we move forward coding our developer console.
Summary
In this module, we've learned what a developer console is, mainly that it's a command-line interface for entering commands within an otherwise graphical application and we saw an example of one in action. We then went over an example demonstrating how adding a developer console to a web application can save time and make you more productive both during development and afterwards. Next, we reviewed the technical skills required to create a console and we learned that it can be built easily using basic web development tools. Finally, we create a skeleton project, set up a server with live reload capabilities, and added some basic styling to our page. We're now ready to start diving straight into the creation of the developer console itself in the next module. I hope you'll check it out.
Designing a Console Control
Designing a Console Control
In this module, we're going to write the client-side code necessary to create the user interface for our developer console and inject the control into a page. Whenever I wish to create a web control, and a developer console is a type of web control, I tend to break it up into three discreet steps. The first thing I like to do is to work out what HTML elements are needed to make up each part of the control. Once that's complete, I know what my markup is going to look like, but what appears on screen rarely looks much like a control. So the next step is to style those elements using CSS in order to make them look like the control that I'm going for. Now at this point, you've got the appearance of a control, but it doesn't actually do anything yet. So the final step is to use JavaScript to bring the control to life and give it its functionality. I don't know that you have to approach it in this order, but that's what's worked well for me. So first up is to figure out what HTML elements do we need in order to create a console. It turns out that you can make a console using only three elements. The first element will be a div and you can think of this element as being the control itself. We'll use it to position the console where we want on screen to set its dimensions and even to provide the console with its appearance. It will act as a container for all the controls other elements. Inside of this first div will be a second div whose job it will be to display output. Initially, it'll be empty, so it won't take up any space, but each time a command is entered or we receive output from a command, we will inject it into this div causing its height to grow to fit that content. We'll style the outer control div so that when the output grows too big to fit inside the bounds of the control, a scroll bar will appear allowing us to scroll the content. Finally, we'll need an element for handling input. So the third element will be text input control allowing us to type within the console. As I mentioned previously, it will require some styling and JavaScript to make these three elements look and behave like a console, but these three simple HTML elements are all that's required to get the job done. Before we start writing the mark up, I'd like to create the two files that will hold the code for the developer console. One's going to be a JavaScript File, and the other one a CSS file. Now I'm tempted to name the JavaScript File console; however, that might cause some confusion between the developer console that we're building and the built-in console object used for outputting messages in JavaScript, console.log, and so forth. So the name I'm going to use instead is webcli, which stands for web command line interface. So I'll create a webcli.js file and also a webcli.css file that will hold the Style Sheet rules for the console. Now ultimately, we're going to want to use JavaScript to inject the markup for the console into the page, but adding HTML via JavaScript is a bit tedious, so until you finalize exactly what the HTML will be and what classes or other attributes you may need to have added to it, it's easiest just to hardcode the mark up into your page and edit it there until you've worked all those details out. So that's what we're going to do here. Now you may remember the first element that we're going to need is a div tag for the control itself, and then within that, we need a second div tag, which will be used for our output, and then finally, we need to add an input control. So that takes care of all of the elements required for the console, but they will require some styling. So I'm going to add a class with the name webcli to the first div and then I'm going to copy that and add that same class to the next two elements, except I'm going to add a suffix to them. For the output div, it's going to be webcli-output, and for our input element, it will have a class name of webcli-input.
Styling the Console
We've now got our HTML markup pretty well taken care of, but the results don't yet look anything like a developer console. So let's fix that now by creating some style rules that will give our control the look of a console. The first thing we need to do is to link the Style Sheet for the command line interface into this document. So I'm going to copy the existing Style Sheet link that we have here for the app.css and just swap out the names, so this one will be for webcli. And now, we can open that file and we can begin writing our style rules. One of the first things that comes to my mind when I think of a console or even a text editor for code is that they all use a monospaced font. A monospaced font is any font whose character's each take up exactly the same amount of horizontal space. This allows you to line up the characters that you write into formatted columns very easily and keep everything looking neat and tidy. So I think that's the first thing that I'd like to set up. So I'm going to begin by defining what the font styling rules should be for the text inside of our console control and I'm going to set this on webcli and webcli-input explicitly to help protect against any existing style rules the app may have and I'm going to give the font a size of 20px and a line height of 20px to make it easier to read in this video, but you may want to use something a bit smaller. Next, I'm going to set the font family to be Lucida Console, which is a monospaced font and seems to be named appropriately for our use case. If for some reason that font is not available, as a backup, we could use Consolas, and if that's not available either, we could also fall back to using Courier New. If the style rules inside your application are such that they may override this, you may want to specify that this rule is important so that it takes precedence. So that takes care of our font. Now let's write some rules that are specific to the control itself. So these will be rules for webcli only and I think the first thing I want to set is its background. I want the console to be a very dark, essentially, the color of black, but I do want it to be slightly translucent, so I will give it a .7 opacity to let the background bleed through a little bit. Okay, that's not bad, but it is a little thin, so I'm going to give mine a height of 300px. The next problem we've got to deal with is the positioning of the console. I'd like it to be more like the examples we saw earlier where it was at the very bottom of the screen and took the full width of the browser. So the easiest way to do that is to give our control a position that is absolute. I can now specify that I want it to be positioned 0 pixels from the bottom of the document body and that will cause it to snap to the very bottom of the window and I'll do the same thing with the left. I also want it to be 0 pixels from the left-hand side of the body. And finally, the right-hand side, same deal again, 0 pixels from the right-hand side of the body. So now we have it positioned at the bottom and stretched across the width of the browser. Okay, we're getting there. Let's now start addressing the input control because it's rather glaring. In fact, you probably don't really want the input control to be visible at all, so let's start writing some rules for webcli-input. I'm going to have it display as a block element and I think I'm also going to give it a width of 100% so it takes up the entire line. So right now, it's pretty obvious that we're looking at an input control and I'd like to try and hide that fact, so let's set its background to be transparent. Okay, so that's take care of the background, but we've also got to get rid of that border, so let's set the border to be none. That's better. We now no longer have a really obvious input control inside of our console. We can still place our cursor inside, but it's not quite so obvious how it's constructed. The input control's black text, however, is now very difficult to read. We're going to want to set its color to be something brighter. You may want to use something like white, but I think I'm going to set mine to be a greenish color. So let's see how this looks. Okay, so that shows up pretty well. Now we probably could get away without doing this, but I think I'm going to set the margin and padding on this control to 0 just to account for any possible discrepancies between browser defaults. Okay, so I think the next thing that I'd like to change is the amount of padding around the inside of the console. I think the text is just a little bit too tight up against the edges, so let's add a little bit of padding, maybe 5px worth around the inside of the console, and that'll give us a little more breathing room around our content. And now, let's actually create a border around the outside of the console to create a little more definition, so I'm going to create a border that is solid and has 1px thickness and a color that's basically white, but not completely white, so we'll give it a .5 opacity, so that'll give us just a little nicer edge around our console. And then just to give it a little bit more depth, let's give it maybe a 3px box shadow and this will be extremely subtle, only barely visible at the top of the console. But where I'd like the shadowing to be more pronounced is just on the inside of the console around the border to create some depth, so there's going to be an 8px blur radius on this one and this is going to be shaded black and this one's going to be marked as inset, so it's a shadow on the inside of the console making it look like the borders are raised. Now in case that's a little difficult to see in the video, let me just color this red temporarily so that you can see the area that's going to be shaded in and will create that illusion of depth and give me a little more of that window look that I'm going for. Alright, that looks fairly good. So now I could enter my console and type in some text like StartJob 1 Now or something like that if I wished. It's starting to look like you'd expect a console to, except you may notice there's this X in the upper right-hand corner, which is kind of specific to the edge browser. It's somewhat of a nice feature in that you can click it and it will clear the input for you, which may be something you like or it may be something you'd rather not have in your console. If you'd like to remove it, it's not difficult to do. We just need to add another style rule. So let's add it down here. The style rule needs to be placed on our input control using the ms-clear pseudo element, which is that X that we saw at the end of the input control and here you can see that's what my editor is telling me. Ms-clear represents the clear button of a text input control in Edge or IE10. So we'll just say that we'd like that element to have a display of none and we might want to just add a little comment here that explains what this is. This rule is for the clear button in Edge. And so now if I put focus on that input control, that clear button or X is no longer displayed. So that's an example of a styling detail specific to a particular browser, in this case, Edge. But each browser can have its own unique quarks, so it's a good idea to review your styling rules across the major browsers every once in a while to make sure that things are working as you expect. So now, let's take a look at the same page, this time in Firefox. As you can see, Firefox is rendering our page basically the same way as Edge did, except we've got some red squigglies under the first word and that's because I have spellcheck enabled in my Firefox browser and StartJob as a single word is not in the dictionary. Many of the commands or parameters that you may wish to enter into a console may have this problem. So you'll likely want to turn this feature off for the console's input. Fortunately, this is pretty easy to fix too. If we head back to our mark up, we can add an attribute on the input control specifying that we want spellcheck to have a value of false, and with that change in place, there's no more red squigglies. Let's now take a look at this page using the Chrome browser. Here again, you can see that the page is being rendered essentially the same as it was in the two previous browsers, except that when the input control gets focus in Chrome, it has blue outline appearing around it. Now you may like this or you may not, but it's certainly not consistent across the browsers, so to fix that, we can explicitly specify an outline rule. So I'm going to add to our input rules that the outline should have a value of none. And again, I'll just add a comment here specifying that this is really only necessary for Chrome. And with that change in place, I can now put focus on the input control and there's no longer any outline being shown in Chrome. So now all three major browsers are showing our console basically the same way. The main point that I'd like to impress upon you though is that if I had been writing my code using only a single browser, it's very likely I would have missed some of these subtle differences, so it's a good idea to swap out browsers now and then as you write and test your code because each one may make you aware of different issues.
Creating a Control with JavaScript
Now that we've given our console some basic styling, we're now ready to start bringing the console to life by writing the client JavaScript code necessary to give it its functionality. I think the HTML markup that we wrote for the console is now close enough to being final that we can stop hardcoding it into the index page, and instead, use JavaScript to inject it into the document's object model. So I'm going to head over to our JavaScript File and begin by creating a class for the console called WebCLI. It will contain all of the data and functions necessary to give the console its behavior. Inside of here, I'm going to create a constructor, which is a special function that gets executed automatically whenever you create an instance of this class. The first thing I'm going to create is a reference to the console object itself and then ask it to create its HTML elements using a function that we haven't actually written yet. The job of the createElements function will be to create the HTML for the console that we've just been hardcoding into the index page up until now. So let's copy this and go ahead and write the createElements function. And I think what I'm going to do is create a split pane here and in the right pane I'll load the index page so I can see what markup I need to replicate using JavaScript. So looking on the right, you can see that there are three elements that we need to create, the control itself, the output div, and then the input control. So inside of here, I'm again going to create a reference to the console itself, as well as a reference to the HTML document. And now we can actually begin creating those elements. So let's create and store our CLI elements. The first is the control element, which will be stored as a property of the console object. I'll be created by asking the document to create an element and the element I want it to create is a div, so this will be our CLI control or our outer frame. And we've got two more to do. Next is going to be the output element, which is also a div, but this is the div that is holding our console output and then we're going to need an input element and this is going to be our input control and it's not going to be a div, it's going to be an input. Okay, so that creates and stores our three main elements. Next, each of those elements needs to be assigned a CSS class, and as we can see on the right, those classes are webcli, webcli-output and webcli-input. So let's add those classes now. First up is our ctrlEl, so we'll say that it has a className equal to webcli and we'll say that the outputEl as webcli-output, and finally that the inputEl has a className of webcli-input. Now let's take a look at the attributes we need to add and really there's just the one. We need to set spellchecking to false, so let's go ahead and do that. And we'll add the attribute and this is just on the input control, so I'm going to grab this guy and say that on the inputEl that we want to set an attribute of spellcheck to have a value of false. And that takes care of all three elements, except that currently they're just three unrelated elements floating in memory. So we need to specify that the output and input elements should be children that are inside of the webcli control. We'll need to assemble them to create those relationships. So let's assemble them here. So we'll say that our outer control element should append a child to itself that is our stored outputEl. And also, we should append the child that is our stored inputEl. Finally, we don't want the console to be visible initially. It should be hidden until the user actually requests that it be brought into view. So let's hide the control and add it to the documents object model so that it enters the page. So I'm going to say that the control elements style.display property should be none, which will cause the console to not display initially, just like if we had set display none in CSS. And then I'm going to access the document's body and say that we want to append the child to it that is our console's ctrlEl, which includes the output and input elements as children. So these two lines will cause the markup to be loaded into the page, but be hidden from view. Now if we head over to our index page, we no longer need all of this code, so we can just get rid of it, and instead, we can replace it with a script tag to load the JavaScript code for our console, so our src will equal webcli.js. Now after this, you'll likely have another script tag for your application code, and within that, you could create an instance of the console. I'm not going to bother creating a separate file here though. Instead, I'll just write the code in line. So inside of here, I'm going to create a new instance of the WebCLI class. Okay. Let's give this code a try. When we look at the page in the browser now, the page is empty, but this is expected because we set the console's display property to have a style of none, but if I open the browser's developer tools, you can see that in the page, there is in fact the HTML elements that we created for the console. They're just currently not being displayed and this is exactly what we wanted. Now it could be that in your application, you don't want the console to be available to everyone. You may wish to add some logic to only instantiate the console if the user meets certain criteria, such as if they're an administrator. So in this pseudo code, let's say that we've determined the user is not an administrator, and because of that, we're going to skip over instantiating our console object. So we could test to see if isAdmin is true, and in the event that it is true, we'll create our WebCLI console object and store it. In this demo, I'm storing it globally on the window object, but in a real application, you'd likely store it within your app somewhere. Now writing code like this that runs on the client is fine if you're doing it to provide a better experience for your users. For example, if a non-administrator were to accidently open the console, they might not know how to close it or even what its purpose is. If it would just serve to confuse them and would negatively impact their experience with the application, then performing a client side check to prevent the console from being available would be a great thing to do in that case. However, please don't kid yourself into thinking that this actually prevents user from running console commands. It's trivial for a malicious user to circumvent any code that runs on the client. In fact, they could easily send requests to the server directly to execute a command bypassing your web page entirely. The only way to secure a web application is through code that runs on the server, not the client. So do write security checks using client-side code if it makes for a better experience for your legitimate users, but don't write security checks using client-side code thinking it will actually provide you with any real security. For that, write the checks using code that runs on your web server. In the case of my demo, I'd like the console to always be available, so I'll remove the client-side security check and always instantiate an instance of our class.
Summary
In this module, we designed and built up the user interface for a developer console control that can be dropped into most any project simply by adding its script and CSS file to the page and then creating an instance of the WebCLI class. We began by working out what HTML elements were necessary to make up the developer console and then tweaking them to meet our needs. Next, we spent some time styling those elements using CSS to give them the appearance of a developer console and we addressed a few minor issues that were specific to particular browsers. We then began writing the initialization code for our WebCLI class and tested it by creating an instance of that class and verifying that the console control was injected correctly into the page. Finally, we learned that though it is fine to perform checks using client-side code to provide a better user experience, it is not safe to rely on them to actually secure your software. For that, the checks must be performed on the server. So this module focused on building our developer console's user interface and then adding the control to a page. Next up, we'll take a look at how to use JavaScript to make our control behave like a console and to give it some useful client-side functionality.
Adding Functionality with JavaScript
Handling Events
In this module, we're going to write the client-side code necessary to bring our control to life so that it not only looks like a console, but also behaves like one. We've written a JavaScript class that creates our console control and adds it to the page, but currently, it's not displayed and it's no way to bring it into view or to interact with it. Let's fix this by writing some event handlers, which will allow us to respond to events, such as keyboard and mouse input. Okay, so let's begin by adding a new line to our constructor and this time we'll ask it to wire up the events for whatever events we'd like the console to handle. And now, let's write the wire events function and again create a quick reference to this object and I think the event that I'd like to handle first is a keyboard event for toggling the visibility of our console on and off. So for that, we can ask our document to add an EventListener for the keydown event. And the second argument to addEventListener is the function that we want to handle the event, which will be passed in an event object. Now if I wanted to, right inside this function, I could write the code necessary to handle the keydown event, but writing it in line like that can get to be a bit messy as your code grows. So what I would prefer to do is actually write a separate function as a part of my class, maybe something like onKeyDown and have it receive the event object and handle the event inside here to keep things clean. Now what would be nice is if I could just write inside of here that I'd like it to call self.onKeyDown and be all set. There is a problem though and it has to do with how JavaScript sets the value of the this keyword inside of a function. If I tried to use this inside of onKeyDown, it wouldn't give me access to any of the properties or functions of our WebCLI class. MDN explains the reasoning for this pretty well, so I'll quote from their website. When a function is called as a method of an object, which is what we've been doing so far when we've been calling functions on our class, it's this value is set to the object the method is called on. So in other words, when we call self.wireEvents, the value of the this keyword inside of wireEvents will have the same value as the object we called it on, which was our console object. So inside of wireEvents, I can use the this keyword to access the functions and properties of the object, but it's different when the function is called by an event handler. Here, you can see that when a function is used as an event handler, its this is set to the element the event fired from, which means that if onKeyDown gets called from an event handler, the value of this inside onKeyDown will be the HTML element that fired the event, maybe that will be a text box or the document body, who knows, but I won't have any way of accessing the functions or properties of our console object from inside this function. So what I need is a function that acts as an intermediary that will be called by the event handler and then call our onKeyDown function using our console object, so the value of this inside of onKeyDown will be what we want and we'll have access to all of the properties and functions of our class. I'm going to call this intermediary function keyDownHandler and I'm actually going to write that right up here. So I'm going to add a property onto our object called keyDownHandler and it's simply going to store our event handling function. So this is the function that will get called when the keyDown event fires, and inside of here, the value of this will be an HTML element. But if from here we use self to now call our onKeyDown function passing in the event object, the value of this inside onKeyDown will now be that of our WebCLI object. So we're now all set to write this function. So the first thing to do is to check which key was pressed. We can do this by examining the key code property of the event object. So let's write a switch and switch on our e.keyCode and in the case that key is 192, which happens to be the Backquote key, we will actually perform our visibility toggle. Now you may be wondering how do I know that 192 is the Backquote key and what if I want to use something other than a backquote. How would I find what that key code is? Well probably the easiest way is to go to this site here, keycode.info. From this site, you can press the key that you're interested in and it will tell you immediately what the key code is. So for example, if I press space it's 32, if I press Enter it tells me it has a keycode of 13, tab is 9, F6 happens to be 117, and the Backquote key, which it's calling grave accent is 192. So this is nicer to use I think than looking up key codes in a table. Now before we implement our visibility toggle, imagine what might happen if someone wanting to open the console hit the backquote while inside of a text box, that would cause the backquote to be entered into the text box, which they probably didn't want. In fact, let's head over to our index page and just add a little input element. So imagine this input element had focus when we'd press the Backquote key. We wouldn't want that character to get entered into the field, so how do we deal with that? Well one solution might be to test the event's target property, which would give us the element that the event occurred on, which has a tag Name property, which we could test against input, select, and text area to see if the target element was an input control, and if it was, we might choose not to process the event, but what I think might be better is to toggle visibility by pressing Ctrl+Backquote, which gives us two advantages. The first being that when you have the Ctrl key down, the backquote will not be entered into an input control, and second, it makes it much less likely that anyone would open or close the console by accident. And it turns out, this is pretty easy to do. We just need to test if the events Ctrl key property, which is a Boolean is true. So if the Backquote key was pressed and the Ctrl key was down, then we'll toggle our console's visibility. And do we toggle visibility? Well, you may remember that we hid the console by giving it a display style value of none, so that's what we need to toggle on and off. And to save myself a little bit of typing, I'm going to create a temp variable up here to hold that ctrlStyle and set it to self.ctrlEl.style and now I'll test if our ctrlStyle's display property is equal to none, meaning that it's not visible, and if it's not visible, then we want to clear the display none value so that it will start being displayed. Otherwise, we want to set the display value back to none to hide the console and that should allow us to toggle its visibility. Another thing that would be kind of cool is if when the console became visible, it also automatically received focus. So let's ask our control to focus itself and we can quickly write our own focus function by leveraging the built-in one that our input control already has. So I'll write a focus function here and I'll say that I want our inputEl to call its built-in focus function. Okay. Let's give this a try. As you can see, if I press the Backquote by itself, the characters are entered into the input field as you would expect, but if I press Ctrl+Backquote, nothing is entered into the text box and our console is opened receiving focus. I can click back into the input field and type some text now and if I press Ctrl+Backquote again, the console closes, but notice the focus is retained on the input field. If I press Ctrl+Backquote again, the console opens and regains focus. I can type into the console of course, press Ctrl+Backquote to toggle its visibility and things are working how I would expect, at least in terms of the keyboard. But what about the mouse? If I click on the input control, it will receive focus, and then if I click back on the console up top where I know the input control is, you will receive focus as well. But what if focus was up here and then I decided to click down in the lower portion of the console. Clicking down here doesn't give the console focus and that's not very good. I'd like to be able to click anywhere on the console and have it receive focus, so let's remedy this by adding a click event handler to our code. Let's follow the same pattern we used to wire up the keyDown event. I'm going to create another intermediary function that will be our clickHandler and it will call our onClick class function. And then I'm going to ask our ctrlEl to add an EventListener for the click event and to call our clickHandler function when the event is fired. And then we can go ahead and implement our onClick function, which simply needs to call our focus function when the click occurs. And that should do it, so let's give it a try. And now, no matter where I click on the console, it will receive focus.
Writing Output
As we proceed, it will be useful to have a few helper methods for writing output to the console, so let's create those now. In the console, as output lines are written, the console's content should automatically scroll down to ensure the input line is visible. In order to do this, I'm going to write a simple function called scrollToBottom. And inside of here, I'm going to set our ctrlEl.scrollTop equal to the ctrlEl.scrollHeight. ScrollTop represents the number of pixels the element's content is scrolled upward. ScrollHeight is the height of the element's content. One of the reasons I can get away without doing any math here is that if the scrollTop is set to a value greater than the maximum the content can be scrolled, you will automatically be set to whatever that maximum is, so that allows us to keep this function very simple and we can call it whenever we want to scroll to the bottom of the console's content. The next thing I'd like to be able to do is to add new blank lines to the console's output. So let's write a new line function and for this I'm going to say that our output element should append a child to itself and I'll have the document create that element and the element that I would like to append is a line break. And then finally, we'll call scrollToBottom to scroll the content if it's necessary. Next, let's write a function that will allow us to output a line of text. I'm going to call that function writeLine. It's first argument will be the text to output and its second will be a CSS class Suffix that will be used to style the text. Now we haven't defined any text styling rules yet, so let's jump over to our CSS file and add them now. I'll just make a copy of this comment and create a new section down here for our output text style rules. First up, I'm going to create a class called webcli-cmd and this will be for normal command text and I'm going to give it a text color of gray and then I'll create a few more of these guys. Next up, I'll create an error style that will have a color of red for displaying errors. And then finally, I'll add an ok style with a light blue color and this will be for output that is generally successful, but feel free to create whatever types of text stylings you'd like to have. There's nothing particularly special about these. Okay, so let's jump back to our JavaScript now, and in writeLine, I'm going to create a span by asking our document to create an element that is a span. And then I'm going to set our cssSuffix to whatever the cssSuffix we were given was, or if none was given, we'll have it default to ok. And then on my span, I'm going to set its className equal to webcli- + whatever the cssSuffix happened to be and then I'm going to set the innerText of that span equal to the output txt we were given and then ask our outputEl to append as a Child to itself this span element. And finally, I'm going to call our newLine function, which will write out a line break and scroll our content if necessary. Okay, now let's do something very similar, except that instead of writing out plain text, let's now make a function that can write out HTML. So let me copy this and let's change this now to be a writeHTML function, which will be given as an argument some markup to output. Now instead of a span, I'm going to want a div. So let's create a div element and then we can get rid of these two class lines, we won't need them anymore, and on that div, we're going to want to set its inner HTML to be equal to the markup that we received, and then, we're going to want to take that div and append it to our output element. And finally, once again, we can end with a call to our newline function. Now let's actually tryout some of these functions. So let's create a showGreeting function that can display some welcome text when our console is first opened. So for this I could call this.writeHTML or maybe I'd rather write a line of plaintext and you can write out any type of message you like. I'm just going to say that this is Web CLI Version maybe 0.0.1 and say that this should have a cmd style. And then, let's add a blank line after the message and now we can try this out by adding this function to our constructor. So let's jump up there and say that we want to call self.showGreeting. And now, if we were to jump over to our browser and open up our console, you can now see that we have our greeting message up at the top and a blank line beneath it as expected. So we've now got some basic tools to help us handle output as we go forward with receiving input and processing commands.
Processing Commands
Our next step is to handle the commands entered into the console after the user presses the Enter key. Once a command is submitted, we'll first want to record it so we can implement a command history. With a command history in place, the user can press the up and down arrow keys to cycle through previous commands. Now this is useful if you want to re-run a previous command or maybe just correct a typo. Then we'll need to clear the text from our input element and move it into our output element to set up for the next command and create the proper appearance for a command being submitted in the console. And then finally, we need to process the submitted command to run on either the client or what's more likely on the server. Let's begin by adding a couple of properties to our class to keep track of the commands that have been entered. First, I'll add a history property, which will be an array, and this will hold each submitted command and make up our command history. And let's also create an Offset property, which will have an initial value of 0 and this will be like a reverse offset into the history. So we'll always start at the end of the history array with the last command that was entered, and each time the user presses the up arrow key, we'll subtract one from this offset and move towards the beginning of the array. Now in order to handle command submissions, we'll need to extend our onKeyDown function and add a case for the Enter key. So let's add a new case for a keycode of 13, which is for the Enter key. And when the Enter key is down, let's call self.runCmd, which will be a function to process the text that is currently inside the console's input element. So now let's create that function and inside here we'll set self to this object and also store the txt that was entered into the inputEl, but let's trim away any white space that was at the beginning or end of it. Now the next thing we want to do is make it so that each time a command is submitted, our cmdOffset into the history is reset back to 0 and this way after entering a command, you can press up and immediately get back that last command entered. And then what we want to do is clear the text from the inputEl just before we write that text to our outputEl. This will create the look of the command being submitted in the console and clear the control for future commands. So now, let's append the command text to the outputEl using our writeLine helper function, passing in the cmd txt that we saved and writing it out using the cmd style. And I can just put a comment here saying that we are writing the cmd to the output. Now before we attempt to process the command, we should first check if anything was in fact entered. For example, it's totally possible that someone just hit Enter on a blank line. So let's test if the command's text is equal to an empty string, and if so, we can just return from this function and I'll just add a comment saying that if it's empty, we'll stop processing the command, but if a command was entered, we should add it to our history. So let's push this new command onto our history array and we can add a comment just explaining what this line does. Okay. How about we try out what we have so far and see how this is looking. So if I open the console now, I can enter a command and press Enter and you'll notice that the command moves from the input element where the text is green into our output element where because it has the command style, its color becomes gray. And each time we add a command to the output, the output elements height grows causing our input control to push down towards the bottom of the console giving us just the look we're going for. You'll also notice that I can enter blank lines and they work fine too. There is a slight problem though. When I enter enough commands to hit the bottom of the console, you can see the output is bleeding right through the console control and this is happening because I forgot to set the overflow property when styling the control. Fortunately, this is really easy to correct. So let's jump over to the CSS file for our console and inside the webcli section, I want to add an overflow property and give it a value of auto. This will make it so that if content is too large to fit inside the control, scroll bars will appear and the content will be able to scroll inside of the webcli div. So now, if we head back to our console, you'll see that the changes have taken effect and now all of the content fits inside the console and I can use the scroll bar to scroll up and down just as you would expect, and also, if I were now to add some additional new lines, everything's working fine here as well. Now it would be kind of nice if I had a command available to clear the screen and return the console to a pristine state. So as we continue writing our runCmd function, that might be a worthwhile thing to remember to add-in. Okay, so let's jump back to our runCmd function, and at the point that we left off, the command has been entered and added to the history, so it's now ready to be processed and there are two types of processing that we can do. We could run a client-side command or we could run a server command. Now 99% of the time, you'll only be interested in server commands, unless you have a very sophisticated single page app that can in fact do meaningful work without involving a server. Still, having some basic client side commands, such as clearing the screen and navigating history are useful. So let's hack together some basic client side functionality. The first step is to break each part of the command into a series of tokens. So I'm going to create an array of tokens by taking the command txt and splitting it wherever there is a space character. And now, we can determine the command name by grabbing the first element of the tokens array. In the other elements will be arguments that should be passed to the command. Now if you don't want your commands to be case sensitive, you may want to convert the name of the command to uppercase and then test against that. I now have what I need to put together a clear screen command, which I'm going to name CLS, but feel free to be creative and make a Winexe command if you prefer. Its implementation will simply be to set our output elements in our HTML property to an empty string and then return. Now as we'll see later, there are situations where this approach of getting tokens is overly simplistic and running commands in this way might not scale ideally. So we'll look at a better implementation when we tackle command processing on the server, but this is certainly adequate for our needs at the moment. Okay, let's give the CLS command a try. Alright, so I'll open the console and I'll enter in a few bogus commands here just to litter the window and prove that these commands don't currently do anything, but when I enter CLS, you can see that the console's content is cleared. Because the output element is now empty, it's height is reduced to nothing and our input is back at the top just like we'd want. And the same is true even if we had lots of content entered into the console and had visible scroll bars. Again, entering CLS regardless of the case will properly clear out the console. Let's now finish up our command history implementation by allowing users to cycle through past commands using the up and down arrow keys. So to do that, we're going to need to jump back to our onKeyDown function and we're going to want to add another case this time for a keycode of 38, which is for the upkeep. And in the case where the up key is pressed, I want to test if the command history's length + our cmdOffset is greater than 0, and if it is, then it's okay for me to subtract 1 from our cmdOffset, which will move us back one position in the history towards the beginning of the array. And now, I can assign that historical command to the input control by setting our inputEl.value equal to the history array element that has an index of this history length + the cmdOffset. Now if you're like me, this might be a little hard to follow without a visualization of what is happening here, so let's walk through an example. Imagine that our history array has three commands entered into it. The first command entered is at Index 0, the next command is at Index 1, and the third is at Index 2. So obviously in this case, the length of the history array is 3. Initially, our cmdOffset is 0, so a history length of 3 + an offset of 0 would put us here at Index 3, which is 1 beyond the array's limits. But if a user presses up, the cmdOffset is decremented to -1. So now, 3 + -1 gives us an index of 2 and we can load the last command in the array. This continues each time the user presses the up key so long as decrementing the offset wouldn't give us an index value of less than 0. We wouldn't want to decrement our offset beyond that point or we'd run past the beginning of the array. The main thing to keep in mind is that we're starting from the end of the array and moving backwards. If we handled the up key, I also want to tell the event to prevent the browser's default action. In some browsers like Chrome, the default action for the up key when inside an input control is to place the cursor at the beginning of the line, and usually, this isn't what you'll want, so I want to prevent that from happening. And we can follow almost the exact same logic for handling down key presses. So I'll just copy and paste this and I'll set the keycode to 40 for the down key and change our test to ensure our cmdOffset has a value less than -1, and instead of decrementing, we increment the offset because we want to move in the other direction towards the end of the array. The rest can stay the same. And also before I forget, we've got to add breaks to the end of our up and our down cases and that's all there is to it, so let's give it a try. So if I open the application once again, I can open up the command console and then enter in a few commands, and then once they've been entered, I can use my up and down arrow keys to cycle through the command history and it looks like everything is working just fine; however, there is a problem. I can also cycle through the command history if focus was on the input control above, not just when the console has focus, and obviously, we don't want that. The problem is that our keyDown event is placed on the document, which means that keyDown events from anywhere in the document will be handled by our code, which is ideal for toggling the console's visibility, but we need to make sure to only handle the other keyDown events when the console has focus. So I'm going to pull out the backquote handling code from the rest and write a comment saying that Ctrl + Backquote can happen anywhere within the document and then just add the keyCode test inside of here, and after handling this, we'll return from the function, but for all other keys, I only want this code to run if the console's input has focus, which we can test by checking if our input element matches the documents currently activeElement and then we just want to wrap this entire switch inside of the if block. And with that change, we'll get the behavior that we want.
Graphical Output
In case it's not obvious, I just wanted to point out that unlike traditional consoles, our developer console is not limited to only displaying text. In fact, because our console is essentially just a div in a web page, it can contain any type of content that a web page can display. So if it's useful for a command of yours either on the client or the server to return, for example, an image, instead of plaintext, that's not a problem. If it made sense for the output of your command to use SVG to display the results perhaps as a chart, that would be fine too. In fact, if you wanted, the results of your command could be in the form of a video or even as an iFrame to another website altogether. That said, if plaintext meets your needs, then I'd encourage you to keep things simple. After all, one of the main purposes to having a developer console is to save development time. Just keep in mind that you have this capability available should you need it and that there's no reason to constrain your thinking to just what traditional consoles are capable of. So before we transition into server commands, I just wanted to spend a little time demonstrating some non-text output. So let's imagine that I had a cmd that was called IMG and whenever that command was run, I wanted it to display an image in the console, which I could do by writing out some HTML. So in this case, I want to output an img tag and I'll need to give it a src that will point to some image I want to display. And then finally, I want to just return after processing the command. Okay, so let's quickly go find an image. We can go to Google and see what we can find. Actually, we can just use this image here. So I'll copy this image's address and then we can just head back and I can just paste that right in for the source of our image. So let's head back to our console now and let's just give it a try. So I'm going to open up our console and enter in our new image command, and sure enough, here's an image right here inside of our developer console and there's really nothing special about it. It reacts the same way text would. We can enter in new commands, we can see that it will scroll through the console, we can use cls to clear it out, same as we would text. There's really no difference. So now, let's try an example with video. So I'm going to do a quick search and see if we can find maybe a Pluralsight video on YouTube somewhere and see about injecting that. Okay, so this seems like their channel. Okay, welcome to the new Pluralsight. (Video playing) So let's say I was interested in this video being the output of my command. So if I go to Share and then Embed, I can copy out the HTML they recommend for adding the video to their page. So with this in our clipboard, let's head back to our editor and let's make a new command. This one is going to have the name of YOUTUBE, and once again, I'm going to be using our writeHTML helper function and I'm just going to paste in what they gave me, although, I think I am going to add an autoplay value of 1 on our source just so the video will immediately start playing. And as usual, after finishing the client-side command, we will simple return from this function. Okay, and with that, I think we're ready to try it out. Okay, so now if I were to open up my developer console, I can run my YouTube command and up pops the video. Of course, I can still continue to interact with my console. So I can enter in additional commands, and of course, I can still scroll through the content while the video continues to play, and as before, I can still use the cls command to clear the console window. Now obviously, the image and YouTube commands as I've implemented them here are a bit ridiculous, but my goal at this point isn't to demonstrate a well-built command. Instead, I just want to impress upon you that our developer console can contain graphically-rich content and that if you're willing to use your imagination a bit, you could build some very interesting commands.
Sending Server Commands
We're now ready to connect our console to a server, and to do that, we need to transmit the user's command to the server and then display the server's response. Now there are a few ways we could do this. If your application uses a third-party library or framework, such as jQuery, Aurelia, or Angular, then you may wish to use their helper functions to send the request to the server. I'd like to have a console that has no third-party dependencies and is really quick and easy to drop into any project. So I'm going to avoid this option, but there's nothing wrong with it. For example, if you know every application you'll be writing will be using jQuery, then it might make sense to use jQuery's AJAX function to send the request. Another option would be to go old school and use the XMLHttpRequest API to send the request and this would be a very safe choice, but Microsoft designed this API long ago and I find it to be a bit clumsy to use in my applications, so what I'm going to do is use a more modern implementation of XHR, known as the Fetch API. Now you could argue that Fetch is a bit of an experimental technology at this point, but it has good support in modern browsers like Edge, Firefox, Chrome, and Opera. If you'd like to use Fetch on a browser that doesn't yet support it, GitHub has written a Fetch polyfil which you can get on surprisingly at GitHub. So this is what I'll be using to send the request, but feel free to swap out Fetch for whatever you're most comfortable using to send a request to your server. Okay, so here we are back at our runCmd function, and as it's written now, it can handle blank input or input that matches one of our three simple client commands. But if we get a command that doesn't match any of those, then we need to send the command off to the server to process it. So I'm going to do that using the Fetch function. It's first argument is the path to the resource you want to fetch. Now I'm going to use /API/webcli on my server, but you can use any route that you'd like. The second argument is an object is an object that you can use to configure your request. The first property I want to set on the object is called method and I can use this to specify the HTTP method the request should use, in this case, post. Next, you can use the headers property and assign it a new Headers object, which can contain any headers you wish your request to have. In my case, I just want to specify that the type of content I'll be transmitting is going to be in the JSON format. And finally, you can use the body property to specify the body of your request. In our case, we use JSON.stringify to serialize a simple object that contains a single property called cmdLine and we'll set the cmdLine equal to the cmdLine text that we got from the user's input. Now when you call Fetch, you will return a promise for a response object. In case you haven't used promises before, a promise is an object that represents an operation that hasn't completed yet, in this case, our Fetch request to the server. The promise we get back has a then function, which allows you to specify a function to call when the promise resolved successfully. This function will receive the response object. Now the response object has a JSON function we can call, which reads the response stream to completion and returns yet another promise for a JSON object. So we can now chain another then function and specify a function to call when the JSON object is ready. This function will receive the JSON object in its result argument. So it's inside of here where we'll be able to look at the result object we got back from the server. Before we tackle that though, I'd like to add one last function to our promise chain called catch. Catch allows you to specify a callback function in case there was an error resolving one of the promises in our chain, which could happen if there was a network failure and we were unable to contact the server. So inside of here, I'll just have our console write out a line of text and have it say that there was an Error sending the request to the server. And finally, I'll just specify that this should be output using our error CSS style. Okay, so now we can deal with the response the server sent us after it processed the command. But first, we need to think about what type of response the server should send us and we need to come up with a response type that will work for a wide variety of possible commands. Here's one way it could be done. We could have the server send us a result object that has an output property and this would be a string that contains the output to display in the console. The output may be a successful result or it might be an error message. The way we'll know the difference is by a second property called isError and this will be a Boolean that will be true in the event the output contains an error message. And finally, we'll have a third property called isHTML, which is also a Boolean, and if this is true, we'll know the output contains HTML, rather than plain text and these three properties should give us enough information to display the results. So I'm going to begin by storing the output from the result object into a temp variable and then I'm going to determine the style to use when outputting text by checking if the isError property is true. And if it is, I want to use the error style, but if it's not, I'll use the ok style. Then I'll check if the isHTML property is true. If it is, I'll write the output using our writeHTML helper function. But if isHTML is not true, then I'll write the output using the writeLine function. And that should do it. So let's go over to our browser and try out what we have. Okay, so I'm going to open the console and make sure that my client commands still work and it looks like they do. So now I'll try and run a new command. And you can see that we're getting that catch error message and that makes total sense. The browser is trying to send the request to the server, but I haven't set up any server-side code yet to handle the API/webcli request. But in terms of our client-side console code, things seem to be working pretty well.
Summary
In this module, we flushed out our WebCLI class adding a bunch of useful functionality to our developer console. We began by writing event handlers for keyboard and mouse events allowing the console to respond to input appropriately. We then created a number of helper functions for writing output to the console. And we wrote some simple command processing code to handle basic client commands, such as clearing the screen and navigating the command history. We also learned that our console can contain content beyond text, such as images or even video. And finally, we wrote the code necessary to send commands to a server for further processing and then display the returned results. So this module focused on building the necessary client-side functionality for a developer console. Next, we'll take a look at things from the server side.
Making a Responsive Server with Node.js and ASP.NET
Project Setup
In this module, we're going to take a look at how you might construct a server to interface with a developer console we've built in this course. And I'm going to attempt to do this by writing not one, but two different server implementations using two popular server technologies, Node.js and ASP.NET. Now I don't really want to advocate any particular way of creating or organizing your server software. The goal here won't be to create the most elegant code, but to instead give examples of how to quickly get a server up and running so that you can incorporate a developer console into your own software projects. My hope is that after reviewing these server examples, you'll take the ideas from them that you like, refactor the pieces that you don't, and enhance them with ideas of your own to create an implementation that will work well for how you and your team like to write software. So let's begin setting up the skeletons for our two server projects. The first thing I'm going to do is create a folder called wwwroot. This is the folder that will server all of our static client-side code from, so all of the HTML, CSS, and JavaScript files that were in the browser will go inside of here. And now, I'm going to move the four client-side files that we've written so far into this folder. Next, I'm going to create two new folders, one for our node server and another for our ASP.NET and I'll give each of these their own copy of the wwwroot folder and then I can remove the original copy. So in the case of our node server project, we're going to serve all of our client files from wwwroot and have our server-side code sit one folder above that in this folder. Alright, so let's begin inside our node project folder. If you haven't installed Node yet, you can grab an installer for your operating system at nodejs.org. Now I'm going to use npm, the Node Package Manager, and its init command to help walk me through creating a package.json file for the project and I think the name is probably fine. I will use a description of console-server and I'll have the entry point be server.js and I'll just accept the defaults for the rest of these guys and that looks good to me. I'm now going to use the npm install command to add the modules to the project we'll need to create our server. Now there's a number of different frameworks out there for creating web servers. I'm going to choose to use express though because I think a lot of people are already familiar with it, and even if you're not, it's pretty simple to follow and understand. Next, I'm going to install body-parser, which is middleware to allow us easily parse the JSON requests sent by the client console. I'm also going to add a module called escape-HTML, which will allow us to escape strings that may contain characters that need to be encoded for use in our HTML output. And finally, I'm going to add the save flag, so these modules will be recorded as dependencies of our project in our project.json file. So that will download and install the modules for us. And now, if I take a look at this folder in the editor, you can see that yes, these three modules have been added as dependencies to our project. Alright, so let's start out by adding a new file to our project and I'm going to be writing server.js, and inside here, I'll start by enabling strict mode, and then I want to create a variable to hold express and I also want to require the express module. The next module we want to load is the bodyParser, so let's make another variable to hold that and require it as well. I can now create a variable for our application and instantiate it by calling express. Okay, so we're now ready to begin adding middleware to the HTTP pipeline. If you're not familiar with that term, middleware in this context means a piece of code that has access to the HTTP request and response and can attempt to handle the request fully or pass it off to the next middleware component in the chain. For example, you might have a middleware component whose job it is to log incoming requests, and then once that runs, it hands off to an authentication middleware, which checks if the request is actually allowed and it may decide that the request is not allowed and send back a response saying so short-circuiting the chain or it may determine that the request is perfectly fine and hand off the request to some additional third middleware component, which will continue processing the request. So for our server, the first piece of middleware I want to run is one that will check if the request if for one of our static files, and if so, serve it from the wwwroot folder. To add the middleware to the application, I just need to call app.use, in this case, specify to use expresses static middleware and pass it wwwroot as the folder to serve from. If the request wasn't for a static file, it's likely a JSON request being posted from our developer console. So the second middleware component I'd like to add to our application will be one that can parse requests that have a content type of application/json. So once again, I'll call app.use, but this time, pass in our bodyparser and use its JSON function. That's really all the middleware we need for this demo, so let's now start the server. So I'm going to create a variable to hold our server and then instantiate it by calling app.listen and I'll have it listen on port 5000 and specify a callback function. So once it's up and running, I can report this information. And I'll just write out a message in here to display to the user what port number the server started on. And with that, I think we should be good to test if our server skeleton is working. Now we could drop to the command-line and run the server here by entering node server.js or if you're using Visual Studio code like me, you may find it more convenient to just launch it from within the editor. You can do that by pressing F5 to run with debugging or Ctrl+F5 to run without debugging. The first time you do this, you'll be asked to choose an environment type. I want Node.js, of course in this case. And then it will create a launch configuration file for you and ask you to make any changes to it that you need. In our case, the default saying should be just fine though. So now I can press Ctrl+F5 again to run the app and you can see from the output that our server is now running. So now, if I navigate to our server using my browser, you can see that our site is running and serving our static client-side code just fine. So our node server is off to a good start. Let's now do the same with an ASP.NET server. When it comes to writing a web server with .NET, you can choose either traditional ASP.NET, which can leverage the full .NET framework on Windows or the more recently introduced ASP.NET Core, which uses a subset of the full framework called .NET core. Of the two, I'd actually prefer to do a demo with .NET Core because it is lighter weight and it runs on multiple platforms. Unfortunately, at the time of this recording, there are a few things surrounding .NET Core that have yet to be finalized, so I will be using traditional ASP.NET for this example. The good news though is that almost all of the code we'll be writing is exactly the same regardless of which version of ASP.NET you end up using. It's mostly just the bootstrapping of the applications that differs a bit. For our .NET project, I will be using Visual Studio Community Edition 2015 as my editor. If you'd like to grab a copy, it can be downloaded at visualstudio.com. So the first thing I need to do is to create a new project and I'm choosing to create an ASP.NET web application that uses the full .NET framework. I'm going to give it a name of DevCon and also have its location be inside our demo\net folder. Next, I need to choose a project template and I'm going to choose the empty template, which will give us a fairly empty project skeleton, and to that, I can choose to have it add folders and references for me for web forms, MVC, or Web API. Now you could use any of these three to handle requests from the developer console, but I'm going to choose to use just Web API. And now when I hit OK, Visual Studio will create the DevCon project skeleton for me. Now there are a few empty folders here that I don't intent to use such as App_Data, Controllers, and Models, so I'm going to select and remove these guys just to clean things up a bit and that looks pretty good. Now you can see on the right-hand side that our net folder contains wwwroot and a DevCon folder, which holds all of the files that are part of the DevCon project. Now if we were writing a .NET core application, having our static files in a folder called wwwroot would be convenient, but in our case, it's actually simpler if they just resided in the root of the DevCon project. So I'm going to go into the wwwroot folder and copy all these files into my DevCon project at the root. As you can see, all of our static files are now shown in our project inside of the Solutions Explorer window. And now I can go up one directory and simply remove this folder that we no longer need. And that's all there is to setting up the .NET server skeleton. Let's test it out by running the application. You can do this by pressing Ctrl+F5 on your keyboard and that should build our application and then launch it. But as you can see, our static files are being served correctly and our client code seems to be running just fine.
Processing Commands - Node.js
Now that our servers can handle requests for static files, it's time to add support for processing command requests from our developer console. Let's begin with our node project. First, I'm going to add a new file to the project called webcli.js, and once again, I'm going to enable strict mode. I'm going to place all of the code related to handling console commands inside of this file, which works fairly well for small programs like this demo, but in larger apps, you may prefer to split the code into multiple files. So I'll take that approach when we create the .NET implementation, so we'll have an example of both ways of organizing the code. Next, I'm going to export a function that will be passed the application object that we created in the server.js file. And now, we can use the app object to create a route handler for post requests to API/webcli and then specify a function to handle the request, which will be passed a request and a response object. Now you may remember that once this function processes a command, it needs to send the client back a result object that looked like this. So let's define a command result class that we can send to the client. I'll have its constructor accept values for its output, isHTML, and isError properties. Now let's add the output property to this object giving it the passed in output or if none was given, it will default to an empty string and this will hold our success or our error output. Okay, and we've got two more of these to do and we'll do the same for the isHTML property giving it a default value of false and this will specify if the output property contains plaintext or an HTML string. And finally, we'll set up the isError property and this is also going to have a default value of false, but it will be true in the event that the output is an error message. Okay, so let's create the result object that we'll be sending back to the client and we'll have it start out with a generic error message of invalid commands. IsHTML will be set to false and isError will be true, at least until we successfully execute a command. Now let's create a try/finally block. We'll attempt to process the command inside of the try block and then use the finally block to ensure that regardless of any exceptions thrown, we'll always return a result by sending it out in our response. Okay, now let's split the command-line into an array of tokens just like we did on the client by looking at the requests body and splitting the command-line property on the space character. Then we'll store the command name by grabbing the first element in our args array and then converting it to uppercase characters. We're now ready to search through our available commands for one matching the command name we've got and then executing it. But first, we need to create this collection of commands, so let's do that now. To do that, I'm going to create an object literal in this module called _commands, which will hold all of our command objects. Now let's define a command class to make it easier to add these new commands. I'm going to pass each new command a help description, as well as a function parameter, and then assign them as properties of the command. The help message should come in handy later for creating a help command and the function property will store the actual implementation for executing the command. With that done, we can now create commands by adding command objects as properties of _commands. I'm going to start out with a simple ECHO command that will echo back to the client the first argument that it was given. Each command's implementation function we pass the args array that we made in the route handler above. So to implement this command, I'll just test if we were passed at least one argument other than the command name, and if so, return a command result when output of that same first argument, but if we didn't get any argument other than the command name, we can just return an empty string as our output. Now if we go back to our route handler, we can now replace our default error result with a result from executing the command that had a matching command name and passing its function the args we received in order to execute the command. And then finally, returning the command result back to the client. All that remains now is to add this route handling function to our application. So let's head over to our server file and do that now. We want to both create and handle the Web CLI route, which we can do by requiring the Web CLI module that we just wrote and passing it our application object. And now, if we launch the server and then open up our web browser, we should be able to test if our ECHO command is working. So I'm going to try and have the server echo back an argument of Hello! And yep, I'm getting Hello! back. Now let's try echo again, but this time using some numbers and yes that looks fine too. Now let's try an echo Hello World and you can see that only Hello is being returned and this makes sense because I wrote the command to only echo back the very first argument after the command name and world is the second argument. But what if I really did need to pass Hello World as a single argument? Well often quotes are used to wrap around arguments to express that they should be treated as a single token, such as a file name that has spaces in it, but that doesn't work given the way we're currently splitting the command line into tokens. We're separating whenever there's a space character. So if your commands are such that you won't have spaces in your arguments, then you might as well stick with the simple command line parsing we currently have. But if this is a scenario that you need to handle, you'll need to upgrade to a more sophisticated way of parsing the command line. So let's see if we can improve our command line splitting a bit without adding too much additional complexity to our code. One way to accomplish this would be to use a regular expression, which is a sequence of characters that can define a search pattern. In our case, we'd be searching for arguments within a command-line string. A benefit of regular expressions is that they allow for complex string processing using an amazingly concise syntax that is supported across many different languages. The downside to regular expressions though is that they can be very cryptic leading to code that is hard to read and to maintain, and for that reason, I tend to use them sparingly, but this is a situation where I think they fit the bill pretty well. It can be helpful to construct your regular expressions using a tool, such as the Scriptular website so that you can test the results as you write your expressions. I'm going to begin by entering a test string that has a command name followed by a bunch of arguments, the third one being a quoted string containing a space. Next, I'm going to enable global matching so that all of the matches will be found. And now I can begin writing my regular expression. I'm going to begin using the except bracket syntax to say that I want to match any character, except a space or a quote, and then I'm going to use the + character to specify that this should happen one or more times. So what I'm currently matching is any character that's not a space or a quote one or more times. And as you can see, this gives results similar to what we already have, except that it knows to ignore the quote characters. I'm now going to add an or to get it another way to find a match. And now I'm going to say that you can also match a quote followed by any number of characters that are not quotes followed by a closing quote. And now it's able to match the quoted argument even though it contains spaces. The only problem here is that the quotes are being included in the match, but this is about as complex a regular expression as my little brain likes to deal with. So once we have the matches, I'll just use a string replace to strip out the quote characters. Let's now replace our string split with a new function called getArgs, which we use our regular expression to parse the command line. And then down here, I'm going to paste in an implementation of that function and you can see that it creates a variable to hold the regex object that matches tokens and another that simply matches quotes. We then can get a collection of arguments by taking our cmdLine and running the match function on it, passing in our token regex. Next, I loop through each of the arguments we got back and remove any quote characters that they may have and replacing any quotes with an empty string. And finally, I return our argument collection. Now this improves over what we had before, but it's still not perfect. There are additional edge cases that this can't handle. So we could continue to improve it, but I think it currently provides a decent bang for your coding buck without being overly complex. I found this to be all that I have needed to parse my commands. Of course, if you have more complex needs, you can certainly enhance this with more sophisticated parsing logic, but I'd encourage you to keep things simple if you can. With this in place, we can now run our echo command passing it a quoted argument, such as Hello World and get back our desired results.
Processing Commands - ASP.NET
Now let's set up the command processing code for our .NET project. We'll be porting over much of the code that was explained in the previous node implementation, so hopefully you followed what was covered there. However, in this server example, I'd like to organize things in a little more modular way. So let's begin by creating some folders to help organize our files. First, I'll create a WebCLI folder, and then within that, I'll make a folder to hold all of our commands, and another called Infrastructure to hold all of the files that will assist in implementing our CLI processing. And we really only need to focus in on the WebCLI folder now, so I'm going to scope our view just to that folder. Of course, we can always back up our view to the entire solution if need be, but for now, I'd like to just focus in on the WebCLI folder. Now let's add a controller that can handle the requests from our client and I choose an empty controller to begin with and then I'm going to give it a name of WebCLI Controller. Now to keep things simple, I'm going to keep everything inside of the DevCon namespace. So inside of here, we're going to need to write an action method that will be called whenever a post request comes in for API/webcli. So I'll create a public method that returns a ConsoleResult object to the client and I will name it Post and it will read from the body of the request a CommandInput object that we will call command. Now obviously, we haven't actually defined the ConsoleResult or CommandInput class yet, so let's do that now. First up, let's define the ConsoleResult object. And into here, I'm going to paste in the properties for this class that I basically copied from our node example. It just poured them over to C#. The main difference is that I called this class CommandResult in the node project instead of ConsoleResult, but they have the same purpose. Next, I'm going to take advantage of C#'s ability to have overloaded constructors and create both a default constructor and one that can take a string parameter called output. This will give me a nice short syntax for creating client responses that were successful. Along the same lines, I'm going to create another class in this file called ConsoleErrorResult and I'm going to have it inherit from the ConsoleResult class above. It will differ in that its default constructor will set isError to true and default to have an output message of Invalid syntax. Finally, I'll overload this constructor allowing it to also take a string parameter giving me a short syntax for creating custom error results. And it looks like these using statements are actually not needed, so let's just clean those up and I think they should be good. So now let's add the other class that we needed, which was CommandInput and let's once again set the namespace to be just DevCon. Okay. So you may remember that our client sends the server an object with a single property called cmdLine, so we'll add that here. Now while it may seem like overkill to be dealing with objects and classes for what is really just a single string, there are some advantages to this. For one, if we later decide to send additional properties to the server besides just the command-line string, it's really easy to add them to this class and this class also gives us a logical place to put our command-line parsing code instead of just having it floating around by itself. So I'm going to paste in a port of the getArgs function that we put together in the node example, and with this in place, we can now simply ask our CommandInput object to parse itself. Okay, it looks like I'm missing a using statement for a regular expression, so let's add that and then we can remove the ones that are not needed. I'm using the same regular expression as before, except that here I needed to escape the quotes by doubling them up. I then called the Matches method on the regex, which returns a MatchCollection. If you're wondering why I'm casting here, it's because I want to use link to convert this MatchCollection into a string array. The thing is most link methods target IEnumerables of T, but MatchCollection only implements ICollection and IEnumerable, but not IEnumerable of T. But Cast is the exception to the rule, so I'm using it to convert what you might call a weakly-typed collection to a generic IEnumerable of T. Once that's been done, the rest of the link extension methods light up and this allows me to call Select to grab the string value for each match object and then call the string's Replace method to remove any quote characters that may be in the string. And finally, I convert the collection to an Array, giving us the return type that we want, which is an array of strings. Okay, so with these two classes implemented, we can close out these files and then head back to our controller. We'll begin by storing the command line as an array of string arguments by calling the GetArgs method on our CommandInput object. And then we'll store the name of the command by grabbing the first element of the args array and then converting it to uppercase. Next, I'm going to create a type variable that will hold the type for the Command class that we will want to instantiate and run and I'll set it to null because we don't yet know what it will be. And then our next task would be to search for the command to run, and then once we know what it is, we can then instantiate and then run the command. Now when running a command, an exception could be thrown, so let's write a try/catch block that ensures that in case of an exception, we'll return to the client a default ConsoleErrorResult object. Okay, so that's our outline. But before we can actually search for our command type and run it, we first need to actually have a command. So let's turn our attention to creating commands. Each one of our commands will be a class that implements the IConsoleCommand interface. If you're not familiar with interfaces, they're basically a way of listing requirements, such as properties or methods that a class must have in order to be set to implement that interface. In the case of a console command, the only requirement will be that the class implements a method that returns a ConsoleResult object is called Run and receives as an argument, a string array. And that's all there is to it. It's a very simple interface. Before writing our command, I'd like to take advantage of another .NET feature, attributes. With attributes, you can declaratively add information to elements of your code such as methods or classes and then query that information at runtime. In our case, I'd like to create an attribute called ConsoleCommand that we can use to decorate our command classes with their name and description. We'll want to inherit from the base attribute class and then I'll add an attribute to our Attribute class to limit its usage to just decorating other classes. Okay. Now we'll create two properties, one for the command's Name and then a second one for the command's description. And of course, you could extend this with whatever additional properties you like. Then we'll write the constructor for our attribute and we'll have it take the name and the description as parameters and then we'll just assign them to the classes properties and that's all there is to it. So let's take what we've got now and write a .NET version of the Echo command. So I'm going to add a new class to our project that's called Echo and I'm just going to clean this up a bit. Now let's add our ConsoleCommandAttribute to this class specifying a name of echo and a description of Echos back the first arg received, and of course, this class needs to implement the IConsoleCommand interface and I'll just ask Visual Studio to create a skeleton implementation of that for me. And now we can implement our command in the Run function, which you may notice looks a bit like the main function used by the C family of languages in that it receives as a parameter an array of strings from the command line. So we'll want to test if we have at least one argument besides the command name, and if so, return it in a ConsoleResult. Otherwise, we'll just return a default console result object, which will just send back an empty string. Alright, so now that we actually have a command, we can finish up our controller. In order to locate the command to run, we'll need to construct a list of possible command types. So let's add to our controller a static list of types called CommandTypes. Now the simplest way to populate this list is to do it manually. So inside of here, I could just list each of the possible commands that can be run, such as the Echo command that we just wrote. Another option would be to populate this list programmatically by querying the application's assemblies for any types that implement the IConsoleCommand interface. Here's a constructor that just does that. You can see that I'm getting a list of assemblies that have been loaded into the AppDomain and then I get the types within each assembly flattened into a single collection using the SelectMany method and then store them in types. I can then get a list of CommandTypes by getting a collection of each types interfaces and keeping only those who have the IConsoleCommand interface. If you go with the programmatic method, there's no reason to keep the explicit list. Next, I'm going to store the Type for the custom attribute we created for console commands because it will be convenient to have available for command lookups, and with that, we're now ready to actually perform a command lookup. What we'll do is loop through each type in our CommandTypes collection and read out the custom attribute that was placed on each one. We'll also need to cast the attribute that we get back to our specific ConsoleCommandAttribute type so we can access its properties. Now each command should have exactly one attribute on it, but let's verify that the attribute we got back isn't null and then test if it has a name that when converted to uppercase matches the command name from the client. If it does, we found the correct cmdTypeToRun and we can break out of the loop, but if after searching through all of the types we didn't find a match, we'll return a ConsoleErrorResult. Now let's create an instance of our command, which we can do by using the Activator and telling it to Create an Instance of our cmdTypeToRun and casting the returned object as an IConsoleCommand. Finally, we can simply call the Run method on that object passing it the arguments and returning the result. Let's run this application and test it out. So if I were to open the console now, I can test out our echo command with a quoted argument, which looks like it's working well, and then if I run it again with no arguments, I get back an empty string, which is expected, and then finally, if I run a non-existent command, I'm getting back the default ConsoleErrorResult of invalid syntax. So our processing code seems to be working just fine.
Summary
In this module, we focused on server-side code and we set up two server projects that could serve the static client files we had written previously. We also added support for handling quoted parameters in our commands and we wrote a simple echo command so that we had something to test with. Next, we wrote the code to process the command sent by the client and return an appropriate response. And we walked through an example of how to do this, first using Node.js and then we saw an alternative approach using ASP.NET. Now that the basic functionality needed is in place, we'll look at some ways to further enhance what we've done and add some additional commands to the console demo.
Further Enhancements
Implementing Help
In this module, we're going to look for ways to enhance what we've built so far beginning with adding a basic help function to our console, but we only have one server command at the moment, so let's add a second, so we'll have a little more for our help command to work with. Fortunately, with our infrastructure now in place, adding commands is really easy to do. Let's say that for some reason I wanted to add a command that could perform simple addition. My workflow would look something like the following in .NET. First, I'd make a copy of an existing command class and then I'd just rename the file to Add. Then I'd give it a command name and a description, such as Adds 2 numbers together. Next, I'd correct the class name and then I'm free to implement the command inside the body of the run function. So taking a simplistic approach, I might do something like storing into a temp variable called x, the Parsed value of the first argument I received, and then doing the same thing with the second argument. And now I can return a console result that is the sum of those two numbers converted to a string. And just like that, I've extended my application with another command. Now granted, performing addition may not be the best use of a server, but it does give us another command to work with. Let's give it a quick test. If I run the application now, I can open the console and enter a command to add the numbers 4 and 3 together and then get back a result of 7, a demonstration of the awesome power of .NET, I know, but what's significant is that the result came from the server, which means that rather than doing addition, I could just have easily been purging files or modifying database records. Now let's implement this command inside of our node project. In this case, I can attach a new property onto our _commands object for Add and then instantiate a new Command object and I'll give it a description of Returns the sum of two numbers. And finally, I can specify an implementation function. Now inside of here, I'll do basically the same thing as before, parsing the first argument into a temp variable called X and then the second argument into a temp variable that's called Y. And then I can calculate their sum and return the result to a string. Finally, I can return a CmdResult object passing it the sum. Now let's just verify that this works as well. So this time I'll try adding two decimals, 3.5 and 2.2, and as you can see, we're getting back the correct result of 5.7. So it looks like we're good. So now let's write the help command and we'll follow the same process as before where we add a help property to the _commands object and then instantiate a new Command object. And I'm going to give this a description of lists available commands. Now I'd like this command to list available commands and their descriptions and because we're using a monospaced font, we could do all the formatting using just white space if we used a CSS style to preserve it such as white-space:pre; however, I think I'd rather use an HTML table. So I'm going to begin constructing the markup for a table and give it a class name of webcli-tbl, so we'll be able to style it a bit. And then we'll need to write out our rows at some point and then append a closing table tag. Finally, we'll return a CmdResult passing it the markup string we built and setting isHTML to true specifying that we're returning HTML content instead of plain text. Now before we write the table rows, let's add a few style rules to our CSS file to help us style this table. So I'm going to open the webcli.css file and then paste in a few table rules. By default, I'll have the text color be green, the cells will have two pixels of padding on the top and bottom, and five on the sides with the exception of the first cell in each row. It won't need any left padding. Then I'll just define a couple color rules for things that I want to designate as being a label or a value, so nothing fancy. And because I want our two projects to stay in sync, I'm going to copy this updated webcli.css file from the node project to the .NET project. Now let's finish writing the help command. I'll use the Object.keys method to get an array of properties on _commands and then loop through them and I'll store the cmd object in a temp variable called cmd. Now I can start actually building the row. The first cell will be styled as a label and contain the key, which is the command's name. The second cell will just hold a colon and will act as a separator. And the third cell will be styled as a value and this will contain the cmd.help description and that's all there is to it. Let's try it out. Okay, so it's basically working, but there are a couple small problems. First, the command names are being displayed in all caps because that's how the _command properties are written, but I'd rather they be displayed in lowercase in the help. The more important problem though is shown in the row for the echo command. There's actually a word missing from the help description. If you take a look at it in our code, you'll notice that the word token is surrounded in angle brackets, which are, of course, special characters in HTML, and therefore, we need to escape them. So at the top of this file, let's require the escape HTML module that we added to the project at the beginning and this can perform HTML encoding for us. Now we can fix the help command. First, I'm going to create a temp variable called name and store the escaped key string that I wanted converted to lowercase. And now, I can use name in place of key and also just make sure to escape our help description. And with those changes in place, I can re-run the application, and now the help command is giving me the output that I wanted. Let's now implement the help command in our .NET project. Because the help command will need access to the list of available command types in our WebCLIController, I'm going to make it in our custom console command attribute public static readonly properties. And now I'm going to just drag a completed help command into the project so that you won't have to watch me type it again. As you can see, it's building up an HTML markup string for a table just as in the node implementation and then it loops through the available command types stored on our controller. For each type, I attempt to read the console command attribute from it and test to make sure that I in fact was able to. Once we have the attribute, we append the markup for the row using the HTML encode method to escape the command's name and its help description. Finally, the closing table tag is appended and we return a console result passing it the markup string and specifying that the result contains HTML. I can now run the application to test it in .NET, and as you can see, it's working just fine. Now of course, we could continue to improve this, possibly allowing an optional parameter to specify a command name to give detailed help for, which we could store on the command object or the attribute, but I think what we have will suffice. Another nice touch might be to modify the greeting message inside of our console control and here you could add a line to inform users when the console first opens that they can enter help to get a list of available commands, but I'll leave that as an exercise for the viewer. As far as displaying help for client-side commands, such as clearing the screen, one option would be to include them as server commands with no implementation so that they get included in the list of commands shown by the help. In .NET, that might look like the following. So here, I've created a class for our client-side CLS command that implements the IConsoleCommand interface and that is decorated with the console command attribute just like I would if it were a server command. The difference though is that I don't actually bother to implement it and this is because it should get handled by the client. The only purpose for creating the class is so that it will get listed by the help command. We can accomplish much the same thing with our node server by adding the client-side commands to the _commands object with no implementation function. And with that done, if I launch the application and run the help command, I can get a list of both the client and server commands together.
Handling Errors
When a command is executed, sometimes things go wrong. So I wanted to speak briefly about how errors are handled by our console code. Say for example, I wanted to use the add command on our .NET server to add the number 5 to a duck. Now there's no way to parse a duck into a double and then add it to another double, so obviously, this will cause an exception to be thrown and the client gets sent a very generic error message about invalid syntax. This happens because we're running the command in a try block and if any exceptions are thrown, we catch them and return a default ConsoleErrorResult. And much the same thing happens if I were to enter in only one parameter to the add command, there are two, an exception is thrown and the default ConsoleErrorResult is returned. As a side note, if we did the same with our node server, JavaScript is perfectly happy to add numbers to ducks and inform you that the result is not a number, which is our goobly true, I suppose. But in the event of an exception, our node server returns a generic error message much like in .NET. And this is actually pretty good because what if we weren't just adding numbers to various farm animals on our server? What if we were performing an operation on a database or performing a security test that failed? If we were to report the details of those errors back to the client, an attacker might be able to better understand how our software was constructed and then craft more targeted attacks against it. So not returning specific error messages to the client is probably a good idea in the case of true exceptions. But what we have here isn't really an exceptional circumstance. We can predict that someone might enter the wrong type or number of arguments to a command. So we could improve the user experience for those cases by testing for them in our command and returning an error that explains to the user what needs to be corrected. There's no danger in that. So let's quickly write a test for an invalid number of arguments in both the add and echo commands. In the case of add, I'll test if the args.Length is not equal to 3, and if it's not, I'll return a new ConsoleErrorResult object with a message of exactly 2 operands required and that's it. Now for the echo command, if we didn't get at least one argument beside the command name, I will again return a ConsoleErrorResult and give it a message of I didn't hear anything. Let's now add the same checks to our node project. In node, the code is almost exactly the same, except that I didn't bother to create a distinct error type, but it's not necessary. So again, in our add command, I'll test if I don't have three arguments, and if not, I'll return a CmdResult object and give it the same message and specify that isHTML is false and isError is true. And it's the same process for the echo command. If we didn't get at least one argument besides the command name, I'll return a CmdResult saying that I didn't hear anything. IsHTML again is false, and isError is true. And now if we run the application and we pass the wrong number of arguments to either the add or the echo command, you can see that we're getting much more useful error messages back. So I would encourage you to try and anticipate the potential problems that can occur with your commands, and in those cases, return custom error messages that guide users in the right direction, but that don't reveal any implementation details about your code. And then for real server exceptions, I'd suggest logging them, but never sending the messages to the client.
Long Running Commands
So far, all of the commands we've run have completed quickly, but what happens if the server takes a while to send a response to the client? Well generally, you'll want to try and design things so that your commands all execute quickly, but the reality is that sometimes servers get overloaded or network traffic slows down communication. So even commands that should run quickly, won't always do so. And we can simulate this easily in .NET by editing our echo command and adding a line to suspend the current thread of execution for 6 seconds before returning a result. With that in place, let's now see how our console reacts to this delay. So I'm going to run the echo command, and while I wait for my result, notice that I can still type into the console and enter in additional commands and get their results, and then once the 6 seconds elapsed, the result from the echo command was eventually displayed. Now perhaps you like this behavior even though it can be confusing which result belongs to which command when the results can appear out of order. However, once a command is submitted, I would prefer the console to block further input until the command completes. And I'd also like to display a busy indicator to show that the console is busy waiting for a command to finish processing. So let's head back to our WebCLI JavaScript class and add an additional element to our console that can serve as a busy indicator. First, we'll want to navigate to the createElements function in order to add the element to the control. Okay, so let's add an additional element down here and store it as the busyEl. The type of element is going to be a div and this is going to serve as our Busy animation. Next up, we'll need to assign it a class, so let's give our busyEl a className of webcli-busy. And then finally, we'll need to append the busyEl onto our console control element after the output and inputEl. And with that, our busyEl should now be part of the console, but it will require some styling in order to look like anything. So I'm going to open a browser with Live Server again to assist me in viewing the busyEl while we style it and I'll just size these windows a bit to give me some more room to type. Okay, so let's now open the webcli.css file and begin styling the element. So I'll begin by copying this comment and then creating a section down here for our Busy animation. And now we can write some rules for webcli-busy. And first up, I'd like to set its dimensions. So I'm going to give it a width of 24px and also a height of 24px, and then I'd like to give it a top border that is 2px thick solid and that has a light blue color since I've got kind of a blue theme going on here. Now what I'm looking to make is one of those arc shapes spinners, so in order to get that shape, I'm going to give my border a large radius of 50% and you can see that gives me sort of a half circle, but rather than leave it like that, I'm also going to give this element a right border that is transparent, which sounds redundant, but it's not. Because of the way the borders blend into each other, this will shorten the right-hand side of the arc so it's no longer a complete half circle, which I think looks better, particularly, when the spinner is animated. Speaking of which, let's create a keyframe animation that we'll call spin and this will be a very simple animation to transform our element by rotating it 360 degrees. And now we can apply this animation to our busyEl. So I'll say that I want to use the animation we just made called spin and I'll give it a duration of .6 seconds, have it animate linearly, and then have it loop infinitely. And that gives us a decent-looking spinner without having to write too much code. I think the only thing I want to add now is a little vertical margin on the element so that it doesn't display too close to our content. So now we have an animated busy spinner inside of our console, but we need a way to hide it when it's not needed. So let's jump back to our JavaScript and add a function called busy that will take a Boolean as a parameter to specify whether the console should be in a busy state or not. I'll begin by creating an isBusy property on the class that will store the Boolean and then I want to set our busyEl.style.display property based on that Boolean. If true, the busyEl should display as a block, but if false, it should have a display of none. And then I want to do the opposite for our inputEl. When busy, it should have a display of none, and when not busy, a display of block. And now, since we want the console to not be busy when first created, let's jump up to our constructor and add a call to our busy function to specify that initially the console should not be busy. When the console is busy, I want to be extra sure to block input, so let's navigate to the onKeyDown function. Now I want the Ctrl+Backquote handling to always be active, but if the console isBusy, I'm going to skip over the processing of any other key events. Finally, let's navigate to the runCmd function. Now just before sending a request to the server, I'm going to set the console's state to busy, and actually while I'm here, I want to add a newLine after a text result is displayed because I've noticed that I prefer to have some white space between my commands. And then after the request is sent to the server, let's explicitly remove focus from the inputEl. And then I'm going to add another function to our promise chain, and because this function follows a catch function, it will act very much like a Finally block because any errors further up the chain will get handled by the catch and then continue on with this then function. So regardless of the request's success or failure, the code inside of here will get executed. And inside of here, I'm going to set our busy state back to false and then return focus to the console. Let's give it a try. So if I now run the echo command, you can see that our busy spinner is shown, which you may not see is that I'm furiously clicking and typing into the console, but nothing happens because we've blocked input. I can now only enter a new command once the previous has completed. So now that we've tested the handling of long-running code, there's really no reason for the echo command to be taking 6 seconds. So let's head back there and remove the sleep code from the command. And finally, I want to copy the webcli JavaScript and CSS files from the .NET project into the node project so that they both have the updated version of the console control.
Additional Server Commands
At this point, we've built a functional foundation that can be used to quickly add new commands to an application. Unfortunately, I don't know what database you use or even what your application does, so only you know the specific commands that will be useful in your scenario. Still, I'd like to spend a little time extending our demo with a couple more commands that are generic enough that hopefully they'll be of use. To our node server, I'll add a command called status that can return some basic server information such as the amount of RAM available and the machine's uptime. And then to our ASP.NET server, I'll add a command called Diskspace that can display the amount of free space on one or all of the systems drives. Let's begin with our node project. The first thing we'll want to do is require the OS module at the top of our code because it contains the functions that we'll want to call in order to get information about the OS. Then I can scroll down to where our commands are and make a copy of one of them for our new command giving it a name of status and then a description of Displays server status info. And now I'll begin the command by creating a variable called freeMem, which will store the result of the os.freemem function, which will give us the amount of RAM available in bytes. And then I can convert this to megabytes by dividing it by 1024 twice and then I can pass this whole expression to Math.floor so that we'll get back an integer result. And now I can make a copy of this and repeat the same logic for getting the total amount of memory that the system has. So now that we have our memory values, I know want to calculate the machine's uptime and break it down into days, hours, and minutes. So I'll create a var to store the results at the upTime function, which will tell us the number of seconds that the machine has been up and running. And now I'm going to paste in some boring conversions to get what those seconds break down to in terms of days, hours, and minutes. Here's a quick guide of the conversions that I used, 86,400 is the number of seconds in a day, 3600 is the number of seconds in an hour, and of course 60 is the number of seconds in a minute. And now I'm going to use those values to store a string in uptime that we can use to display the breakdown of the days, hours, and minutes. And now, let's build the string that we'll be sending to the client. So we're going to report the freeMem out of the total available memory in MB and then follow that up with the machine's upTime. And finally, we can just return a CmdResult passing it the string that we just built. Now let's start this server and give this command a try. So if I run help, you can see that the status command is being listed along with the others, and if I run it, you can see that my machine has around 4700 MB of memory free out of 8 GB and the machine has been up for about 11 and a half hours. Now let's switch over to our .NET project and write the Diskspace command. I'll begin by copying an existing command and then renaming it to DiskSpace. Next, I'm going to add a using statement for System.IO since we'll need that in order to get information about the systems drives. Now let's fix the class name, as well as the command name and then give our command a description of Reports the amount of free disk space in bytes. Alright, now I'll just clear out the old commands code and we should be good. Let's begin by getting a collection of system drives by calling DriveInfo.GetDrives and then I'm going to create a result string that will be built up inside this function and that will ultimately return to the console. Now if a drive argument is specified, we'll only want to return results on that one drive, so let's test if we received an argument other than the command name. If not, we'll show info on all the drives. So if a drive is specified, let's grab a single drive from the drives collection whose name when converted to lowercase matches the first argument converted to lowercase and the string to return will be the result of a call to the GetDriveSpace function that we haven't actually written yet. So let's ask Visual Studio to stub out that function for us. Now in the case of showing all drives, we'll loop through each drive in the collection, and append the result string we get back from calling GetDriveSpace for that drive. After that, we'll want to pass the result string to the console result object that we returned to the client. Now all we have to do is implement the GetDriveSpace function. So I'm going to create a format string for what we'll be returning and it will be div that I'm going to style with an amber color and I'm also going to use white-space:pre this time as an alternative to using a table. And we'll just display the Drive name followed by the amount of bytes that it has free. Now all that's left to do is to return a call to string.Format passing it our fmt string and giving it the drive.Name as the first argument and the drive.AvailableFreeSpace as the second and that should do it. Let's give it a try. So once again, a call to help shows that our new command is being listed and if I now run diskspace on my Z drive, you can see that it has I don't know something less than 4 TB free. And then if I run diskspace again with no arguments, you can see the number of bytes free across all of the drives on this system.
Summary
In this module, we added some improvements to our console beginning with adding basic help functionality, allowing users to get a list of available commands and their descriptions. We then took a look at error handling and learned that it's a good idea to anticipate common user mistakes, and when possible, give meaningful error messages in response that guide users in the right direction while taking care never to give attackers valuable information about your software. Next, we improve the console's handling of long-running commands by blocking input until a command completes and displaying a busy indicator to the user to let them know work is being done. Finally, we added a couple more server commands to fill out our demo that allow you to display the server's memory usage and uptime in node and to check the diskspace available in .NET. Now it's time for you to take what you've learned and build some commands specific to your own web projects and needs. Maybe you need a command to perform a database operation, start a job, or clear out a memory cache. You've seen just how quickly the console we've built can be extended with new commands, so be creative and try things out. My hope is that you'll be able to build upon the examples shown in this course and create something even better. I found the developer console to be a huge timesaver in my projects and a lot of fun. I hope you will too. Thank you for watching.
Course author
Carlos Saloio
Carlos Saloio has been programming since 1998 across a wide variety of areas, ranging from 3D graphics engines to line-of-business applications. Currently, his focus is on creating modern web...
Course info
LevelBeginner
Rating
(40)
My rating
Duration2h 34m
Released18 Aug 2016
Share course