Electron Playbook
Introduction Hello, and welcome to the Electron Playbook here at Pluralsight. My name is Rob Conery, and I'll be your host for this course. If you have any questions or need to get a hold of me, you can use the addresses on screen. In this course, I'll show you some basic tips and techniques that make working with GitHub's Electron Shell a little easier. From layout strategies to dropping in full text search, these techniques will save you some time and hopefully help you in your application building efforts. This course is a Pluralsight playbook, which is a little different than a typical course here at Pluralsight. Each module is focused on a particular task and is designed to stand alone. Rather than focusing on concepts and building a demo application, we'll focus on essential tasks so you can get to work right away. This course is meant to be a companion to another Electron course that will be published at Pluralsight at right around the same time. If you're new to Electron, I highly encourage you to watch that course first, as it covers the basic concepts. To get the most out of this course, you should know HTML and JavaScript at the very least. We'll be building things with Node so you should be comfortable with it and how it works. You can get away without having used Node before, but I might recommend watching one of the many courses here at Pluralsight before you go further if you've never used it. I've put together this agenda based on experiences I've had creating desktop applications on and off for the last 24 years of my career. Over the last year I've been working heavily with Electron, and it's all I've been doing for the past few months with a small side project I've been working on called RED:4. I've learned things the hard way, and as such I'll have some opinions that I'll share with you, which I guess is no surprise. Our agenda today is straightforward and task-oriented. We'll start by thinking about layouts and how to structure your application using some basic mathematics. We'll then build our very own and very simple view engine using Handlebars and then Jade. We'll move from there to creating online and offline content using Markdown. We'll then plug in a full text search completely offline using lunr.js. Finally, we'll enable offline video viewing by implementing the save file dialog, and we'll engineer what happens when a user clicks an external link. Before we even get started, however, I want to address the biggest question I get asked regarding the way I build things with Electron. Which framework did you use? My answer is always the same; I don't use any of them. The reason is simple. I don't need them. All of these frameworks were designed to work on the web in various browsers to simulate a desktop experience. We have a desktop experience with Electron. Node is available to us directly in both the main and the rendering process, and I have full IO access. In addition, I'm not going to use any CSS frameworks either. These things were built with the web in mind, and mobile applications. I'm building a desktop application that has pixel perfect requirements. I don't want responsive changes, and I don't need cross-browser support. I don't need a grid system. So in short, working with a CSS framework will actually take a lot of extra work. And don't get me wrong, I use these frameworks all the time for websites I build. I just don't need them for a desktop application. Over the years I've found one thing to be glaringly true when writing desktop applications, simple wins always, and it's what I focus on. And even when I start making a mess, I don't turn to big frameworks. I stop and think about what I'm doing, and I go back and I refactor until it's just right. My opinion is based on my experience building desktop applications, and others have had the same experiences as well. Consider the Atom text editor, the project that brought Electron to life. Here's their announcement about using React to run their code editor. This was in July of 2014. Here's their announcement that they're removing React six months later. Their reason was simple, speed. Atom at the time was plagued by a slow start-up and a general sluggishness. They removed almost all abstraction and went straight to the DOM. And while this might be more difficult for them to manage as developers, it's made all the difference to their users, myself included. I love the Atom text editor. I'll share one more story. Here's an article on Medium about the Slack desktop application, which was built on Electron, and I'll talk more about that later. The article is quite funny. It highlights an important thing. Slack only uses jQuery, and I can understand why completely. Platforms like Angular, Ember, and React change often, and if you don't need routing, two-way data binding, and a template engine, as we don't, what's the need? The speed of your application is critical loading up, execution, and rendering. It's the best user experience that you can give. Big frameworks slow things down, especially when your application gets complex. Having complete control over your HTML, CSS, and JavaScript will save you time both now and in the future. And I honestly can't say enough about framework churn, Ember's constant shifting focus, moving from Angular 1 to Angular 2. Rolling out and supporting a desktop application is hard work. The gains you make using something like Angular will be lost three times over when you have to upgrade, or more likely when you decide to rip it out completely. Simplicity and a little extra work, that is our goal. The good news for us is that we have the full power of Node and a dedicated browser. Let's go and build something. Layout and Fonts Introduction 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. Initial Layout 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 Electron Reload 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. Absolute Layout 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. Using Phi 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 Introduction 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 I downloaded Photon, and I stuck the raw files right here in the root of our project, including this directory here, template-app. The neat thing about this is it gives you HTML and everything that you need to just go, which I think is great. So let's just copy and paste all the HTML right out of here. I'm going to drop it right in here. And then I have to make sure that the stylesheets and the JavaScript line up. And so app.js, I'm going to pull up to the top level. And then the js directory, I'll pull up to the top level as well. And then index.html stays there. Package.json stays there as well, it doesn't need to move anywhere. So that should do it. And let's just put that just like that. As a matter of fact, make it a relative link. There we go. Okay. And then I'll rename this to Vidpub, awesome. And then we'll leave everything else exactly as it is downloaded. So running our app for the first time, yeah, look at that. It looks pretty good. It's a little bit finicky, although the clicks are working with the links, and making them colored so they'll stay active, ideally, when your navigation happens. And then also through here you can see that those are Mac colors. That's good. And this grid looks quite nice. The problem that we have, or seem to have here, is Cannot read property 'addEventListener' of null. And that's happening in menu.js, line 24, which is this guy right here. So let's debug this for a second. It's looking for .js-context-menu. So one way that we can debug this is just to copy it, go back over to Atom, and I'll just do a project-wide search on exactly that. And boom, you can see it's only in one spot so that means it doesn't exist in our DOM. So it looks like what's happening here is when the DOM is loaded, we are doing some raw jQuery, or excuse me raw JavaScript, and yeah, we're looking for that and it doesn't exist. So I've investigated this a little bit and as it turns out this is just an adapted thing. It's taken out from an example, I think the person who started Photon was using, live perhaps somewhere, and just forgot to take this out. So, it's kind of nice though that it's in there, and you have the ability to have a context menu. Oaky-dokey, so let's go back to our app and see if we killed that error, and we did. So this looks pretty good. As you can see, the menuing and the fonting and everything is quite nice, but what if we wanted to do something a little bit different? Right here this just looks like the Finder when that's not exactly going to be useable for our case. So let's tweak the layout in the next section. 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 Intro The goal for this module is to transition from one view to the next, and to do it in the simplest possible way. Right now, I just have a home view, and this looks okay, but it doesn't do anything. And what I want to have happen is if I click on one of the icons on the left here, I want it to be able to transition this view. And as I say that, I'm having visions of Visual Basics 6. I wrote those apps, wow! Anyway, so, what I want to do is to focus on the simplest possible mechanism, and I know for the JavaScript Front-end people out there you might be thinking just use React, just use Angular, or Ember. And I could, but the thing about it is, and you're going to hear me say this consistently, I have the full facility of Node at my fingertips here. I could use React and hand everything over to React, or Ember, or whatnot, but those frameworks are built to manage web page interactions, to make a single page application on a website, right? Act like a desktop app. We have a desktop app right here, I don't need that abstraction, and hopefully, as you're about to see, creating your own rendering engine is not that difficult to do. I know that sounds nuts, but we don't have to deal with the fluff of the web, we just have this website, and JavaScript, and Node at our command. Alright, we're going to start off bleedingly simple, and then we're going to move on through to creating our view engine. Let's get started. Simplest Navigation 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. The View 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 Hopefully you've done some event-based programming with JavaScript because this next part is going to lean heavily on the notion, and it fits perfectly with a desktop application because it is all about events and responding to those events. So, without further ado let's jump right into it. So I am going to create a file called app.js, and I could probably come up with a better name than that, but anyway. The first thing I'm going to do is I'm going to reference Node's EventEmitter. So for this it's in the events module, and then the EventEmitter. And then I need to require util, and this is going to require util, just like that. There we go, a fairly straightforward EventEmitter in Node. If you've never seen one of these things, you might have a steep learning curve in front of you. But basically what I want to do is to have an event aggregator, something that I can emit events to and respond to events from. So basically the deal is this, our app is going to respond to events from our view here, and then our view is going to respond to events from our app. It might sound a little bit convoluted, but hey, that's the way browsers work. I hate to say it that way, but it's true. And desktop programming is all about evented stuff, if you will. Anyway, so the first thing I'm going to do is I'm going to say this.on, and then view-selected. And then whatever view is selected, I am going to take a callback. And I'll just accept in the viewName, like that. And what I'll do is I will just say var view = new view, and pass in the viewName. Just like that. And then I need to do something with it. What exactly am I going to do? Well, if we take a look at the view here, that is the thing that we just created, the idea is that we are going to pull a template off of disk, we're going to compile it, and then we need to send it to HTML, and then render it somehow. But rendering is not the concern of our application; it is just pulling the view off. So what I think I'll do is just toss in an event, Html, and I'll just pass off the HTML that is rendered from the view. Alright, so what am I going to do with this where and how? So let's go back to index.html, and then down below here, I just need to have some scripting to deal with the app and its events. So because we're working with Electron, we have full access to Node right here on the page itself. So I can say var app = require, and then lib/app. Well, the way I can test to see that this is working is to actually take this out right here, and I can create a new view, and I'll call it home.hbs, Handlebars. We don't need to call it that, I can call whatever I like, but for right now I'm just going to call it hbs so I can have proper syntax highlighting. Now that I've done that, I'm going to use jQuery to then render some stuff inside of here. And so I don't have jQuery installed just yet, but I'll get there. But anyway, basically what I want to do is say app.on rendered, so that is what's fired back here. When I say this.emit rendered, I want to respond to that event right here. And I am going to take in a function, html, and then I'm just going to stick it in to #main.html html. Good. Actually I don't like that, how about we just say rendered? So before I can do anything else I need to say npm install jquery, save. And then I need to come down here to our view, and put in script, and then var $ = jQuery, which is going to be equal to require jQuery. If you don't know what I'm doing this is a traditional fix for jQuery and Electron. The deal is that I can't just reference jQuery directly in here because of the way Window and Modular are defined especially with Node. It's a long story. Basically the deal is that Modular gets redefined by Node and jQuery doesn't quite know what to do so it gets overwritten. There's no Window object. So, anyway we need to specify the $ and jQuery, and we're going to set it equal to the require jquery response putting in a semicolon. Okay, now that we've done that, this should work. The final thing I need to do is to wire up what happens when the body loads. So I'm just going to do this, and I am going to say app.emit. And by doing that, it's going to fire the view-selected event, and it's going to specify the name of the view. And inside of here, view-selected is going to get fired, name of the view, I'm going to instantiate a new view object, passing in home. That's this right here. It's going to look in the views directory for home.hbs, which is that right there. It's going to read it in. It's going to compile it. And then, back here in app, I'm going to say view.toHtml, and then I'm going to emit the rendered event. And then over here, that rendered event is going to get fired. Boom! And ideally, it'll all just work. It feels a little crazy, but that's how this stuff works. Now you might be thinking, wait a minute, why are you putting this code right here on the page itself? And the short answer is that I like view code on the view. This is my preference. You can put it wherever you like, but if I'm going to be doing DOM manipulation with jQuery, I'd rather have it right on the page. So, that's just my preference, keeping things as simple as I can. Alright, well let's start things up and see what happens. Boom! There it is. So now this is rendering nicely. Okay, well let's see if we can get our switch to the search page to work. So what I'm going to do here is instead of doing an href right there, I'm going to do that, and then I'm going to give this an id, and then I'll just say search, like that. And then here, I'm going to say $ #search. Alright, so it's all I need to do. So Electron Reload hopefully reloaded our app, and if I click on that, boom! And then nothing is going to happen there. That's okay. Let's go back. And then I'm going to give this the id of home. And then we'll do the same thing there, app.home .on click, view-selected home. Okay, so our app reloaded, and if we go here, and then back to here, and then back to here, and then back to there, that's awesome. Notice that it's so much faster it feels more like a desktop application. And we now have a view engine. Refactoring 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 Introduction 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. Adding 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. Using Markdown Introduction 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 For this next part, I am going to rely on two really neat npm modules. The first one is gray-matter, and this thing powers a lot of static sites out there. And in typical Node style, they peeled out the parser and made it its own project, which I think is so cool. Anyway, it can parse all kinds of stuff. You can have the Front Matter using YAML, which is what we have, or you can have it be JSON. You can have it be TOML, CoffeeScript, CSON, all this other stuff. So, we're going to take gray-matter here, and have it read in the file, and parse it for me, which I think is groovy. Then, we need to also parse the Markdown from the file itself, and so I'm going to use Showdown for that. Both of these things are easily installed using npm, so let's do that now. And I'm just going to say npm install gray-matter --save. Alright, there we go. So our modules are now installed, what I need to do is to formulize this a little bit. So let's pull our page over here, and what I'm going to do inside of our lib directory, I'm going to create a brand new thing, and I'm going to call it a markdown_page.js, and then it's just going to be JavaScript. Inside here, I'm going to reference those modules that I just installed, and also file services, fs, and then path. And I'll do what I did basically for view. And inside of here I will just parse the things out that I need to parse, and then jam something out when I say toHtml. Let's do that now. Here we go. Not all that much code. I am just pulling a file off disk and parsing it. As I was writing this out, I decided I would use Underscore. I'll show you why in a just a second, but let's walk through this. At the very top here I'm just specifying where my chapters are, and this is just up a directory using path.join, upper directory, and to chapters. In case you don't know what this, this is the local executing directory, and that would be corresponding to my root. And then, MarkdownPage here is just an object that I, or a function I'm declaring, a prototype if you will. It's going to accept the title into the constructor, and then I'm creating a return object here, I'm just going to call it result, constructing the fileName. Then I'm using fs.readFileSync, we've seen this before, and then I'm parsing with gray-matter. And this is where I'm using Underscore because what I get back here, what parsed is going to be, is an object with properties on it that correspond to the Front Matter. So it's going to have a title property, and a source property set to these values. I could put anything I want in here. The third thing it's going to have is a content property, and that's going to be this stuff down below. So, I am going to extend my result variable with whatever is parsed or returned back to me, parsed.data, then .title, and .source, and so on. And finally, I am setting the body property on my results object, and it is going to be set to the HTML that is created by Showdown. So Showdown, this is our converter, showdown.Converter right there, making HTML out of the returned content, and that is just the pure Markdown, and then returning the result. So I should be able to create a brand new Markdown page and have the HTML at the ready. Now what I just need to do is to display it when people click a link. So that's what I'll do next. Rendering My MarkdownPage logic is finished, so now what I want to do is to wire up the click event of these anchor tags to display the content here in my chapter div. Right now all I have is the title, and it's hardwired. So what I'm going to do is I'm just going to say id=title, and then what I'll do is I'll just have a p tag, and id=content. Those are the only two things that I'm planning on showing currently. So, to make this all work I'm going to wire in some JavaScript on the page, and when using Jade you put in script., that means here comes some JavaScript. So what I think I'll do is I will just wire up some jQuery. There we go. So this is going to wire up the click event for the chapter link. And you might be thinking why are you putting this right here on this page? This is a Jade page, how is this possibly going to work? I'll talk about this in a second. So, one thing that's really important is that we make sure that we know what's being clicked. We don't want to just wire up the a tags because if someone was to click a link on let's say your sidebar or whatever, you could break your entire application. And it's really important because links do not work in the same way in an Electron app as they do on a web page. So it's really important to know that. Okay, so now that I have that wired up, what I need to do is then to have a function that is going to actually go and pull that MarkdownPage. So I will just say var render, renderMarkdown. There we go. I'm requiring my MarkdownPage module. I am instantiating it, passing in the title, and then I'm setting the title on the page here, that's this guy, and then I'm setting the content as well. So then what I can do is I can say var id = this.id, and then I'll just say renderMarkdownPage, and pass in the id. Great. Okay, well that's what happens on the click event, but what I also want to have happen is when the page loads, I want the default to display, the default chapter, chapter 1. So, that's what I will do, I will hardwire this. That's what I was doing before. So I'll just say renderMarkdownPage, and then pass that in. So, let's see, if I've done everything right, then ideally what should happen is you should see, and let's restructure this window, there we go, you should see it show up. Ready? Boom! That looks pretty good. The title is repeated here, and that's because if I go back over to my Markdown Page you can see this is the title, that's what's showing here, and then I have A Brief History of Pi. So let's actually just change this. Why not? And I'll just say A Brief History of Pi, and then I'll just start the Markdown straightaway. So changing this, boom! That looks pretty good, I think. So now what I need to do is to make sure that the Origins of Beer show up, and it does, and Phi, The Golden Ratio, and hey, look at that. I've even got a little video. That makes me happy. Umm, this doesn't. So what's happening is, yeah, obviously the absolute positioning isn't reflowing, it's just going to the length of the screen and stopping. So if I scroll, well, you can see the deal. This, thank goodness, is easily fixed. So let's go back over to our CSS. Man, I love having this all right in front of me. It makes me very happy. Anyway, so if I go down to chapter, which is this right here, what I can do is I can just say overflow-y, and make sure that it's scroll. So if I refresh this, now, there we go, much better. So, if we go over here, yes. Okay, that is happening exactly as I want. So all-in-all this is a pretty simple solution, and it highlights something that I'm going to keep saying over and over again, that the simplest possible thing always wins. And notice here that my rendering logic for this page, in particular, is right on this page. There's no guessing. There's no trying to hunt down and find it in your lib directory or your js directory. All of the rendering logic happens right here, and for a Markdown Page, I can reuse that anywhere, and you're going to see me do that in just a bit. But for the Markdown Page, again, as simple as I can possibly make it. So you're going to hear me say that a lot. Anyway, I think our solution here is finished. Refactoring 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 Introduction 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 Now that I have all the chapters loaded into memory, what I can do is I can simply say var loadSearchIndex, and this is going to be a private function. And what I think I'm going to do is I'm going to accept in the app itself rather than do any weird requires, or this, anything like that. So what I need to do first here is to define the search index, and that's what I will do, I will say app.searchIndex. Now I just need to tell the index what fields to search over when I add documents to it. So I can just say this.field. And the first thing will be the title, and then the second thing I will search over is going to be body. So whenever I add in a document, I'm going to give it one of the objects that we've been working with. It's going to have a title, and it's going to have a body on it, and lunr is going to index it. And speaking of, I need to require lunr up here. The next thing I need to do is to specify what the reference field is for the document. So this is going to be the id. And if you recall, when I load the chapters I'm setting the id right here to just the basename of the file, so it would be 01-history_of_pi, etc. The reason why you have to do that is that when you do a search over the search index, you don't actually get back any information, all you get back is an id. And it basically says, here, you go find your own documents. I'll show you that in just a second. So the next thing I need to do is I need to use Underscore. And I need to say each, and then app.chapters, and then I'm going to loop over each one. And then I'm just going to say app.searchIndex.add, and I'm just going to add the document straightaway. And so now that I have loaded up the search index, I can search on it. So I am just here going to say loadSearchIndex, and I'm going to pass along this. And then let's just output loaded here. And so coming back over here to our application, which has been refreshing in the background, good, loaded. So we don't have any errors. The searchIndex is loaded. So now what I want to do really quickly is I just want to say, I just want to define a search method here. So I'm going to say this.search, function term. There we go. This is going to look a little bit weird. But the first thing I'm doing here, and I'm escaping any scoping issues because I'm doing a lot of nested function calls. The idea is that I'm just calling search on our searchIndex, passing in a term, and what I get back is an array of objects, and those objects have a simple property on them called ref. We talked about ref above. So, I'm going to loop over whatever is returned using a map because that's just going to be ids, but I want to map that, I want to return actually something different. Then I'm going to find, inside of our chapters, where the id of the chapter is equal to the reference of this object here. That is that. I know it looks a little bit weird. JavaScript. Anyway, let's see if this actually works. And what I'm going to do is I am just going to say res = this.search, and we'll just search for the term pi because we know that's in this first document. And then we'll just output that to the screen. And so our document has reloaded itself. And look at that. Boom! Down below here, the search actually works. It's almost as if I practiced these demos. Well that's exciting. So, how are we going to wire this up? Well, I could do a function call directly like this, but what might be better is to follow the evented thing. So what I think I'll do is instead of doing this.search, I'm going to say this.on search-requested. Now that that's done, let's head over to our search page and wire things up. So right now I just have an input sitting here on the page, but let's actually wrap this with a form. And here, I'm just going to set this to be my search-form. There we go. A little bit hacky. Just a little bit. I am not exactly a huge fan of this, but you know what? As I keep saying, if it works it works, right? And we don't really need much more other than second level header, and a p tag, and we're good to go. And we're just dropping that in to our search-results div. Here, I am just, as you could've guessed, I am admitting the search-requested event, and I'm passing in the value from our input above. Not too difficult. So, pulling over our app, let's see if it worked. And I am just going to enter pi and Return. And look at that. Wouldn't you know? Wouldn't you know? But yeah, there're a few problems. The first is that I'm returning the entire page, so that is just probably not going to fly. What I think I'd rather do is just have a summary, and I can do that. So let's really quickly change things around so it reads a little bit better. And also, I need to get rid of this overflow. Let's take care of that next. Refining 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. Saving Files Introduction 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 Introduction 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. Farewell 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.