OOUI Basics: Part 1

ToDo List App

In this tutorial we'll walk through creating a simple JavaScript ToDo app with the OOUI library, which was created by the Wikimedia Foundation. OOUI has a lot of potential for super-powerful JavaScript applications in your browser — so we will start small and grow as we go, hopefully giving you a taste of the library and its concepts.

Project Files


After installing OOUI in our project directory, we will start our project by creating these three files:

Add to the Project


We will now attach the CSS and JavaScript files, along with the OOUI files, to our HTML page. This is how our basic page should look:

<!doctype html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>ToDo OOUI</title>
		<meta name="description" content="A demo ToDo app made with OOUI">
		<meta name="viewport" content="width=device-width, initial-scale=1">

		<!-- jQuery -->
		<script src="node_modules/jquery/dist/jquery.min.js"></script>
		<!-- OOjs -->
		<script src="node_modules/oojs/dist/oojs.min.js"></script>
		<!-- OOUI -->
		<script src="node_modules/oojs-ui/dist/oojs-ui.min.js"></script>
		<!-- OOUI theme -->
		<script src="node_modules/oojs-ui/dist/oojs-ui-wikimediaui.min.js"></script>
		<link rel="stylesheet" href="node_modules/oojs-ui/dist/oojs-ui-wikimediaui.min.css">

		<!-- ToDo app custom -->
		<link rel="stylesheet" href="todo.css">
		<script src="assets/init.js"></script>
	</head>
	<body>
		<div class="wrapper">
			<h1>Demo ToDo app with OOUI</h1>
		</div>
	</body>
</html>

We will use the wrapper div element to inject our application into.

Building the Base


So now that we have our basic page, we need to start writing code. Our ToDo app should have two main pieces to start with: An input to add a new item, and a list displaying all items that have been added.

For the input We will need to use an OO.ui.TextInputWidget.

And since a ToDo list allows us to show a list of items that can be selected, for the list itself we will use an OO.ui.SelectWidget (You can see a demo of all OOUI widgets in the demo page).

Here is how you can add a SelectWidget and an TextInputWidget to your assets/init.js file:

$( function () {
	const input = new OO.ui.TextInputWidget(),
		list = new OO.ui.SelectWidget();

	// Append to the wrapper
	$( '.wrapper' ).append(
		input.$element,
		list.$element
	);
} );

Let's break this up and see what we did there. One of OOUI's principles is to separate the data from the UI, so each one of the widgets we're creating is first and foremost an object that is separate from the DOM. That object contains the DOM element itself in the $element property, which we use to attach to the document, but the behavior itself (as we will soon see) is done through the general OOUI object.

So in short, we created two widgets—a text input and a select widget—and then attached their $element to the document. If you load your page, it should have the title and an input. The list is invisible because we don't have a way to add elements to it yet—so let's do that now.

Adding Items to the List


We have our input, and we have the list, and now we need to connect them. OO.ui.TextInputWidget emits several events. One of them is simply "enter" when the Enter key is pressed (You can see all events in the documentation). Let's make our input add an item to the list when we hit the "enter" key. Since the list is an OO.ui.SelectWidget we should add into it an OO.ui.OptionWidget.

// Respond to 'enter' keypress
input.on( 'enter', function () {
	// Add the item
	list.addItems( [
		new OO.ui.OptionWidget( {
			data: input.getValue(),
			label: input.getValue()
		} )
	] );
} );

That would add an item to the list. Try it out! Add a ToDo item and click Enter in the demo box below:

Add Highlights for Hover and Select Indication


You can make your ToDo list items change color to indicate an item has been hovered or selected. We'll achieve this effect by overriding existing selectors. Add the following code to your todo.css file:

.oo-ui-selectWidget-unpressed .oo-ui-optionWidget-selected {
	background-color: #80ccff;
}

.oo-ui-optionWidget-highlighted {
	background-color: #b9e3ff;
}

Now try it out:

Unique Values


But what happens if we try to add an item that already exists in the list? Let's add a condition that checks whether the item exists first before it is added. Let's also prevent adding an empty input.

Update your assets/init.js file to look like this:

// Respond to 'enter' keypress
input.on( 'enter', function () {
	// Check for duplicates and prevent empty input
	if ( list.findItemFromData( input.getValue() ) ||
			input.getValue() === '' ) {
		input.$element.addClass( 'todo-error' );
		return;
	}
	input.$element.removeClass( 'todo-error' );

	// Add the item
	list.addItems( [
		new OO.ui.OptionWidget( {
			data: input.getValue(),
			label: input.getValue()
		} )
	] );
} );

Now we can only add unique items to this list. When an illegal value is added (an empty input or an existing item), we attach the class .todo-error to the input.

