Building UI Components with xstyle

By on September 17, 2013 8:36 am

xstyle_transparentIn this post, we will walk through creating widgets, or UI components in xstyle. CSS itself has a basic component-like unit, a CSS rule. However, xstyle enables a level of advanced UI for constructing rich components, beyond what can be specified with basic CSS. The simple extensions in xstyle allow us to generate DOM elements, respond to events, and encapsulate functionality for easy reuse and composition.

Traditionally, widgets in web applications have been created through JavaScript constructors. Typically JavaScript instances are created as an abstraction from the underlying DOM elements. This pattern is used in many frameworks, and certainly has been a useful concept for web application development. However, this approach suffers from several fundamental limitations. First, toolkits generally advise against the use of JavaScript constructors for minor elements, like headers and anchors. In dgrid, we usually advise against using widgets in every row, suggesting plain inputs instead. This means the JavaScript interface applies for some components, but other elements necessitate direct API interaction. The JavaScript interface is discontinuous because it simply won’t scale to the number of elements on a typical page.

This issue is exacerbated in mobile application where performance and memory is more constrained, but the modern capabilities mean more native elements can be used without full-blown widgets. Mobile web applications often prefer cleaner, simpler elements that can be described with native elements and CSS. Attempting to abstract all of these through a JavaScript interface often means either a gratuitously heavy interface or sharp discontinuities in the UI interface.

Even in full JavaScript enhanced widgets, significant portions of the UI are most efficiently and appropriately described through CSS. Some frameworks try to keep a pure JavaScript interface for all styling, leading to heavy reliance on inline styles instead of best practice CSS usage. Other toolkits do encourage CSS usage, but again, there means that presentation definitions are split between the JavaScript and CSS interface. Again, looking at the trends in browser development, CSS capabilities are growing at an amazing pace, and we can and should increasingly lean on these capabilities.

Creating a Component

In this tutorial, we will create a common UI element, a simple popup menu. For background information, read our post on getting started with xstyle, or the documentation.

Again, the basic unit of a UI component in CSS is simply a rule. Before looking at how we can improve upon this, it’s worth noting the advantage CSS intrinsically provides in selecting elements for applications; we don’t need a separate step to query the DOM for nodes and iterate through these to apply a widget constructor. The selector handles the application of presentation definition for us.

Now of course, a simple CSS rule is very limited. Traditional CSS allows us to use some predefined properties to style an element, but it lacks composition capabilities. One of the first important concepts that xstyle provides is the ability to create new named definitions that we can reference and reuse. Here we create a definition beginning with a regular CSS rule, but by applying the = operator, our new definition can be referenced like a new element or used as a property in another CSS rule. Here we define our new component, calling it a “popup-message”:

	popup-message = {
		...
	}

The next key construct that xstyle introduces for defining components is the ability to compose a component from child elements. This is achieved through xstyle’s element generation syntax. To begin, we create the sub-elements using standard CSS selector syntax. Additionally we can define text nodes with string literals. We will define a button element and div element (for the popup) as the children of our component. Element generation can use whitespace to denote hierarchy, here creating the elements as siblings that each have a text node child:

	popup-message = {
		=>
			button 'More information'
			div 'Here is more information';
	}

It is worth noting that element generation is also a very convenient way to use our new component definitions. For example, we could add a popup-message to our page’s footer element:

#footer {
	=>
		popup-message;
}

Next, xstyle allows us to style these elements directly inline as a nested rule. This is a natural, intuitive way to add another rule, within the stylesheet without having to create a new selector. Here we define our popup layer, setting as an absolutely positioned element and initially hiding it:

	popup-message = {
		=>
			button 'More information',
			div 'Here is more information' {
				position: absolute;
				display: none;
			};
	}

