This entry is part 3 of 4 in the series Getting to Know xstyle

xstyle_transparentIn this post, we want to walk through how you would get started building an application using xstyle, an extensible CSS framework. xstyle provides extensions that allows us to declaratively describe an user interface, making use of data bindings, element generation, and components, giving us an elegant means to create an application. In this tutorial, we will be using xstyle with Dojo, and use xstyle completely for our UI. Before starting, remember that you can use xstyle to any degree desired. xstyle can simply be used to make CSS improvements, it can build more sophisticated UI components, and it can be used to describe the entirety of the user interface (combined with a JavaScript-driven data model). Here we will be looking at the application level usage of xstyle, and we will create an app with a simple list of contacts and a form to edit the contact.

Let’s start with some our basic HTML for loading dojo and xstyle:

<script src="dojo/dojo.js" 
          data-dojo-config="async: true, deps: ['xstyle/main']">
</script>
<link href="app.css" rel="stylesheet"/>
<body></body>

Now let’s start constructing our UI with xstyle. One of the key tools in xstyle for creating our interface is element generation. Now again, we could certainly create our app layout using HTML, which would be encouraged if it our were producing a semantically significant document, but in this case, we are creating an application layout for the purpose of presenting a set of JSON-originating data. We will start by creating a simple header, essentially the “Hello, world” of xstyle. We do this with the element generation syntax in xstyle, which involves use the => operator followed by CSS selector indicating what elements to create (much like the syntax used by put-selector):

body {
    =>
        h1 'Hello, world of my contacts'
}

Now let’s expand it to include our list of contacts and a form. We can create multiple sibling elements under the body by using the comma operator, so we will create a ul.list (a <ul> with class name of contacts-list) for the list of contacts and form for viewing the details of the contact and editing it.

body {
    =>
        h1 'Hello, world of my contacts',
        ul.contacts-list,
        div.form;
}

So far we have only generated a static set of elements. If we are going to be presenting dynamic data, we will need to connect to this data. We do this by using xstyle’s data bindings functionality. To create this binding, we first create a new definition, pointing to our data source module, and then referencing this data definition within our element declaration, using parenthesis to indicate a binding to our <ul> element:

contacts = module('my-app/contacts');
body {
    =>
        h1 'Hello, world of my contacts',
        ul.contacts-list (contacts),
        div.form;
}

xstyle will then generate (<li>) elements for each item in the contacts collection.

Now, let’s turn to our data source, and setup our data model that will drive this interface. Above, we defined that the contacts comes from the module my-app/contacts. Let’s create this module now. In our data model, we construct a Memory store, provide it with data, and query it, returning the results as our list of contacts:

my-app/contacts:
define(['dojo/store/Memory'], function(Memory){
    contactStore = new Memory({data:[...]});
    var contacts = contactStore.query({});
    return contacts;
});

Here we have created a data model. Since this is a Dojo object store, we could easily swap this out for a different store, like the JsonRest store, which will interact with the server.

With this in place, the data binding can now be used within the xstyle element declaration. However, we haven’t provided any information about how to render each of these contacts. Let’s do that now, by adding a nested rule for the ul (contacts) binding. Again, we are working by extending CSS, so this can be filled with CSS property declarations. However, one of the extensions provided by xstyle, is the each property that allows to specify further element declarations that will be rendered for each item in the data-bound collection. Here we will indicate that each item should be rendered as a <li> element, with contents consisting of the first name and last name of that item. We can also reference the current item with the item definition, using the xstyle property accessor /:

contacts = module('my-app/contacts');
body {
    =>
        h1 'Hello, world of my contacts',
        ul.contacts-list (contacts) {
            each: li (item/firstName + item/lastName);
        },
        div.form;
}

Also, since this is CSS, remember that we could can easily add additional styling information; to demonstrate, let’s put a border around the contact list:

        ul.contacts-list (contacts) {
            each: li (item/firstName + item/lastName);
            border: 1px solid #aaa;
        },