For it to actually show something, we need to define it in our CSS file. Add this to your todo.css file:

.todo-error input {
	background-color: #ff9696;
}

Let's try out a live demo of this version, and see if it behaves as expected:

It works! Now, let's add a little bit of extra flair to the app.

More Custom Styling


Let's make sure that our list and our input are styled a bit better, and add a placeholder test to the input. Let's go back to the piece in our init.js file where we created the widgets, and add configuration to both:

$( function () {
	const input = new OO.ui.TextInputWidget( {
			placeholder: 'Add a ToDo item'
		} ),
		list = new OO.ui.SelectWidget( {
			classes: [ 'todo-list' ]
		} );

	// code continues...
} );

The above configuration adds a CSS class to the list widget and a placeholder to the text input widget.

In the part of our init.js where we've added items to the list, we can use input.setValue( '' ); to clear the input each time an item is added.

// ...code

		list.addItems( [
		new OO.ui.OptionWidget( {
			data: input.getValue(),
			label: input.getValue()
		} )
	] );
	input.setValue( '' );
} );

We can now go edit our todo.css stylesheet. Notice that we can also style the underlying objects, which (for now) we will do by calling their oo-ui-style class, similarly to what we did in the Highlights section.

.wrapper {
	width: 60%;
	margin-left: auto;
	margin-right: auto;
}

.todo-list .oo-ui-optionWidget {
	border-bottom: 1px solid #666;
}

One last thing: let's add in some padding to our CSS.

.oo-ui-inputWidget {
	margin-bottom: 0.5em;
}

.todo-list .oo-ui-labelElement-label {
	margin-left: 0.25em;
}

.oo-ui-labelElement .oo-ui-optionWidget {
	padding: 0.25em;
}

That's it. We now have a basic ToDo app. Yay!

Reload your own app and look at your wonderful result. Here is our final demo:

For your convenience, we included the complete code below.

The Complete Code


Here's the full code of our assets/init.js file:

$( function () {
	const input = new OO.ui.TextInputWidget( {
			placeholder: 'Add a ToDo item'
		} ),
		list = new OO.ui.SelectWidget( {
			classes: [ 'todo-list' ]
		} );

	// Respond to 'enter' keypress
	input.on( 'enter', function () {
		// Check for duplicates and prevent empty input
		if ( list.findItemFromData( input.getValue() ) ||
				input.getValue() === '' ) {
			input.$element.addClass( 'todo-error' );
			return;
		}
		input.$element.removeClass( 'todo-error' );

		// Add the item
		list.addItems( [
			new OO.ui.OptionWidget( {
				data: input.getValue(),
				label: input.getValue()
			} )
		] );
		input.setValue( '' );
	} );

	// Append the app widgets
	$( '.wrapper' ).append(
		input.$element,
		list.$element
	);
} );

Here's the full code of our index.html file:

<!doctype html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>ToDo OOUI</title>
		<meta name="description" content="A demo ToDo app made with OOUI">
		<meta name="viewport" content="width=device-width, initial-scale=1">

		<!-- jQuery -->
		<script src="node_modules/jquery/dist/jquery.min.js"></script>
		<!-- OOjs -->
		<script src="node_modules/oojs/dist/oojs.min.js"></script>
		<!-- OOUI -->
		<script src="node_modules/oojs-ui/dist/oojs-ui.min.js"></script>
		<!-- OOUI theme -->
		<script src="node_modules/oojs-ui/dist/oojs-ui-wikimediaui.min.js"></script>
		<link rel="stylesheet" href="node_modules/oojs-ui/dist/oojs-ui-wikimediaui.min.css">

		<!-- ToDo app custom -->
		<link rel="stylesheet" href="todo.css">
		<script src="assets/init.js"></script>
	</head>
	<body>
		<div class="wrapper">
			<h1>Demo ToDo app with OOUI</h1>
		</div>
	</body>
</html>

Here's the full code of our todo.css file:

.todo-error input {
	background-color: #ff9696;
}

.wrapper {
	width: 60%;
	margin-left: auto;
	margin-right: auto;
}

.todo-list .oo-ui-optionWidget {
	border-bottom: 1px solid #666;
}

.oo-ui-selectWidget-unpressed .oo-ui-optionWidget-selected {
	background-color: #80ccff;
}

.oo-ui-optionWidget-highlighted {
	background-color: #b9e3ff;
}

.oo-ui-inputWidget {
	margin-bottom: 0.5em;
}

.todo-list .oo-ui-labelElement-label {
	margin-left: 0.25em;
}

.oo-ui-labelElement .oo-ui-optionWidget {
	padding: 0.25em;
}