Documentation

Report Edit

Tutorial

Cx is built using ES2015 JSX with webpack as a module bundling, building and dev server option. This is also the preferred way, but not required, for developing Cx applications. In this step-by-step tutorial, we will show how to get started with Cx, by developing a simple Todo manager application, using the same preferred tools.

You can get the source code of the completed application here.

git clone https://github.com/codaxy/cx-getting-started

This tutorial is intended to familiarize you with some of the packages used by Cx. A more practical way to start a new Cx project is to use the CLI.

Prerequisites

There are a couple of components required for developing Cx applications which we will be using in this tutorial:

  • Git
  • Node.Js (>= 4.4.7)
  • npm (>= 3.10.6)

Before continuing, please check that these components are installed and up to date.

git version
node --version
npm --version

First, let's create an empty folder that will serve as a root for our new Cx application and initialize the application by issuing: npm init from within this folder. We fill in the required data following the on-screen instructions, making sure that we change the default choice for application entry point to app/index.js (we will follow the recommended Cx source layout) and it will create an initial project.json file for our application.

Next, we need to install prerequisite packages, by running npm install command. Alternatevly, if you already cloned the project from github, just run the npm install command without any other parameters.

mkdir cx-getting-started && cd cx-getting-started

npm init

npm install cx cx-react --save

npm install webpack webpack-dev-server babel-core babel-loader babel-plugin-syntax-jsx babel-preset-cx-env css-loader extract-text-webpack-plugin file-loader html-webpack-plugin json-loader node-sass sass-loader style-loader react react-dom babel-preset-env --save-dev

After installing the required packages, we need to set up webpack by creating a suitable configuration in webpack.config.js file. Explaining various webpack configuration options exceeds the scope of this tutorial, so you should check out the official webpack config documentation for an extensive explanation of the contents of this file. For now, you can simply download and use a relatively simple configuration that works for us from here.

Application entry point

At this point, we have everything needed to start writing our first Cx application. First, let's define an index HTML template. In the application root folder, create a subfolder named app, and within it, create a simple HTML file app/index.html.

The file is very simple—it only contains an app div, a place where our application (or, in fact, our top-level widget) will mount.

Next, we need to define an app/index.js JavaScript file that will serve as our application entry point.

In this file, we're importing a few required objects from Cx modules. In order to use plain HTML elements, such as p or div, within our widgets, we need to import HtmlElement. The store instance we define a few lines below will hold the data model of our entire application—all UI bindings in our application will point to elements in this store tree.

hello is a very simple Cx widget defined using JSX syntax. By wrapping the widget contents inside a cx tag, we're distinguishing it from a standard React component, and it gets parsed by the Cx transformer, which will turn it into a configuration describing the widget contents. Technically, we can write the configuration by hand, using plain JavaScript, but JSX syntax is much clearer to write and read.

At the very end, we're starting the application rendering loop, passing three parameters:

  • the element in the HTML template where the widget will mount,
  • application store instance,
  • the widget to be mounted (our simple hello).

The startAppLoop function returns a handle for stopping the application rendering loop. We will use this handle in the hot module replacement related code we will add a little later in this tutorial.

app/index.htmlapp/index.js
<!DOCTYPE html>
<html>
<head>
    <title>Todo Manager</title>
</head>
<body>
    <div id="app"></div>
</body>
</html>
Copied!

Before we can start the application, we need to take care of one more thing. The line: require('./index.scss') in app/index.js will instruct webpack to load the stylesheet from the transformation of app/index.scss file. For this to work, we need to have this file defined. For now, we will create a minimal SCSS file that will just import the default Cx stylesheet. Notice that we are importing two SCSS files, one containing the Sass variables, and the other the actual styling. This enables easier style theming of Cx apps, which is explained in depth here.

app/index.scss
@import "~cx/src/variables";
@import "~cx/src/index";
Copied!

We can now run our Cx application using webpack-dev-server. To simplify server invocation, we can add a start script to our package.json file.

This enables us to start the application simply by issuing npm start from the application root folder. Upon issuing this command, a browser will open and you should see the following result:

Hello, Cx

package.json
...
"description": "Getting started tutorial for Cx framework",
"main": "app/index.js",
"scripts": {
    "start": "webpack-dev-server --open"
},
...
Copied!

Hot module replacement

