Frontend JavaScript TypeScript

Generating view model and view using Aurelia CLI

In the previous post, I presented to you the Aurelia – new, great JavaScript framework created by Rob Eisenberg. We also used its Command Line Interface (CLI) to create new Aurelia project with all its dependencies, unit tests, and HTTP server. As I announced back then, we’re going to play with CLI to generate some code inside our project. So, let’s get started!

Aurelia CLI generator

Okay, before our implementation, let’s find out what kind of code we can generate using Aurelia’s CLI. To do that, just type the following command inside your project:

 


au generate

 

The following screen presents available options:

 

sc1

 

We can check whether it’s working by creating sample attribute. All we have to do is type:

 


au generate attribute

 

You’ll be asked to type the name of your attribute. I’m going to call that Test (clever, right?). Almost immediately, you should get information about successful creation. Now, where should we look for the code? Below I listed paths for each generated „thing”:

 

  • attribute: ‚./src/resources/attributes’
  • binding-behaviour: ‚./src/resources/binding-behaviours’
  • element: ‚./src/resources/elements’
  • value-converter: ‚./src/resources/value-converters’
  • generator: ‚./aurelia_project/generators’
  • task: ‚./aurelia_project/tasks’

 

As mentioned I’ve created TestElement, so I’m going to check the elements folder. Here’s the result:

 

sc2

 

Nice! Everything works like a charm. Except, that’s not what I was looking for. None of this „things” satisfies me since the usually created files are view-model with its view. What is more, these files can be located in different folders each time because most of us try to group them depending on application’s functionalities. So, what’s the solution? As you’ve probably noticed, Aurelia’s CLI generator delivers an option to create… generator! That’s awesome!

 

Creating view-model generator

Let’s start by typing the following command:

 


au generate generator

 

I’m going to call it view-model, then I redirect to aurelia_project/generators and look for the view-model.ts file. Here’s the default implementation:

 


import {autoinject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';

@autoinject()
export default class TestGenerator {
  constructor(private project: Project, private options: CLIOptions, private ui: UI) { }

  execute() {
    return this.ui
      .ensureAnswer(this.options.args[0], 'What would you like to call the new item?')
      .then(name => {
        let fileName = this.project.makeFileName(name);
        let className = this.project.makeClassName(name);

        this.project.elements.add(
          ProjectItem.text(`${fileName}.js`, this.generateSource(className))
        );

        return this.project.commitChanges()
          .then(() => this.ui.log(`Created ${fileName}.`));
      });
  }

  generateSource(className) {
return `import {bindable} from 'aurelia-framework';

export class ${className} {
  @bindable value;

  valueChanged(newValue, oldValue) {

  }
}

`
  }
}

 

The code might look complicated, but it’s not. The generator has three objects injected using the autoinject decorator (delivered by aurelia-dependency-injection). All magic happens inside the execute method. First, it invokes a method called ensureAnswer on the UI object. That’s going to print the question on the console. The written answer is then passed inside the name object. Next, it makes a proper file nad class name (based on the name object) and adds the new file inside a particular folder (in this case that’s going to be the elements folder). New file contains the code delivered by the generateSource method which returns a string. So, it’s not that hard, right? We don’t need to play with some hard, strange mechanisms. All we need to do is to type the code inside the string, nothing more. As the last step, generator commits all changes using the commitChanges method and prints information about the successful creation. That’s it. So, as I mentioned earlier, we need to modify that code a little bit. First, we need to generate a view model and view code, but that’s going to be simple since it’s just a string. Our second objective is to develop an ability to point location in which the file should be placed. The code below shows the implementation. It’s not the best code I’ve done but it works (at least I think it does):

 


import {autoinject} from 'aurelia-dependency-injection';
import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';

@autoinject()
export default class ViewModelGenerator 
{
  constructor(private project: Project, private options: CLIOptions, private ui: UI) { }

  execute() 
  {
    var self = this;

    return self.ui
      .ensureAnswer(self.options.args[0], 'What would you like to call the new view model?')
      .then(name => {
        
          let fileName = self.project.makeFileName(name);
          let className = self.project.makeClassName(name);

          return self.ui.ensureAnswer(self.options.args[1], 'Where would you like to create the new view model (this is root level)?')
          .then(path => {

                self.project.locations.push(self.project.requestedPath = ProjectItem.directory(path));

                self.project.requestedPath.add(
                    ProjectItem.text(`${fileName}.ts`, self.generateViewModelSource(className)),
                    ProjectItem.text(`${fileName}.html`, self.generateViewSource())
                );

                return self.project.commitChanges()
                    .then(() => self.ui.log(`Created ${fileName}.`));
          });                
      });
  }

  generateViewModelSource(className) 
  {
return `export class ${className}ViewModel
{
    message = 'Hello from ${className}ViewModel !';

    constructor()
    {
        
    }
}
`
  }

  generateViewSource() 
  {
      return '<template>${message}</template>'
  }
}

 

There’re two major changes here. Firstly, after making a file/class names I invoked the ensureAnswer method again. That’s because we need to ask about the path to the file (starting from the root level of the project). Then comes this strange line:

 


self.project.locations.push(self.project.requestedPath = ProjectItem.directory(path));


 

Since there’s no such thing as the aurelia-cli.d.ts, it was kinda hard to find out how to add a custom path to the project object. That’ why I dived into the js file and I discovered that it contains the locations array. So all I needed to do was to add the new one and use that to add the new files. The rest of the code is almost identical to the default, except it also adds the HTML file (generated below). Before checking whether it’s working, we need to add one more JSON file containing the description of the generator. It’s important that the name of JSON file must match the generator’s name. Here’s the code:

 


{
  "name": "view-model",
  "description": "Creates a view model class with view and places them in the chosen path inside the project."
}

 

Back, to the console we can run generate command again:

 

sc3

 

It looks like, it detected new generator. Let’s create a new view model using the command below:

 


au generate view-model

 

I’m going to call that Generated which is going to be located under the src/generated-view-models path:

 

sc4

 

Looks fine! Not only it generated the code but also created a generated-view-models directory. Nice!

 

Summary

I hope that today’s post will help you to create your own, great code generators (based on your needs). How about the service generator? Or maybe go a little bit further? Generating new area with generated routing also sounds great. As always I encourage you to follow me on Twitter and Facebook where you can find all my new posts!

 

 

Don’t miss new posts!

If you enjoy reading my blog, follow me on Twitter or leave a like on Facebook. It costs nothing and will let you be up to date with new posts 🙂