Building reusable components is more than just encapsulation. Here we have hard-coded in our text values, but to make this usable, we want to parameterize these so that this new component can be used with different values and settings. We can do this by creating new property variables. We will use a “label” property variable and a “content” property variable for defining the text of the button and the popup. We then use these variables within our element generation and xstyle will bind the values to the parent elements. We will also define a popup-background-color property to allow for easy adjustment of the popup’s background color, and use the standard CSS variable syntax to reference it. The “content” variable is implicitly defined, but we will explicitly create label and popup-background-color properties with default values:

	popup-message = {
		label=var: More information;
		content: Here is more information;
		popup-background-color=var: #aaa;
		=>
			button (label),
			div (content) {
				position: absolute;
				display: none;
				background-color: var(popup-background-color);
			};

These property variables can be used just like standard CSS properties, when we use (or extend) the popup-message component, and standard CSS properties can be used along with them. So now we can go back to our usage of popup-message, and provide specific values for these properties (note that we aren’t specifying a label here, we will just use the default):

#footer {
	=>
		popup-message {
			/* use our new properties we defined */
			popup-background-color: blue;
			content: Contents of another popup;
			color: #123; /* we can still use standard CSS properties as well*/
		};
}

The “content” property that was used has a special meaning, denoting the main contents of the component. In the example above, we used the “content” property as a CSS property. However, with our component the content can come from the target node (the inner nodes of the target node), or if can be defined in our element generation. For example we could create a popup:

=>
	popup-message
		h1 'Header inside popup'
		p 'A paragraph inside a popup';