While developing this application, we will be using hot module replacement feature of webpack, so before we move on, we will add a small block of code in order for everything to work correctly. We want to remember the data in the store when the main module code gets replaced, and use that data for the new module (for details, see webpack documentation).

To achieve this, we will add this little snippet to our app/index.js file right after the stop variable declaration.

app/index.js
...
var stop;
if (module.hot) {
    // Accept itself
    module.hot.accept();

    // Remember data on dispose
    module.hot.dispose(function (data) {
        data.state = store.getData();
        if (stop)
            stop();
    });

    // Apply data on hot replace
    if (module.hot.data)
        store.load(module.hot.data);
}
stop = startAppLoop(document.getElementById('app'), store, hello);
Copied!

Creating Todo widget

In the previous section, we showed how to build a minimal application, with a very simple static widget. Now we will replace that static widget with a working Todo manager.

First, create a subfolder app/todo where all the files related to our new component will reside. Within this folder, we will create a file app/todo/index.js that will hold the definition of our widget.

Again, we are importing objects from several Cx modules we will be using, as well as a local Controller module. We could have defined the controller needed for this widget directly in the app/todo/index.js file, but we'll follow the Cx convention and put it in a separate file: app/todo/Controller.js.

The widget

As with the hello widget, our Todo widget is also wrapped in a cx tag, which denotes that it's a Cx widget.

The widget itself is composed of several components. Top-most element, HtmlElement of type div has the controller attribute set to our Controller class. This means that an instance of this class will be in charge for this view's behavior logic, in this case, data initialization and event handling. The same controller instance is passed to all descendants of the div element (see Controller documentation for more details).

The next interesting component is a TextField. This is a place where the user will enter new items for the Todo list. We're binding a value from this field to the value of $page.text value in the application store. This is a two-way binding—whenever a value of $page.text changes, the field will reflect the change; also, as the user types the text into the field, a value of $page.text will change accordingly.

A Button element is a Cx wrapper around HTML button input. By clicking on this particular button, the user will add the item from the text field to the list of the all Todo items, so we need to assign it the appropriate click handler. By setting its onClick attribute to a simple string value of "onAdd", we're connecting the button's click event to the method of the controller that was passed down to the button from the div above, which we will explain shortly.

To disable the button when the text field is empty, we bind its disabled property to an expression. If the result of calculating this expression is truthy (meaning, the text is empty), the button will be disabled. This is a very simplistic validation strategy; in real-world applications, we will use something more flexible, like Validation groups.

The last part of our Todo widget is the actual dynamic list of Todo items. Here, we use a Repeater component to iterate through all items of a collection like this one:

[
    {
        id: 1,
        text: 'Create GitHub project',
        done: true
    },
    {
        id: 2,
        text: 'Explain parts of the application',
        done: false
    }
]

This collection will be present in the store under the name $page.todos, so we're binding repeater's records attribute accordingly. Repeater component will iterate through the collection, instantiating the content within (in this case, an li item), once for each element of the collection. While doing so, it exposes two values to the store of each instantiated component:

  • $record is bound to the current item in the collection,
  • $index holds the index of the current item in the collection.

The li element for each item in the $page.todos collection contains a checkbox and a button. The text property of the checkbox is bound to the current record's text property ($record.text), so that it shows task name. In this case, we're using one-way binding in form of a template which is generally more suitable for displaying formatted text (see Templates). However, in this basic example (template is just a value), we could have easily used a simple binding, too.

Property value is bound to the $record.done value, so that the checkbox appears checked if the task is marked as done, and $record.done will change whenever the user changes the checkbox state. We're also binding CSS class name of the checkbox element like this:

{ "css-task-done": { bind: "$record.done" }}

This means that the class name css-task-done will be applied to the checkbox element, only if the $record.done value is truthy. This is just a more verbose syntax of the same binding mechanism (applying binding configuration object to the property, instead of a more common -bind syntax). As expected, class name will be recalculated every time $record.done value changes (for example, when the user clicks on the checkbox). We can use this class in our stylesheet to display completed tasks as stricken through.

The button is used for removing the task from the list. Its onClick property is connected to the controller's onRemove handler which we will explain shortly. In this case, we used a simple HtmlElement button, since we don't need Cx styling applied to it (we will add our own styling to it later).

app/todo/index.js
import { HtmlElement, Repeater, TextField, Checkbox, Button } from 'cx/widgets';
import Controller from './Controller';

