Wane

The basics

This guide is written in form of series of tutorials where we'll build a couple of small applications and explain concepts on the fly. It's created for people who like to follow step-by-step guides and learn the necessary building blocks when you need them.

If this is not how you typically learn, you might want to check out the Complete Guide. It gives you better foundation of the topics you need to know, but the examples are purely illustrative instead of guiding you through building a complete application.

If you already know Angular, React or Vue.js, you can learn Wane's syntax in a few minutes with our Comparison Guide.

Writing the Hello World app

Hello World programs might not be the most interesting piece of software you'll ever built and might not provide the best insight in complexity of any framework, but they are a great way to test if your environment is correctly set up.

We'll go through installing Wane and creating a simple app which displays "Hello, World!".

If this is your first contact with web development, you should check out our quick introduction. This guide assumes that you can get around the console (terminal), that you have Node.js and npm or yarn installed and that you're comfortable with using its basic commands.

The setup probably feels pretty familiar since the basic principles are the same as using any other framework or library when developing a web app.

Just use whichever package manager you're used to and fire the installation command. The package you're looking for is named wane. After installation is done, you're ready to go!

# with npm
$ npm install wane

# with yarn
$ yarn add wane

Having proper directory structure is quite important, so we'll immediately set that up. Worry not, it's just a single folder.

In the root directory of your project, you'll need a src folder. This is where all the fun will happen. In that folder, create a file named index.ts. This TypeScript file is where all the magics starts from. When we write the code, Wane will open this file and start analyzing your code from here.

$ mkdir src
$ touch src/index.ts

The preparations are done and we're finally in for some code.

Like in most SPA frameworks, your code is organized around components. In Wane, a component is defined by declaring a class and attaching the Template decorator to it.

The decorator accepts a single argument of type string which is the template of your component. This is where you define how things should look based on the current state of the app. Of course, in order to use the decorator, we have to import it first.

Every application must have exactly one entry component. If you imagine components in an app like a tree, the entry component is a root of that tree. In Wane, the entry component is always defined in the src/entry.ts file and is always the default export from it.

Our class has an empty body because there's no data in the app. All we do is present a static message. We'll add some stuff in there right after we verify that Hello World works.

import { Template } from 'wane'
@Template(`Hello, World!`)
export default class {
}

To run the application, simply run the binary executable which comes with the installed wane package -- it's called (you guessed it) wane.

You'll have to utilize your package manager because wane is not a global executable.

Wane allows you to run the app in development mode or to build it for production. For now, we'll use the dev mode, which is activated by the start command.

While the dev server is running, it will watch over your code: with every change you save, Wane will re-compile your application, and then refresh the browser page so you can see the changes in action.

# with npm
$ npx wane start

# with yarn
$ yarn wane start

If this worked, it means we're ready to dig into some basic concepts in Wane. Pay close attention to this section. We don't call them basic because they're not important or because they're obvious/trivial/easy, we call them that way because they're the most important things to know. A vast majority of every app is built by using only things from this and the next section.

Let's learn about string interpolation. It allows us to define a string in the component and print its value somewhere in the template.

We can refactor our app by adding the property greeting. We initialize it with the string `Hello`.

Now instead of writing "Hello" directly in the template, we want to interpolate it. This is done by enclosing the class property name in double braces: "{{" and "}}".

When Wane renders the template, it sees the interpolation braces and the property name written inside it. Then it checks the current value of the property with that name and prints the value.

Whenever the value changes, the view will update automatically. We'll get to that pretty soon.

If you save these changes, you'll see that nothing changed in the browser. This is because we've created the exact replica of the previous version of the app, just in a different way. Change the string Hello to Hey there and hit "save" to see the change.

import { Template } from 'wane'
@Template(`{{ greeting }}, World!`)
export default class {
  greeting = `Hello`
}

Time to add some interactivity to our app. We'll create a button which changes the greeting from Hey there to Hello and vice-versa when clicked.

The first step is to simply add the button to the template. If you click it, it will not do anything yet.

import { Template } from 'wane'

@Template(`
  <p>{{ greeting }}, World!</p>
  <button>Toggle greeting</button>
`)
export default class {
  greeting = `Hey there`
}

Now let's create a method on the class which will handle the toggling requirement that we've set. It's straightforward: if the string already is Hey there, we change it to Hello; otherwise we change it to Hey there.

import { Template } from 'wane'

@Template(`
  <p>{{ greeting }}, World!</p>
  <button>Toggle greeting</button>
`)
export default class {
  greeting = `Hey there`