Before moving on to the form, we also want to add the ability to select items in our list, so that the selected contact can be displayed and edited in the form. To do this, we will use xstyle’s event registration properties. xstyle provides on-* properties that allow us to register event handlers. We will register the click event in our contact list user interface as triggering a select, and then define the select action in our model.

        ul.contacts-list (contacts) {
            each: li (item/firstName + item/lastName) {
                on-click: contacts/select(item);
            };
            border: 1px solid #aaa;
        },

In our model module we then add a select function to handle this action:

        contacts.select = function(item){
            contacts.set("selected", item);
        }

Now we should be ready for the form, which will be used to view and edit the contact that is selected in the above list. To accomplish this, we will be creating inputs in our form that are bound to the fields of the selected item. Again, we create this binding by simply referencing the target object or property. Since we are binding to an input, the binding is two-way, not only does the input render the value, but editing the input will change the value of the binding’s target. We will start our form by assigning the selected item, contacts/selected to the name selected for easy reference with the element generation. Then we can simply create our labels and inputs. We will also create a header showing the full name to help illustrate the bindings as well:

        div.form {
            selected = contacts/selected;
            =>
                h1 (selected/firstName + ' ' + selected/lastName),
                label 'First Name:',
                input[type=text] (selected/firstName),
                label 'Last Name:',
                input[type=text] (selected/lastName),
                label 'Email:',
                input[type=email] (selected/email);
        }

Next, we will add the ability to save this form. We will do this by setting up an event listener:

                ...
                label 'Email:',
                input[type=email] (selected/email),
                button.start-row 'Save' {
                	on-click: contacts/save(selected);
                };

And then in the contacts.js module, we add the save function, which will take the current values (which are automatically updated by the inputs), and put()s it back in the store.

    contacts.set('save', function(selected){
    	contactStore.put(selected);
    });

And next we will create a button for creating new contacts:

                ...
                label 'Email:',
                input[type=email] (selected/email),
                button.start-row 'New Contact' {
                	on-click: contacts/create();
                },
                button.start-row 'Save' {
                	on-click: contacts/save(selected);
                };

And then in the contacts.js module, we add the create function, which will set a new object to the selected object that is bound to the inputs:

    contacts.set('selected', {
    	firstName:"",
	lastName:""
    });

Form Validation

Most form-based applications will generally need validation, and providing instant validation of entries as the user moves between fields is important for a good user experience. With xstyle, we can accomplish this by adding validation logic, and binding validation results to an element for error messages. We will add a listener to field, checking the value each time it changes, and then set an error binding with the result. First, let’s create the error element, bound to the error value:

        div.form {
            selected = contacts/selected;
            =>
                h1 (selected/firstName + ' ' + selected/lastName)
                label 'First Name:'
                input[type=text] (selected/firstName)
                span.error (selected/firstName/error)

And now we add the validation logic to our data model, in contacts.js:

	var firstName = contacts.get('selected').get('firstName');
	firstName.receive(function(value){
		firstName.set('error', value && value.length < 3 ? 
			'Name must be at least three characters' : '');
	});

Now, changes to the firstName will go through validation, and be synchronized to the error element we created.

Navigation

Another critical aspect of modern web applications is proper use of browser navigation, so that back, forward, and bookmarking work correctly. In this application, we are basically navigating to different users, and we can easily track this navigation with hash-based URL changes by simply apply dbind's "navigation" module. The navigation module takes a binding object that it can attach, and store to lookup objects by URL's hash. This can be setup by simply running:

define(... "dbind/navigation"), function(... navigation){
	...
	navigation(contacts.get('selected'), {
		store: contactStore
	});

With this in place, each selection of a user will result in a change to the URL (hash). We can then use the back and forward to navigate back to previously selected users, and even bookmark the selection of a user.

Conclusion

This introduction to building applications will hopefully give you some ideas on how you can use xstyle that are clearly written, leveraging xstyle's capabilities like CSS extensions, bindings, expressions, and element generation, and demonstrating a clean, distinct separation of presentation and data model.