export default <cx>
    <div class="csb-todo-wrap" controller={Controller}>
        <div class="csb-todo">
            <h1>Todo list</h1>

            <div preserveWhitespace>
                <TextField style={{width: 320}}
                        value-bind="$page.text"
                        placeholder="Type a task name here"
                        required
                />
                <Button type="button" onClick="onAdd" disabled-expr="!{$page.text}">Add</Button>
            </div>

            <ul class="csb-task-list">
                <Repeater records-bind="$page.todos">
                    <li class="csb-task">
                        <Checkbox class={{ "css-task-done": {bind: '$record.done'} }}
                                text-tpl="{$record.text}" value-bind="$record.done"/>

                        <button onClick="onRemove" text="x"/>
                    </li>
                </Repeater>
            </ul>
        </div>
    </div>
</cx>;
Copied!

The controller

Although it's possible (and easy) to connect component handlers directly to functions, typically within the same module where the widget resides, for larger applications it makes sense to concentrate the behavior logic in a controller, and that's what we're showing in this example, too.

Upon the controller's instantiation, the init method is called and, in this method, we can perform initialization our widget requires. Here, we're just making sure our $page.todos collection is well-defined. If the collection does not exist in the store, or if it's empty, we reset it to the default (a collection of one element).

We've seen that one of our widget's buttons has a callback set to the controller's onAdd method. In this method, we want to add a new item to the $page.todos collection, by the following steps:

  • we define an id for the new item (we take the maximum id value from the existing records and increment it by one);

  • we're adding a new item to the $page.todos collection (with the new id and the text set to the text field value read from the $page.text store value bound to it). A new collection is created by concatenating this new item to the current list of items and this new collection is set as the new value of $page.todos in the store;

  • we clear the $page.text value, so that the text field is cleared.

We also need the code for removing a task from the list. Signature of the onRemove method shows that button's onClick handlers receive two parameters: an event object and a target component instance (which contains properties like store, controller, etc.). Since the button is a part of the repeater's iterated content (an li instance), its store will have $record and $index values defined, set there by the repeater. We use the $record.id value to identify the record associated with the button clicked (the record we want to remove from the collection). Then we just create a new collection by filtering out the given record and set that new collection as a new value for $page.todos.

Note that we don't need to set up anything in the controller for the "mark as done/undone" functionality—it all works simply by means of data binding.

app/todo/Controller.js
import { Controller } from 'cx/ui';

export default class extends Controller {
    onInit() {
        var items = this.store.get('$page.todos');
        // Reset the list to default data if it's empty
        if (!items || !items.length) {
            items = [{ id: 1, text: 'Create a demo app', done: true }]
            this.store.set('$page.todos', items);
        }
    }

    onAdd() {
        var items = this.store.get('$page.todos');
        var id = items.reduce((acc, item) => Math.max(acc, item.id), 0) + 1;
        items = items.concat({
            id: id,
            text: this.store.get('$page.text') || `Untitled (${id})`,
            done: false
        });

        this.store.set('$page.todos', items);
        this.store.delete('$page.text');
    }

    onRemove(e, {store}) {
        var id = store.get('$record.id');
        var items = this.store.get('$page.todos');
        this.store.set('$page.todos', items.filter(item => item.id !== id));
    }
}
Copied!

Now, let's use our newly created Todo widget in our application in place of the hello widget we defined earlier. In the app/index.js file, we need to import the widget and pass it to the startAppLoop as the root widget of the application.

We can now run our application again (npm start) to make sure everything is working correctly.

Todo list

app/index.js
...
import Todo from './todo';
...
stop = startAppLoop(document.getElementById('app'), store, Todo);

Also, we want to style the widget a bit, so we will add the following line in the app/index.scss file and create the new app/todo/index.scss file. In this file, we can add any style definitions concerning this particular widget. A sample app/todo/index.scss can be downloaded from here.

app/index.scss
@import "~cx/src/variables";
@import "~cx/src/index";
@import "todo/index";
Copied!

After these changes, the new look should be applied immediately, and we can play with it until we get the final version of our application that we're satisfied with.

Cx Starter Kit

This concludes our tutorial in which we showed how to create a Cx application from scratch and explained its main elements.

For new projects, however, it is much more convenient to use our Cx Starter Kit boilerplate application which has all of the elements described here (plus many other features needed for building large client-side applications, such as routing, application layout, ready-to-use design, consistent folder structure, etc.) already set up.

git clone https://github.com/codaxy/cx-starter-kit