Series: Building the CRUD-builder

Blueprint Basics Pt 2-Custom Variables, Editing Files, and Exploring the Unknown

Published on May 10, 2015

Today we're creating custom variables, learning how to edit preexisting files, looking at the blueprint lifecycle hooks, and more!

A 13-minute extravaganza that includes common pitfalls of working with blueprints and general tips for when you're exploring undocumented features.




Welcome to part 2 of our series on blueprints. In the last episode, we learned how to create a file system and files as well as how to put content within those files using the default variables that are available. While this allowed us to do a lot of time-saving automation, it's somewhat limited. That's why, in this episode, we're going to be editing our index.js.

Index.js is where we can create our own variables, as well as take other actions.

Exploring the Undocumented

A little bit of a warning before we go farther— we're going to be using some features that aren't very well documented. The main thrust of this episode isn't necessarily telling you how to use these specific features because I'm not necessarily using them perfectly or to their full extent. The idea is to show you how that you can go and use features like this when they're not documented, steps you can take to explore. Also to do some sweet automation using blueprints. To do that, we're going to tell you some common pitfalls and some common strategies, as well as make our blueprints.


Let's start with some pitfalls that you may encounter while working with blueprints.

First is that you don't have access to ember. This is not an ember environment, this is an ember-cli environment. You also don't have access to a lot of things that ember-cli provides you. You don't have import statements or ES6, for example. Also, you can only use npm packages that are in dependencies, not the ones that are in devDependencies.
In a way, we really are exploring a hostile, unfamiliar environment. Let's keep that in mind, but we'll press on because, unlike other hostile environments, the only thing that can get hurt is our ego.

Learning from the Model Generator

The first thing we'll want to do is find a way to create the attributes for our ds.model. To do that, let's look for inspiration, say, in the model generator that comes with ember-cli. As you can see, they put out attributes just like we want to and they compile those attributes in index.js. Let's take inspiration from that.

When I say "inspiration," what I mean is copy. This is the code from the model pasted and simplified. Let's take a look at our simplified locals. What locals does is it takes in options and it returns a hash of variables that you can use in your components over here, in your files.
The pattern we have is we have our empty array. Then we loop through the entity options and this line I just copied whole-sale from model, I'm not sure why things are as they are, but when you're exploring, you don't have to know why for everything.

We loop through all these entity options. We use StringUtils to create different forms of the name, and we have to do this because, as you recall, we can't access ember. Normally we'd do ember.string. Here, we have to fall back on StringUtils.

This line is copied over, but we also had to go and create our own lib utilities directory and put string within it. We had to copy that over. If you see files like this when you're working with blueprints, you have to copy those over as well.

EOL, it worked right off the bat.

Anyways, so we loop through those, we push them onto the empty array. We have the camelized name, then a colon, and then the results of DS.attr, which is just a switch statement saying if it's known, then you do it of that type. If there's no known type, you just do an empty attr.

Then, when you've looped through all those, you join them together with a comma, then an end of line, then a little bit of spacing. Once you've joined them, you dump them out into model attrs. That you can then use as a variable. That variable will get you the results that you need. Here, these were formed from model attrs.

Replicating the Pattern for Other Attributes Displays

We're going to repeat that pattern 3 more times. One of the times will be in the new controller, where we're creating the new object that is not yet an ember data object. So you have to get the new object attrs and their defaults. Then, in the show template, we have to get the display attrs. Then in the edit fields, we have to do the input attrs.

Up here, in the locals function, the pattern is the same. You start off with an empty array, and then you push something into it. It differs a little bit and most the differences are going to be down here in the functions we call out to. Then we join them, usually with an end of line, sometimes with a little extra in the case of new object attrs, since these are in a JavaScript file and these are a Handlebars file. The technique is still very similar. Then you return them in the locals.

You can put in different Handlebars, different mark up, to set them apart. As we said, the input field and display and new object default, they're also going to be case statements that change based on the type. A boolean input field would be a type checkbox and have checked, while everything else is regular and has a value. If it's a date, good luck with that. The same with a new object default, if it's a boolean, the default is false, otherwise the default is an empty string.

That will generate, for example, in the new post, these. In the template, it'll generate something appropriate. Then in the edit fields, it'll also generate something appropriate. Sometimes, like this text area here, it'll start off as input. Then you want to change it because you'll want something a little bit different, a little bit longer. That's okay, this is convention over configuration. It'll spit out the convention and you can go back in and change it to configure it.

Editing an Existing File (router.js)

We've got most of what we want, but the router is still not being edited. We want this chunk of text to be inputted into the router, into an already-existing file when we type in our blueprint. How do we do that?
My first thought was to go to the ember-cli router blueprint. Unfortunately, they use a package called "ember router generator," which is even more undocumented than what we have going. As far as I can tell, it won't be able to do this sort of nested routing that we need.

So we look to a slightly unorthodox source. This is the index.js for the ember-cli CoffeeScript route blueprint. I chose it because I knew that they would need to generate a route that was in CoffeeScript, so it's different than the standard. I wasn't disappointed. Down here, in add route to router, what they do is, essentially, they grab a regex and that will put you in the middle. Then they replace it with new content, so they open up the file and stick stuff in the middle.

This is what we get after stealing liberally from the ideas in the ember-cli router blueprint index. We have our add route to router function, which it gets the path for the router and then all the content from it. That uses path and fs, which are things that we have to require. Fs.extra, we have to remember to put it in our dependencies in our package.json. Do that.

We use a similar regex. I've changed away from CoffeeScript and made it so it uses the regular JavaScript conventions. Then this is pretty much the same, but with the thing we're adding in being different. This has been handcrafted to generate the stack, the CRUD stack. Then we use fs again to write the file.

Blueprint Hooks

That's our function that adds stuff to the router. How that gets called is through the after install hook. It gets called after you install it and then it just calls this.

After install is one of the seven hooks that are available in ember-cli blueprints. There's a locals hook which we saw before, and the file map tokens hook which we saw in the previous episode, and normalized entity name. Then there are the several install and uninstall hooks, which do about what you would expect.

The uninstall hooks, you can destroy your blueprint. You have a destroy command. The destroy commands are really nifty because you can generate something and if you made a mistake, just type destroy with the same stuff and it will destroy it. However, we're not going to make one for this particular blueprint.

So Much More to Explore

As a matter of fact, there's a lot that we're not going to do today. This is already over 12 minutes. It probably could've been multiple screencasts, and each of these things, adding them would take its own screencast.

What I want you to get from this, even more than specifics of things, like how to work with locals and after install, is how to explore features that are not yet fully documented. How to look at other open source code and find what you need.

I hope you're able to do this and I hope to see you next week, where we tackle the slightly more tamed area of creating an add-on. I'll see you then.

Building the CRUD-builder

Subscribe to our mailing list