  toggleMessage () {
    if (this.greeting == `Hey there`) {
      this.greeting = `Hello`
    } else {
      this.greeting = `Hey there`
    }
  }
}

What we need to do now is to make clicking the button invoke the method toggleMessage.

To do this, we need an event binding on the <button> element. This is done by adding an attribute in the template. The attribute name is the name of the event you're listening for, surrounded by parenthesis.

For example, if we want to invoke (i.e. call) the method toggleMessage when user clicks on the button, we write (click)="toggleMessage()".

Wane will figure out that a change in your model (greeting) happens when this function is called, and the view will automatically update.

Run the app now to see in action.

import { Template } from 'wane'

@Template(`
  <p>{{ greeting }}, World!</p>
  <button (click)="toggleMessage()">
    Toggle greeting
  </button>
`)
export default class {
  greeting = `Hey there`

  toggleMessage () {
    if (this.greeting == `Hey there`) {
      this.greeting = `Hello`
    } else {
      this.greeting = `Hey there`
    }
  }
}

Let's now change who we are greeting. Our goal is to add an input field on the page where the user can type a name which we want to greet. In other words, given the user an option to change the World part of the template.

Just like previously, we'll first extract World into a variable called name and interpolate that in the template in place of text World.

Then we'll add the input field. Remember that inputs must be connected to a label. An easy way to do this is to put it inside the label.

This won't still do anything. The input exists, but it is in no way connected to the variable name.

import { Template } from 'wane'

@Template(`
  <p>{{ greeting }}, {{ name }}!</p>
  <button (click)="toggleMessage()">
    Toggle greeting
  </button>
  <label>
    <span>Name</span>
    <input type="text">
  </label>
`)
export default class {
  greeting = `Hey there`
  name = `World`

  toggleMessage () {
    if (this.greeting == `Hey there`) {
      this.greeting = `Hello`
    } else {
      this.greeting = `Hey there`
    }
  }
}

We'll listen to the DOM even in a similar way as we've listened to the click event on the button, except now we'll listen to the change event.

There's one important difference though: last time we didn't need the actual event from the button. All we cared was that it was clicked on. This time, knowing that the user changed the value to something is nothing enough. We need to grab the event emitted by the input element and read what user has typed from there.

To do this, we use a special character for an argument in the function call: #.

This basically means "grab the emitted value when the event happens". The event carries a lot of things, but what we really need is the value typed by the user. We can get this by inspecting the target of the event (which is the input element), and then reading its value property.

If you run the app now, you'll see that the name at the start of the page is changing as you write in the input field.

import { Template } from 'wane'
@Template(`
  <p>{{ greeting }}, {{ name }}!</p>
  <button (click)="toggleMessage()">
    Toggle greeting
  </button>
  <label>
    <span>Name</span>
    <input
      type="text"
      (change)="onNameChange(#)"
    >
  </label>
`)
export default class {
  greeting = `Hey there`
  name = `World`

  toggleMessage () {
    if (this.greeting == `Hey there`) {
      this.greeting = `Hello`
    } else {
      this.greeting = `Hey there`
    }
  }

  onNameChange (event: Event) {
    const target = event.target as HTMLInputElement
    this.name = target.value
  }
}

There is, however, an issue here. The first time you open the app, the paragraph says "World" but the input field is empty. We need to synchronize the value of name not only in the paragraph, but also in the input field.

We need to bind the value of name to the property value of the input element.

Binding is achieved by using [ and ] to wrap the property that we want to bind to, and specifying the name of the variable that we're binding.

For example, binding the value of variable foo to the property bar is done by writing [bar]="foo".

Take care to note the difference between type="text" and [value]="name". In the first one, we're assigning the literal string "text" to type. In the second one, we're reading the value of a class property named name, and binding that value to the property value. You can tell the difference by presence of the wrapping pair of brackets.

Wane will keep an eye on the value of name and trigger an update of the value property on the input element whenever it occurs.

import { Template } from 'wane'
@Template(`
  <p>{{ greeting }}, {{ name }}!</p>
  <button (click)="toggleMessage()">
    Toggle greeting
  </button>
  <label>
    <span>Name</span>
    <input
      type="text"
      [value]="name"
      (change)="onNameChange(#)"
    >
  </label>
`)
export default class {
  greeting = `Hey there`
  name = `World`

  toggleMessage () {
    if (this.greeting == `Hey there`) {
      this.greeting = `Hello`
    } else {
      this.greeting = `Hey there`
    }
  }

  onNameChange (event: Event) {
    const target = event.target as HTMLInputElement
    this.name = target.value
  }
}