The next step in creating this component is actually showing the popup in response to the button being clicked. At this point, we should point out that the goal of xstyle is not to eliminate the use of JavaScript, but rather provide declarative, reactive definitions that scale across many elements, for aspects of a component that can be best described with these tools, complementary to JavaScript. However, when it comes to the imperatively updating the state of a component, this is clearly JavaScript’s domain and xstyle is designed to interface with JavaScript to handle this type of activity. We will create a JavaScript module to work with this definition. We can think of this as the “model” for our CSS “view”. This model may in fact be functioning as a “view” for other models; often it can be beneficial to have several view-model layers, as exemplified in an MVVM approach. To use this model module, we will introduce it into our component view definition we have created:

	popup-message = {
		popup-model = module('my-package/popup-model');
		...

And now of course we need to create the referenced module. In this model, we will create a toggle function, that will be triggered by the button, to show the popup message.

define([], function{
	return {
		toggle: function(event){
			var popupDiv = event.parentNode.lastChild;
			popupDiv.style.display = (popupDiv.showing = !popupDiv.showing) ? 'block' : 'none';
		}
	};
});

Next, we connect the toggle function as an event listener to the button. We could do this in the JavaScript module, but xstyle allows us to conveniently declare event listeners as well, again keeping the definition intuitively next to the element declaration. xstyle uses event delegation to listen for events, which consistently aligns with the CSS/xstyle model, with the event selector implicitly derived from the rule in which it is declared. Here we add a ‘click’ listener:

		popup-model = module('my-package/popup-model');
		=>
			button (label) {
				on-click: popup-model/toggle;
			},
			div (content) ...

We now have a functioning popup message when clicking the button. Of course there are countless ways that we could improve upon this component, with better styling and more features, but this gives you an introduction on how to construct reusable components with xstyle’s extensible CSS.

Next steps

  • Feedback. We’re looking for your feedback on xstyle. If xstyle interests you, please try it out and tell us what you think.
  • Getting involved. We’re also looking for help in adding features and refining xstyle. Let us know if you would like to get involved, either in the comments or via GitHub.
  • Support and development. Need professional help for your next project or your team? Tell us about your project and let’s discuss how we can help you get the most from the web platform.

Other posts in the series

Comments

  • Corey Alix

    After spending two days studying underscore.js, backbone.js, marionette.js, ractive.js, rxjs and then stumbling across this post (and finally reading it) I’m yet again struck by how much your solutions appeal to me. I’ve only used xstyle as a css loader but now I understand I can create UI components in my css. And dojo has an answer for almost all the problems those other libraries solve. But I’m still unclear on reactive declarations in dojo.

    I’m guessing xstyle would work with an input { on-change: model/update; } but is there a way to declare that the input be updated when the model changes? Maybe input { value: model/get; }.

    As I post this I realize this is part 4 of 4 and the answer to my question is likely found in part 3 (which does not contain the word ‘reactive’).

  • Guest

    I can’t seem to get the (content) working. I realize this is ‘lightly’ tested but perhaps my syntax is off?

  • ca0v

    In the code below I cannot get the (content) working. It is always “Query Builder” even for a element. I realize this is ‘lightly’ tested but maybe my syntax is a bit off? I apologize for the formatting…not sure how to correct it.

    div.test {

    =>

    div.query-builder {

    content: ‘Test’;

    }

    }

    .query-builder {

    border: 1pt solid black;

    border-top-left-radius: var(border-radius);

    border-top-right-radius: var(border-radius);

    content=var: ‘Query Builder’;

    =>

    p.title (content),

    textarea,

    div.toolbar;

    }

  • ca0v

    Having no success with event handlers. I expect to see “execute” when I click the ‘Execute’ button but that is not the case. I believe this is the correct declaration so what might be wrong?

    [code]
    toolbar = {
    toolbar-model = module('app/toolbar-model');
    =>
    button.submit 'Execute' {
    on-click: toolbar-model/execute;
    }
    }

    Module loads but click is not hooked up:

    {code}
    define([], function() {
    return {
    execute: function (event) {
    alert("execute");
    }
    };
    });

  • ca0v

    Couple things to note:

    * adding **()** after these methods will invoke them, e.g. toolbar-model/execute()
    * the tests from https://github.com/SitePen/xstyle pass

  • kriszyp

    There are a couple of issues here due to the limitations of xstyle. The inheritance/overriding mechanism in xstyle is designed to allow you to override custom properties from *definitions* that you are extending, not simple from other arbitrary rules (which would be computationally very expensive). Second, this code is referencing a rule that it would extend prior to the definition being defined. Here is how this xstyle stylesheet could be written such the original definition’s ‘content’ could properly be overriden:

    query-builder = div {
    border: 1pt solid black;
    border-top-left-radius: var(border-radius);
    border-top-right-radius: var(border-radius);
    content: ‘Query Builder’;
    =>
    p.title (content),
    textarea,
    div.toolbar;
    }

    div.test {
    =>
    query-builder {
    content: ‘Test’;
    }
    }

    I am not sure if we could really solve the first limitation for all cases. I could potentially add support for being able to define override custom properties from particular, easily identified selectors, like class names, but there will still always be some combinations of selectors that could not be statically anticipated.

    The second limitation is probably something that could be overcome, although I think I need to evaluate the complexity implications of having to defer resolving definition references that may come later.

  • kriszyp

    In the latest versions of xstyle, the event handler value is evaluated as an expression when the event occurs, so you would want to write:

    on-click: toolbar-model/execute();

  • rafael

    Thank you for your imaginative ideas, for you work and for making it available. I first got to hear of xstyle when learning how to use dgrid. Do I understand correctly that xstyle will not be a dependency of dgrid in the future? And does this mean you have switched your focus to other projects?

  • Hi @disqus_fe73NckFBG:disqus , with dgrid, its dependency on xstyle was simply for its ability to load CSS files. With dgrid 0.5, we started using the Stylus preprocessor to be consistent with what we’ve been doing with Dijit. xstyle is a fairly large dependency for this relatively minimal usage of loading files, so we removed the dependency for the next release of dgrid. We’re still very excited about the promise of xstyle and its other uses, and would love to see more people that are interested in it contribute and get involved!

  • rafael

    Thank you for your reassuring answer. As I wrote, I ran into xstyle by accident through dgrid, but by reading its documentation I found it very interesting in itself. But I have no contribution other than encouraging words for the time being.