Layout and Fonts
One of the first things that we have to do is to visually structure our application, come up with a layout, and for me, at least, this can be a difficult thing because every time I start writing an Electron app I tend to start thinking in terms of the web. You're going to hear me say this so many times in this course, but we are building a desktop application, not a web application, so there are certain things that just don't belong. For instance, whenever I work with a web app, I usually choose Bootstrap or Foundation as a way to at least get started, and typically I keep them in there. It's because they take care of the things that I just don't want to think about, such as responsive design. Does responsive design belong in a desktop application? If you haven't already thought of that, well you better because believe it or not it doesn't. Consider Slack here. The reason I'm showing you Slack, and I'll be showing you a few other applications, is because they have already thought through these things. They have teams of UI experts that sit down and think about the best possible layout for their application and how to make things easier on their users. These things have been AB tested with beta users and so on, so you can kind of take stock in what they have come up with. For instance, look at this. When I move this window, notice that I get to a certain spot and it stops. This is the minimum height and width for this application. When was the last time you coded something like that for a web app? Typically, you would want responsive design to shift everything so it works on mobile. Mobile is not a concern of ours. We have a desktop application. What is a concern is that we maintain the visual integrity of our app. What I mean by that is if it's this small, let's say I'm working on laptop and I want this in the corner, it's still sort of useable. I can go through here and read what the Slackbot has to say, but if I expand it out, it sort of looks the same. I could still go through here. Notice that this menu did not change at all. This menu needs to stay exactly the size that it is and same with this primary menu on the left side. So that's Slack. And by the way, Paul Betts, one of the people who helped create Atom in Electron, is one of the primary developers behind this app that you see here. He works at Slack now. So let's take a look at one of his previous applications, which is the GitHub desktop. And if I take this and do the same thing, look at that, same exact deal. I don't have any reflow. I don't have any responsive design concerns in here. It is still useable at this size, although barely. It's not meant to be used at this size. But on the menu over here, look at if I move this, the menu moves a prescribed amount, which I think is interesting. Let's take a look at another application, which is the Atom text editor itself. You'll notice that these layouts are all following the same kind of pattern. On the left side here we have some kind of global menu, if you will. This is just a file tree. It doesn't make any sense to have anything other than that. And then you have a main viewport, if we want to call it that, where everything just happens. Down below we might have some kind of informational status thing, or maybe that'll be up above. If I move this to the right, notice that it just becomes unusable. It doesn't stop, which I think is fine, but notice that there is no resizing happening, no wrapping of text. Everything has a standard place to be. Okay, well let's go back over to Slack here because this is the layout that I'm just going to use. I want to have a small menu on the left side right here, and then I want to have a bigger menu on the right. Now your application might not even need that. It might just need some icons here on the left. We'll talk through all of that. At the top, you'll notice that you have another menu, a top menu, and these tend to be context switching things or little utility things like Search. If I want to open up some informational stuff, I can. Stuff pops open on the right side over here. And then down below is primary functionality. Now, depending on the kind of app you have, this kind of layout might not be what you need, but either way this layout, this cascading layout right here, follows general design principles. And more than that, if you're into math, you'll notice that the width right here, and the width right here, and the width right here are all connected to the golden ratio, which is important. These visual things really lend themselves to a good user experience. Right now I am going to focus on the simplest possible thing. I have made many desktop applications in my life, and every single time without fail I have realized that if I keep things bleedingly simple it really helps because desktop applications change quickly and with respect to the way they look, so that's what we're going to focus on next.
Starting from the very beginning, I'm here at the electron.atom.io website, and if you scroll down to the bottom, you can see Getting started, and they suggest that you clone the Quick Start application, which is exactly what I've done. I have cloned it to my desktop here, and I have it opened in the Atom text editor because it's only fitting, isn't it? I have reset the title to be Vidpub. This is our fictitious organization. I've added two style sheets, app.css, which is currently empty, and then I have also added a reference to font-awesome because I like using fonts and symbols. In the main body here, I have simply just put a little blurb. The last thing I want to show you is main.js. This is the bootstrapper for Electron. And the only thing I've changed in here is to set the window to 1280 x 720, which happens to be the same size as our video viewport here. So I have the application running, and this is what it looks like, The World's Leading Video Tutorials. Hooray! No fonting at all, and that is exactly where I'm going to start. Now you might think hey, why don't you start doing the layout stuff; we'll worry about fonts later? And it's just the way I roll. To me, fonts are the look and feel of your application. They are massively important, and it's also a relatively easy decision to make. What we can do is just rely on the basic system fonts that we have at our disposal. So, depending on what operating system you're targeting, you can just use the system fonts. Is that a good choice? I think so because you're making your application look just like a desktop application that the user is going to understand and recognize. What are those fonts you ask? Well let's take a look. I'm going to crack open the app.css here, and what I'm going to do is I'm going to step through the various fonts from various operating systems, and I'm going to specify the font-family. And if I am using OS X on the latest version of Yosemite, the system font is one called San Francisco. The problem though is you don't specify it like this. It is something like SF UI DX, or something like that. Another one, another specification is SanFrancisco, if I could even spell that right, SanFrancisco. Well, I'm not even going to try it. It won't even work, so the reason why is that the only font or, excuse me, the only application that is allowed to use that font outside of a desktop environment is Safari. There is a specification in there. So, we are not using Safari; we are using Chrome. So, they used to be all Webkit. And it doesn't matter. It's just one of those things. What I can use though is Lucida Grande. Lucida Grande was used in Mavericks and in previous versions of OS X, and also in iOS, so I can specify that. If Lucida Grande is not present, I can also specify Helvetica Neue. Helvetica Neue is an older system font used a lot in iOS. So whether it's appropriate for your app, well I'll leave that up to you. It's a really nice looking font though. But I'm going to take this out because my next choice is going to be something for Windows, and I'm going to choose Segoe. Segoe is one of the Windows fonts. And at this point we have an interesting decision to make. We have a default OS X font, we have a default Windows font, so our app should look good in, well, versions of Windows and Mac let's say up until three or so years ago. If we want to support Ubuntu or some flavor of Linux, we can do that next by just using the Ubuntu font, which is given out with Ubuntu. But if we are not working with just only Ubuntu, we want to support other versions of Linux, we might want to choose Cantarell. That is found in a lot of the Linux-based desktops. And then finally we are kind of down into the nethers. If none of those things are around, you could be on an older Windows system, so for that just go with Arial. And if none of these fonts are around, then you're in some weird land. Just go with sans-serif, and this will keep you covered as far as fonts go, at least that's what I found. So let's take a look at how this looks, so I'll say npm start. This is going to fire it up. And here we go. And given that I'm on a Mac, you're seeing Lucida Grande. Now this wouldn't be my very first choice for font if I was on the web, but given that I'm on a desktop application, I think it's really, really important that you go with just system level fonts and make it easy on yourself.
Creating a Basic, Custom Layout
As we're playing with the structure of our application, well, refreshing every time is going to be a bit of a pain, so I found a little module that I really, really like and I'll share it with you, it's called electron-reload, and I'm just going to install it right now and say save. Now what this is going to do is every time I change a file, it's just going to reload the UI, and it doesn't do anything tricky like LiveReload does, but we can't use LiveReload because we're not using a browser, we are inside the Electron runtime. Anyway, we're going to need to use electron-reload. So to get this thing to work, I'm just going to come in here and right there I'm just going to require electron-reload. And then I need to tell it which directory to watch, and I'm just going to pass in the current execution directory, this root directory of our application. Alright, so if I start our application again, npm start, we get a weird error, I get this all the time. Electron could not be found. Maybe it's just a bug in the app. I've tried to get rid of that message so many times, but anyway, it works. So showing you that it works, if I come over here to index.html, The World's Leading Video Stuff, why not? Well, you can see it just reloaded for me. It's a convenience thing if nothing else, but it will be helpful when we start building out the structural look of our application.
A Simple Layout
Let's put the structure of our application together, and this is where typically you might reach for something like Foundation or Bootstrap, relying on some of their default templates. And you can do that, however, from my own experience it actually costs more time and trouble to do that than to hand roll your very own, at least external structure, meaning the outer kind of bits if you will. I know exactly what I want this thing to look like in that way, so what I'm going to do is I'm going to just keep things bleedingly simple. Now I can refactor this later on, but I want the style and the stuff to be on the exact same page. And I know that what I want is I want to have a side nav, so I'm just going to call this side-nav, and I also know that I'm going to have a main or just the content area, so I'm just going to call this main for right now. And I'm probably going to want to do a min-width so that when resizing happens things will reflow, but for now let's just see how this looks. Well I know how it's going to look, it's going to stack because the default layout for a div is block, and so they're going to stack one on top the other. So for the side navigation I wanted to have a black background and kind of just be pinned up to the left side. There's no hardened fast rules here, we just need everything to look pixel perfect, and that's critical, more so than for a website because this is on your user's desktop, and if it looks clunky, they're going to uninstall it because it needs to look good. So for this I'm just going to style up my side-nav. There we go. So this is something else I do a lot of, inline styling. If I don't need a class for it, if I don't need to just kind of use misdirection, then I won't do it, especially for UI stuff, I like stuff to be as apparent as possible. This right here is a little bit much to put into a style tag, so for this, however, I'm just going to just put it right inline, list-style:none. So our app is still up, and it should be refreshing on us, and indeed it is. Ew, it's not exactly what I want to see. So it looks like we're having some problems with the margins, so let's set the margins and padding to 0. We'll come in here to the body and I'll just say margin: 0, 0. And so our app should be refreshing itself, and yeah, nothing's happening. So this is one of the problems where you don't use one of those fancy CSS frameworks as you are confronted with your own lack of ability to do anything CSS related. Oh my goodness. Well, I just happen to know where the problem is because whenever I've started working with floats, I always have a problem and so I'm pretty sure I've seen this before, and it is because I am floating one thing and not floating another. If I want everything to float together, I need to make sure they all, well, float together. So if I set main to float: left, yes, good. That's kind of cool. Okay, well rather than walk you through all my horrible CSS thrashing about, I have a pretty traditional two column layout here. Just floating a couple of things, wrapping it all up, making sure that height is set to 100%, min-width 800 pixels on the wrapper itself. A pretty simple layout, pretty simple styling, not much to it. And if we go take a look at the harness, we want to call the harness this, the structure of my application is exactly as I want it to be. I don't want to allow for any responsive layout. That flows exactly as I like. We have a pretty good structure to move forward with. I even have some nice icons on the left side here that are, well, pixel perfect, at least where I want them to be.
I have a pretty straightforward application in front of me, and the layout is pretty good. My left bar here looks pretty nice, I have a top bar, and I've got content laying out in the middle fairly well. However, I have some weird scrolling issues, and some height issues, and there are things that will probably crop up later on as I add content here. Working with floats can really be a pain, and this is usually when people, when they build a website, will turn to something like Bootstrap and just go with a grid system, or Foundation, or whatever it is you're in to, or they'll call their friend who knows CSS. Let's take a little bit more of an extreme look at how we can do this differently. What I'm going to do here is I am just going to flip over and start using absolutes, because, and I have a mandate for this, because my app needs to be pixel perfect. That's what I want it to be, pixel perfect. And since we have a layout with a behavior that we understand and know, going with absolutes is just fine. We don't need things to flow over. In fact, we want to have that rigid control. So that's what I'm going to do. And I just wanted to show you it's not as scary as it might seem, believe it or not. So in here, in my side-nav, right here you can see I'm floating things, I'm floating the main to the left here. The side-nav is also being floated to the left. I am going to take that completely out. I'm going to take out that as well. I'll leave the background color, the padding, and the width exactly the same. So here I'll just say position: absolute. And that means that since I've done that, and I want it to stretch from top to bottom, I need to say top:0, bottom. So let's go take a look at that. It'll refresh and you can see it's stuck over here on the corner, as we want, and everything has flowed into it, and that is to be expected. So the next thing I'll fix is the top-bar, and the top-bar is down below here, so let's take this and I'm going to put it right next to the side-nav. We can take out our fit-content, and we can take out the float:left. Text-align: right is fine, padding is fine, background-color is fine, height:20 is fine, position: absolute, and here we are going to say that the left margin is going to be the width of our side-bar, so that's going to be 65 pixels. And the top should be stuck to the top at 0, and the right should be stuck to the right at 0. Flipping back, well hey, that looks good. It is positioned exactly as we want it to be. The only thing that we have that's kind of messed up right now is our main area. And one of the things that I always find useful, and man, it's been a while since I've had to do absolute positioning, is setting the border, just so you can see if everything is flowing the way you want it. So if I set this to 1 pixel solid blue, you can see of course it's not, but what I could do here is remove the float, and the min-width is fine, and I am going to again say position: absolute, and the left side is going to be 65 pixels. Top is going to be, let's put it at 30 pixels just to see it, where it's landing, and the bottom is going to be 0, and the right is going to be 0 as well. So here it's flowing pretty well. The right is kind of right up on the edge of the developer tools. We have a little bit of a pixel buffer in there, but that's just the border of the tools. I think that's okay. So let's bring this down a little bit, I want it to match up exactly. Now the height here is 20 pixels, right? And so we also have some padding in there, and coming up here you can see a padding of 12 pixels, so that's top and bottom. So that's 24 pixels plus 20, so that's 44 pixels. So here let's set this to 44, and there we've got it. So that matches exactly. Math, isn't it fun? If you find yourself doing math in your layouts, that's a good thing. You really want as much control over this layout as you can possibly get. Notice my scrolling issue is gone, although I am on a Mac so it's kind of pushing a little bit. That's okay, that is by design, but anyway, yeah. And if I check my flow, the type is now wrapping, which is good, that's fine, but it's also, the window is also stopping. And if I pull this over just a little bit, actually we can close this for now, this is what my app looks like, and this is exactly pixel perfect what we want to see.
We're getting down to some pretty exact layouts. I like it man. I like using math and telling HTML what to do. It makes me happy. I don't have to think about things flowing around. Well anyway, since we're using math to dictate exactly where elements are on the page, why stop? Let's add some science, shall we? So, I want to play around with the Golden Ratio, Phi, 1.618. If you don't know what this number is, prepare to have your mind blown. It's one of those universal numbers if you will. It's like Pi, except Phi is the ratio in the natural world between two things. I know it sounds weird, but it defines beauty in a sense. That's why they always show these pictures. But like here in the model, the distance between the nose and the chin is in Phi ratio, if you will, to the distance between the chin and the top of the head. If you look at your own arm, your elbow is in Phi ratio, if you will, to the ratio between your forearm and your entire arm. It's crazy. There is so much in nature that's governed by Phi. Well, I invite you to go take a look at that. But we can use it here because our layout is a little bit funky looking. But how would we actually apply it here to our layout? Well, what I'm going to do is I'm going to just go with when the window opens it's 1280 pixels wide, what I'd like is for the entire page to kind of comply with that. I know people can resize the window. That's okay, I'll let them, but I want this bar to be in a Phi ratio with the entire window, same with the top bar, and same with the layout here. So how do we do that? Well, calculators to the rescue. What I could do is I can just find the ratio of my window in terms of the Golden Mean or the Golden Ratio. If I use the reciprocal, 0.618 times 1280, I get 791. So that is roughly one-third across, as you would expect, so it's right around here. If I was to make a shaded area 791 pixels wide, well it would look naturally pleasing to your eye. Well obviously I want my bar over here to be less than that, so I'm going to multiply this number by 0.618 again, and then I get 488, that's still too wide. I can do it again, and I get 302, still too wide, .618 again is 71.3. So now we're down to something that is in ratio to the entire window, 71.3. So what I can do is I could just change this now. The side-nav can be 71 pixels. And I can come down here, top-bar, left-side needs to be 71 pixels, and main needs to be 71 pixels as well. So if you flip back over here, ah, isn't that so pleasing? I know I'm a bit of weirdo. But actually it's supposed to be naturally pleasing to the eye. I think it looks nice. What about the top bar? The top bar looks a little bit funky, doesn't it? I think it does. Let's actually bring this down. This is going to correspond to 720 because that is the height of the window, so I want it to look natural according to that. Let's bring up our calculator again, shall we? Forty point one pixels, it looks good to me. So let's go back in here to the top-bar, 40 pixels, and we'll have to adjust a few things, but that looks nice. That looks much better. Well now what we want to do is we want to make sure that the padding here between the top and the side for our main content area is at least in some form a Phi ratio. So what I think I'm going to do is I'm going to take exactly half of this and apply it to the padding here. So our side-nav is 71, so half of that is well, right around 35, 36. So let's go down to main. Alright, there we go. I think that looks a lot better. Much more pleasing to the eye, don't you think? We have nice pixel padding here that is in ratio to our left side, and the top is in ratio to the bottom. I think it looks great.
Mac Layouts with Photon
In this module we're going to build out our desktop to look like a true desktop application, bound by the design guidelines of our operating system. I'm going to be focusing on the Mac experience, but I am going to show you Windows components too that you could use. One of them, we'll start right there, one of them is the React Desktop, and well with a lot of things with Electron, it's in early Alpha. So React Desktop could be useful, however, my opinion here is using React with Electron is overkill. I don't think you need it. You'll see what I mean later on as we start doing rendering with Handlebars and a few other things, but if you're a React developer and you really like it, check it out. Here's a demo. It's kind of neat. You can switch between Windows and Mac. So if we switch over to Mac, you can see, well, it's very Mac looking. A little bit off, but it's getting close. Anyway, as I mentioned I am going to be focused on the Mac desktop. I'm assuming that if you're doing Windows you're either going to be using Windows software, and .NET, and so on, for ease of installation. However, if you're using Electron app and you want to go cross-platform and develop for Windows as well, well see, since you're going cross-platform you're probably not going to go with a dedicated look and feel of the Windows operating system either way. If you're building something for the Mac, which I assume you probably will be, then you might want to go with something that looks a little bit more like the Mac desktop. Now this is all CSS and HTML. As you can see it looks pretty close. So if you find yourself in a situation where you need your desktop to look like the operating system, well you could use something like Photon. And that's what we're going to be plugging in in this module.
Plugging in Photon
Tweaking the Layout
One thing I really like about Photon is that you get an awful lot with very little HTML. So taking a look at what we actually built out here, and then taking a look at the HTML, it's pretty slight. I think that's kind of neat. We just have a table, and this is our grid, and then we just have window-content, pane-group, some panes, and then up here, everything goes inside of a div with a class of window. That's easy peasy, and I think that's pretty neat. So as I mentioned though, I don't want all of this. I don't want something that looks like a Finder, so let's actually go back and take a look at the Getting started page here. And it tells you what's included, blah, blah, blah, blah, blah, but if you go on down, you can see that if you want to just have a simple application layout, you can. What I'm going to do is I'm just going to copy each thing and drop it in here. So, right here in the body, I'm just going to paste that in. And then if we go back to our app, and then hit Refresh, you can see nothing except for our little dots up here in the corner. So this is just a simple application surface, and if we want to then build it out from there we certainly can. And I am going to go back and pick one of the ways that I want this to look. I like the Mini panel over there on the left side so I'm just going to take that, and I'm going to copy it in, and there we are. So going back and then refreshing, good. This is exactly what I want to see. Now if I want to style things I can just follow the guidelines on Photon of what classes to use. I don't have to do any of the guess work with trying to figure out how to make it look like a Mac desktop, it's already done for me. I just need to figure out which elements go where. So for instance, I might want to have a header, and a footer, and this is why they call this the common layout. Sidebar is actually a little bit wider so let's go with that rather than a smaller sidebar. And I'll head back over to our code, and then I can just pop that in just like that. And then refreshing, boom. It looks pretty good. The header is a little bit redundant because we already have some chrome at the top so I can actually take that header out, and if I refresh this, that looks pretty good. Alright, well I think we could easily go with this and build our application on top of it, lay in the menu list right here, and then fill out what we need inside of the grid. To show you what I mean, right here we just have these guys, and a simple message that says, Welcome to Vidpub, with a nice title, and you can see if I refresh, that looks pretty nice. That fonting is very, very nicely done. Alright, well that is a quick look at Photon.
Handlebars View Engine
So to start with I just want to show you that this works. Let's do the simplest possible thing and navigate to a search page. And here is the project. And I can go up here, New File, search.html, and in here I can just put an h1 and say search. And in here in index.html for search, I can just have it navigate to search.html. Now I just want to show you that this works. It's not necessarily a good idea. So if I click this, boom! We are on Search now. And notice that all of our lovely HTML is gone, which is okay. What I can actually do is I can go back over here, and I can copy, and I can paste, and I can take out main, right? Just like this. And I can put in Search. And having done that, well you can see now Electron Reload reloaded our site for us so I can navigate. Oops, I can't do that so let's go back over here and we will navigate to home. Okay, this is the search page, we're going to navigate to home. And so if I come up over here and click this, what happened? Well, this is actually navigating to the root of your site, or the root of your machine, I should say. It's not going to the root of your site. What you actually need to do is you need to put in the exact reference to the page you want to go to, so index.html. If I refresh this, you'll notice that nothing loads. So, I need to reload my application, and hopefully we have done things in a better way. So if I go here and then I go here, good. But notice what's happening, it's blinking. This looks like a website, doesn't it? This is not the interaction you want. If it is the interaction you want I would push you just to make a website. We're here to make a desktop application. So this whole transgression right here is just to show you that this will work. You can navigate between pages. Is it a good idea? I certainly don't think so, so let's make this better.
This might look a little bit crazy to you, but trust me, simple wins the day. I am going to install Handlebars. And you might be thinking why did you chose Handlebars? That's because I like it, and I know how to use it, and it works, and it supports partials. These are all the things I want to work with as I assembled my views on the back-end. The other thing I want to install here is Underscore because I really, really like Underscore. So we will save that down. And this is just about all I need. Now I can get trickier if I want to. You might see me do that in just a bit, but for right now I have Handlebars and I have Underscore. And so what I really want is I want to create the notion of a view. So, here in my application, taking a look at it I've sort of assembled it as if it is a web application. And that's okay. I think it's okay to go along certain conventions, css folder, fonts, image, as long as in your mind you are clear that you're not building a web page. For instance, I am going to create a lib folder, and inside the lib folder is going to be some Node code, and inside of here I am going to simply create view.js. This is going to be a node_module. My view.js file is literally going to be a view that gets rendered by Handlebars, by pulling a template off disk. This sounds a little bit crazy, doesn't it? But it makes perfect sense if you think about it because this is exactly how a website works. Why wouldn't you do the same thing with a desktop application that is web based? There we go; it doesn't get much simpler than this. I'm using the file services, or fs module for Node, and path, and Handlebars. And then I'm passing in the name of the view, and inside of here I am going to assemble the path using the path module. I'm going to pull the source off disk, and this is going to look inside the view's directory, viewName, that I'm passing in, .hbs. So I could have views, a search .hbs. It'll pull that text in, and then it's going to be compiled by Handlebars. So what does it mean to be compiled? Well basically it's just going to turn this source HTML into a function that I can then execute when I need to, and that is what this toHtml method is all about. It's going to take in some data, and then I pass that data off to the function. Alright, well this is simple enough. So how would this actually work? Well, let's start by creating a brand new directory called views, and inside of here I'm going to create search.hbs. This is a Handlebars template. And for right now I'm just going to output search, and I'll fill this in later with some functionality. And so what we need to do now is to figure out a way in which this can be rendered. And for that, I am going to actually lean on more of Nodes functionality. Let's do that next.
The App Event Aggregator
All my work, but it's not exactly the simplest code in the world. Let's trim this thing up. First thing I'll do is I'll just put in nav as our class for each one of these elements here. And, there, I'll just put it like that. And what I'll do down here is just have one event, so on .nav .on click, and then in here I will just make sure that I do ev.preventDefault because we don't want to fire an event in there. And then we're just going to say app.emit view-selected. And then what we're going to do is we're just going to say this.id. And then we'll get rid of that. Alright, that looks a little bit better. Now, let's actually make it look a lot better. I'm just going to say showContent, and then function, and then I'll just say view. And that way we can have this app.emit just happen in one place, and good. So then on startup we can just say showContent home. We're getting there. We are getting there. And then here I'm going to say showContent this.id. Good. Okay, well that looks a lot better, doesn't it? Alright, let's go back and make sure that our app works. It looks like it does. Hooray! That makes me feel a whole lot better. Alright, well looking at this I think we can call this good. This is a great harness, and if we've built things the right way, ideally, it should be simple to swap things out. For instance, what if you don't like Handlebars? We're going to take care of that in the next module.
Jade View Engine
We have separated our view logic from our application logic using Node's EventEmitter. We are not relying on any frameworks, which is making me exceedingly happy. So, we have an application module, and that, as I mentioned, is just an event aggregator, so we respond to the rendered event from our application, and we simple replace the HTML inside the main div, right here, with whatever is handed to us from the app. And then we fire events off to that application simply by just saying app.emit, and then view-selected, and then it goes and gets the stuff for us. This is all just dandy. The problem that I have is that I have a brand-new senior developer that just got hired, and well, she does not like Handlebars. That's too bad. Handlebars works pretty well. She happens to be a fan of Jade. So, my first directive for the day is to remove Handlebars, and I can do that just by saying uninstall handlebars. Oh, that's a bummer, but I can understand. Sometimes Handlebars acts a little bit old and creaky. So now that's gone, let's make sure that everything is busted, so I'll just say npm start. And then kaboom, Handlebars is not defined, and our application is broken. That's too bad. Well, let's cut out of here, and I'll just say npm install jade. Now I just have to fix everything. Hopefully it won't be a big deal. So we're going to go grab the Jade view engine. It is a very, very terse, simple to use view engine. It kind of looks a little bit like Haml, if you will. Here it is in action. It's kind of nice, doctype html, head, title, script, body. You can toss code in there if you want to, and then off you go. So I kind of like this view engine myself because, well, typing HTML can be a bit of a pain. Anyway, let's see how hard it is to switch over our little view engine to using Jade.
First thing I want to do is to consult the API, and having a look at the documentation, it's pretty straightforward to use the Jade view engine. I just have to require it. Then I have to compile whatever string it is I'm compiling with whatever options I want to use. And then down below here, I just need to invoke whatever I've compiled. It seems a lot like Handlebars, doesn't it? So, we can kind of leave the options at their defaults. So for right now, let's just go plug in Jade. I will just replace it right here, jade require jade. Boom! Template is going to be jade.compile, and then down below here I'm invoking the template. This is just the compiled function that is returned. So basically it's just turning a bunch of HTML into a function, which is neat. And then I'm just invoking it, passing in some data. So if I've done everything right, I should be able to come over here to our views. Actually, I should just be able to leave them exactly how they are. Let's see if it works. So I'll pull over my console here and say npm start. And, wouldn't you know? It just works. Wow, that's neat. So let's actually see if Jade is really working. I never trust anything the first time I see it. So I'll come over here, and we'll go to home.hbs. I will rename this to home.jade. And good, it means my syntax is off. There you go. And I'll say h1 The World's Leading Video Stuff. Good. Okay, so that is home, and I'm going to replace now, very good. Okay, so pulling over here you can see we have a problem. Now I wanted to show you this because Uncaught Error, no such file or directory, this is showing up in the console, and it's a Node error, which I think is really neat. Anyway, it's telling me it can't find home.hbs. Debugging these little applications is exceedingly simple. It's such a nice tool to work with, I have to say. Alright, well, that is because we're looking for .hbs. If I change that, and then hit Save, well, it's reloaded itself. And boom, look at that. Oops, that needs to be up against the side. Okay, so checking search. There we are. Alright, our application is Jaded up, and that looks pretty nice.
The last Electron application that I built centered around content, and there was a list of Markdown files that were structured the same way that you might find in Jekyll, or any other static site out there. There's a bunch of Markdown with some metadata at the top, and I had an index over it. I had a table of contents that people could click, and they'd see a list of all the chapters that I created, and there was video in there, text, code samples, and so on. And a lot of times you find yourself building out an application that has to display content like that. It could even just be help files. Either way, it's a really interesting task to solve. So let's take a look at how I did that. And before we get going, I do want to point out that the application that you see here is the result of previous modules, so if you're cherry picking your way through, and some of these things look weird to you, I'd go have a look at the other modules. Now for this one, I'm using Electron Reload, which you've seen me use, I am using Jade to render content to the screen. I'm using jQuery. And in my index.html, I have some links set up for home and search. Chapters doesn't do anything just yet, but that's what I'm going to wire up. And then down below here I have my rendering logic. And if you haven't seen the way I'm doing the event aggregator, please pause for a moment and go take a look at one of the previous modules. Anyway, I'll be leveraging this as I build things out. So the first thing I need to do is to hook up the navigation here, and the way I have it set up is that the id corresponds to a view on disk, as you can see here. So I have home.jade, search.jade, and so on. So I am going to add chapters, chapters spelled correctly, chapters.jade. And in here, I will just put in chapters. Geez, I can't spell that word. There we go. Okay, so my app reloaded over here, and if I've done everything right, yep, boom! So there is Chapters. Now the next task I have is to actually read the chapters that I have off disk, and then to populate a little list right here, and then I want to display each item over here to the right side. So here are the chapters that I created, and I got the History of Pi, The Origins of Beer, which I think is fascinating, and also Phi, The Golden Ratio. We just talked about that. This, at the top here, is Front Matter. If you've never used a static site generator, they all use kind of the same layout. It's a little bit of YAML, if you will. It just basically says the title of this page is this, source is this. We want to display that somehow. Anyway, I'll show you how that all goes together in just a second. Our task in this module is to hook this page up so it displays this content and also parses the Markdown appropriately.
The Chapter List
First thing I want to do is to add some styling for a chapter list, and so I'm going to take out the header right there, and actually what I want to do is as I build this thing out I'd like the app on the right to refresh so let's go over to index.html. And then down below here, by default, I have home showing up, but I think I'm going to change that to chapters, so now when my page loads, good. So now I can build this thing out while I watch it. So let's start out with a wrapper for the main area, and I'm going to give this an id of chapter-nav. And if you're wondering, what am I looking at? What is this language? This is Jade. And Jade is just a very terse, clean way of writing HTML without a bunch of end tags. Okay, so we'll do an H3. There we go. So, if I save this, awesome. So that's showing up there. So now what I need to do, of course, is I need to have another div here, and I'll just call this id=chapter, and then I'll apply some styling to it in just a second. And here is just going to be the title, so let's just do Title. I'll just pop that out for right now, so good. Everything is laying out completely wrong. Let's add some styling. I'll go over to my app.css, and then down here at the bottom, I'm going to put some styling for the chapters and so on. Once again, I'm going to use absolute positioning because I don't need to accommodate wrapping, and window shrinking, and all the other things, and absolute positioning is the simplest possible thing that I can do. There we go. Position: absolute, I'm pinning it to the left, top, and bottom so it's going to span the entire size here. I'm going to set the width of our little nav area to 180 pixels for right now. I'll set the background color as well to match the top bar. And, I'll have the border on the right. Blah, blah, blah, blah, blah, so if I save this, that looks quite nice, doesn't it? And I've added in some nice hover effect so people know what they're looking at. So neat. So here if they click on A History of Pi, what I want to have happen, of course, is for the title to show up here, and I want to have the parsed Markdown down below. How am I going to go about doing that? That's next.
The Markdown Page
So right now all our application is doing is it's just responding to events, and it's doing it well, but we can do a little bit more here. So the first thing I'm going to do is I'm going to go into MarkdownPage, and I'm going to restructure things a little bit. And I'm going to do this slowly. So, I am going to move the chapter directory out here, and what I'll do now is I'll just create a private function in here, and I'll just say var loadChapters = function. And what this is going to do is it's going to read all of the chapters in and render them. And the reason why I want to do that is for two reasons. One, I can keep everything cache. I don't have to keep re-rendering it every single time, and that makes things just a little bit faster. Two, I want to be able to loop over it and index each thing. So that's going to be two separate bits of functionality. The first one though is to cycle over all the chapters and to read them in. Let's do that right now. So this has made things a little bit cleaner. I've moved the file reading logic out of the MarkdownPage, which makes me happy. We were still reading things off disk, but I don't have the chapter stuff embedded in here. And it didn't really belong in here in the first place, but that's okay, it got us off the ground. So, here I'm just reading the file, parsing it, handing it back nice and clean. And then here I am looping over all of the chapters, and then returning them out as an array. So what I want to do is to basically say this.chapters = loadChapters. Here we go. Now that we've done that, let's actually console.log this out just to see how it looks. And I will go over here to our app, and I will npm start, and the app starts up, and hooray. This.chapters is an array of objects, and good, each object reads in as we want. So that means we can do a little refactoring in our page here. We can see we're getting an error. So I can fix this error by leaning on more event-based programming, which makes me happy. Here I'm just kind of muscling my way through it so let's refactor a little bit more. This is a lot cleaner, and not only that, but a little bit faster. If you have a whole bunch of chapters in here, well, it'll speed things up a little bit because they're all cached, which is nice. So I am just requiring the application, and I am wrapping things up in a function right here, showChapter, and all that's doing is emitting a chapter selected event. And it's passing along the id that we have requested. What is that id? Well it is this right there. So down below on the click of a link, it is just calling showChapter, passing along the id. And, well pretty much everything else is as you've seen it except for this part right here, chapter-rendered. This is responding to an event from the application, and then we're setting the title and the content. So how is that happening? Well, in app.js we have a new event, so this.on chapter-selected. We are finding the chapter in this.chapters. You get the idea. So it's just bouncing events back and forth. Not a lot of code, but this is more clean and more along the lines of how you build a desktop application. Okay, does this work? Why yes it does. The nice thing about it is it's superfast as you can see. The nice thing about it is it helps us plug and search a lot better, and I am going to get to that right now.
Full Text Search
One of the primary benefits of having a desktop application is that you can do things offline, but there are some things that are a little more difficult, such as full text search. But the good news is there are tools out there like lunr.js. Of course it's not going to be as good as Elasticsearch or Postgres full-text search; however, it does the job and it does it well, and it's simple to plug in, and that's what I'm going to do. Also, if you recall, I've had a search page here for a while, and off camera I styled it up a little bit, so you didn't have to watch me slog through CSS. So what we're going to do is we are going to index our pages here, The Origins of Beer, Phi, The Golden Ratio, History of Pi, we are going to index all of this content right here so that we can search it. It could come in handy, you never know. And for that, we are going to use lunr. And to install it, it's as simple as npm install lunr.js. And just like that, boom! We have it, version 0.6. Awesome.
Hooking up Lunr
I want to add a summary to my document here, and right now, all I have is the body and the title, and I want to give it a little bit more than that. So right here I'll just say result.summary is going to equal parsed.content. So I'm going to use the raw markdown. This will work just right, at least for the format that I have here. And the raw markdown is this right there. So, if you're working with something else, obviously you're going to want to do some replaces, or whatever you need to do. But anyway, I'm just going to say parsed.content.substring. I know, tough coding here, isn't it? Substring 0-200. And then you know what I'm going to do? I am just going to make sure that people know it's a summary by adding in a couple of dots. Boom! Okay, so what that means is that we are going to get back this property in our document. I don't need to change the search at all, which is neat. So I can just come back over here to search.jade, and instead of pushing the body, I will push the summary. There we go. And our app has been happily refreshing in the background, so now when I put in pi, oh, that looks quite nice, doesn't it? Okay, well the last thing I need to do is to adjust that CSS, which is the search results. And if I go into app.css, and here are search-results for p, and excellent. So now if I have a whole lot of results, I will have a nice scrolling result. Again, I cannot tell you how nice it is to have this so apparent right in front of me. I am in control of the CSS. It is not abstracted away in some library. I know a lot of you out there might say, I know a foundation in Bootstrap really well. No doubt you do; however, I find it to be so, so very nice to have this control over things. Look at that, phi. Boom! There's Donald Duck.
We just added some content to our application. If we head over to our chapter list, we can see we have a brand new entry, Surfing in Munich. This is a real video that I took while in Munich. Check it out. This is groovy. This is a real river that goes right through the center of town into the Englischer Garten, and these people surf this standing wave. It was a lot of fun. Well anyway, my boss thought it would be a really neat idea if we decided to add this to our content, so why not? But one thing that we need to do is to allow for offline viewing. As you can tell this video is hosted up at Vimeo, which is great if you want to enable streaming, but if you want to have offline viewing, you need to give people access to the local video file so they can open it in the viewer of their choice. Well, indeed, we have that .mp4 file. It is right here in our downloads folder. Boom! So what we want to do, what our task is in this module, is to allow people to download this for offline viewing. And you might think it's as simple as just including a link, but it is not. We want to use file dialogs. We don't want to rely on any native browser functionality. So we're going to implement the file dialog and allow people to save this video where they choose.
Saving a Video
I need to copy a file from one place to another on disk, and you would think that would be an easy thing to do with Node just using the fs module, but it's actually a little harder than you think. So this enterprising individual created the fs-extra module that I am going to go install. And the reason why is it gives you a lot of extra methods, one of them I'm going to need, copy. And it's going to allow me to push my .mp4 file some place on disk. So, that's going to be my first order of business. So for that, I'm just going to say npm install fs-extra, and then of course I'm going to save it. Now that I have that, what I need to do is to create a download button. So let's go back to our code here and then right in here I'm just going to add a little hr tag, and then in here I'm going to add a button. Here I'm just going to give it a class of downloadable, and then I'll use HTML5 data attributes to specify the file that I want to download, surfing-in-munich.mp4, and there it is in our downloads directory. Alright, I've added a little CSS styling. Let's go and take a look at our application. And taking a look here at Surfing in Munich, boom! View Offline. Alright, well this doesn't necessarily do anything right now; obviously it's just a button. So, now what I need to do is use a little jQuery and wire it up, but how do I do that? This is where we kind of sit back and think about things. I could put it on this page right here and it would work just fine; however, I want to be able to have a centralized area where I can reference this class, or wire this class up on click to be able to download anything. So let's put it in our HTML here, our index.html. And what I'll do is I'll just put it right down below here, and I'll just say .downloadable. And let's just output the source file to make sure that we have access to it. Okay. Dot downloadable on click, we're going to grab the event, we're going to pull off the current target, access the data tag, video, we should be good to go. Alright, and let's just double-check, class is downloadable. Awesome. So our app has been up here, and it has been refreshing itself. We have no errors. That's good. When I click this, however, you'll notice that nothing happens. Why is that? Well if you know anything about jQuery and the DOM, well, what we're seeing here is, well, events being wired before the DOM is loaded. What do I mean by that? Well, if we head over to our chapters.jade, if you recall, what we're doing here is we're just emitting an event, and then when we get chapter-rendered back, we are just simply piling the title and the body into the DOM. In this body is exactly where this button is being appended. When that button goes in it has no notion that it needs to fire an event because this event was wired before the button hit the DOM. So this is something that you're going to run into if you're doing things like I'm doing, which is, well, basically by hand. But I still prefer it. I still very much prefer doing things by hand. So there's an easy way out of this, and it's a thing I've been doing for a long time, I am going to create a function called wireEvents. And what this is going to do is just wire up events. So let's have a quick think about this. I am centralizing the wiring of jQuery events here on my index.html page. These events are going to trigger things on other pages throughout my application. Doesn't this seem a little bit like spaghetti code? Not only that, but every time I change the DOM, am I going to have to call this? Well, possibly. This is for global events. This is for global application stuff if you will. Right now, I have the concept of a downloadable button. I only have it in one spot. I don't need to do this, but I think in the future I'm probably going to add more pages here with more videos that I'm going to want to allow the user to download. There is only one event right now in my application that I need to use wireEvents for. This is for global application events. It's not for every single DOM event. What do I mean by that? My habit, when I build these things out, is if I have a page that needs to do a certain thing, such as this chapter page, where say you click on one of these links, and it re-renders the material, if I have a page that does something, I will put the code on that page itself. Because the code is on this page, it will get loaded into the DOM, and the events for that page will be fired. So I have the concept of local events, as well as global events. Anyway, what I need to do is I need to call wireEvents right here because I need to wire up the global events that are present inside of my pages. So in other words, every single time Surfing in Munich gets rendered I need to make sure that I wire up the global events, in other words, that our downloadable button gets wired up. I could put this in the page itself, but I could see easily in the future having multiple pages in here that will allow for downloading videos. Alright, a lot of talking, but let's take a look at what we have, Surfing in Munich. And if I click on this, undefined, and let's go check on our stuff here. So, if I take a look at index.html, it looks like this is correct. That should be data-video. Okay, let's try that again. And heading back over, Surfing in Munich, View Offline, boom! Great, there is our file. So now we are wired up nicely, and now what we need to do is implement the save dialog. And what I need to do here is reference a few modules, and the first one I'm going to reference is fs-extra. I also need to reference path. Okay, what are these modules? Well, we know what fs-extra is, I just went over that. We already know what path is. Remote is the remote process. So if you're familiar with Electron, then you know that there is a main process and a rendering process. The main process is what goes off in here. So, when the bootstrapper kicks up, Node kicks in, and what it will do then is it will create a brand new window that has its own rendering process. That rendering process is what we are in here. We have access to Node on both sides and both processes, but we don't have access to the dialogs. So what I need to do is I actually need to talk to the main process, and I do that using the remote module. And then I say, give me access to the dialog using remote.require. It seems a little bit convoluted if you ask me, but this is just what you need to do. Alright, well now that I have access to our dialog, I just need to show the save dialog. We know the name of the file. We know where it lives. We just need to know where the user wants us to put it. There we go. So dialog.showSaveDialog is going to pop up a save box. I'm going to give it a default path, and that's simply just going to be the name of the source file itself, .mp4. And then, we are going to get a callback here with the exact, absolute filename that the user has chosen. So they could decide to rename this to be anything they want, but they're going to tell us where they want that file to be saved. So if we have a filename, I am then going to just go grab the source, that's this right here pulled off the data attribute. I'm going to pull it from the downloads directory, which is right there. And then I'm going to copy it. I'm going to copy the source to the filename that the user has specified for us. Whew! Alright, well let's see if this is going to work. So I'll go over here, Surfing in Munich, View Offline, up comes the dialog. You can see it's prepopulated with Surfing in Munich, and I can go ahead and put this on my desktop, and I hit Save. And on my desktop, if I double-click, there is my file. Boom! Woo-hoo. That's just crazy. These people are such good surfers. Look at this. It's pretty crazy to find people surfing in Munich, which is pretty much land bound, and these guys are really good surfers. I love this. Okay, so there you go, a very simple way to allow offline access to files in your project.
Handling External Links
Our boss is reviewing our work and really liked the addition of the Surfing in Munich video, but thought it would be a really good idea if we included a link so that people could know more about Munich, and what the name of this river is, and so on. So, I added a link, but uh-oh. Given that we are working inside of a browser that's not really a browser, well you can see we have trouble. You would think that we could get out of this by using target=blank. Well that's just not exactly the case. To show you what I mean, I used markdown in this file, and as you can see, well it really didn't work. So let's actually rejigger this. Let's take this link right here, and we'll do a target = blank. Okay, there we go. So, I need to restart my application because it is kind of stuck over there on Wikipedia. Alright, so now that it comes up, I should be able to pop a brand new browser window, but notice, number one, that my formatting is off, which stinks. Two, when I click on this, that's not exactly what I want. This is a tiny little window, and of course it's not going to do me any good. More than this, the default browser that I have here is Safari; it is not Chrome. This is coming up in a windowless Chrome environment. This is a bit of a pain. So let's fix this. We can fix everything by just doing a little bit of jQuery magic, and also working with the built-in IO stuff from Electron.
Using the Shell
So before I do anything, it's critical that I understand which links are going to open an external window versus which are going to operate internally as internal navigation. So right now, the only links that I have working internally are the ones on the outside. These help navigate through the various views of the app. Here, if I had this wired up, could go to a customer profile page, but basically everything else is internal here, and this is all content. So I could make that decision right now, that if I'm click inside of content, inside of the main window here, that I'm going to want that to open an external link. Now what I could do is I could tag stuff if I had, let's say I had an anchor tag inside of the main window and I didn't want it to open up externally, I could tag that with not external, or internal. So, that's what I'm going to do. Here, in index.html, inside of wireEvents, I am going to make sure that every link in our main div, every A tag, and not anything that's marked internal. There we go. On click, and then we're going to just prevent the default. Good. So any link inside of our main div that is not marked with a .internal class .on click should open up an external link. But right now I'm having it do absolutely nothing. I just want to make sure that what I've said here my selector works. So coming over here, and going to Surfing in Munich, and clicking this. Yes, nothing works. No window is popping, so it looks like I've wired up jQuery okay. So now what I need to do is to actually make sure that it opens the external window. So the first thing I'm going to do is to grab the URL, so I'm going to say var url = this.attr href. And then I am going to grab the shell. And then I'm just going to say shell.openExternal. And it's going to open the external application corresponding to the URL that we give it. In this case, it's just a web URL so it's going to open it right up. There it is. Alright, that wasn't too hard, was it? The hardest part honestly is just making sure that you have good rules for internal versus external links. So coming over here, Surfing in Munich, and notice this time we are dealing with Safari. And goody, we are here on our Wikipedia page, and we are now handling external links.
There are so many tips and tricks that I could share with you, but I chose the ones that I think will benefit you the most. I really do hope you've enjoyed the Electron Playbook. Thank you so much for watching. If you have any questions, feel free to drop me a note on Twitter, or send an email to the address that you see on screen. See you again soon.