yellow tutorial

sign in

Yellow is an engine for web sites with pages linked by "back" and "next".

Let's call such a web site a "story". In a story, the order of pages and their construction is defined by a javascript program. Let's call the program "the script".

This page gives you a step-by-step introduction into making a story and how stories work.

get a login

You need a login. There is no online registration (yet). Send me an email telling me your gmail user (like ted@gmail.com), and I will enable login for you.

create your space

Once you have a login, create your story space. A story space is an identifier. For example, mine is "alex" and all my stories have URLs that start with https://www.yellow.cat/stories/alex.

Go to /stories. Choose a space name and see if it's still free. If it is free, you'll be able to create your space. Otherwise, choose a different space name.

Your email may be ted@gmail.com and your space may be "ted". Then all your story URLs will start with "https://www.yellow.cat/stories/ted", and that URL itself will show you a listing of your stories.

create a story

If your space is called "ted", then the URL https://www.yellow.cat/stories/ted is your space, a list of your stories.

Initially, your space will be empty (no stories).

You could upload a whole story as a zip if you already have files that make up a story.

But for now, let's make a fresh one. Choose a name, for example "simple" and click "create".

Now you will see one story in your space, in the first and only line of the list. The link "simple" takes you to editing it. The link "download" lets you download the story as a zip. The link "run!" lets you run the story, that means, execute it as if you were one of the users that will visit your story.

For now, click "simple", to have a look at the story. You will see a page with title "Story simple". Again, it has a link for downloading the story as zip, and for running it. Then there is a list of all files and directories in the story. At the moment there is just one file simple.js, and some input-elements for creating or uploading files and directories.

Let's have a look at the file simple.js. The new story was created with a minimal script that can give us a first feel for how yellow works, and this files simple.js is that script. The name of the script is the same as the name of the story. This is the script:

			var d = new Document()
			d.title = "first"
			d.show()
	
			d.title = "second"
			d.show()
		

How would such a story run? When a fresh visitor comes to this story, the yellow engine executes this script from the top: It instantiates a Document object and assigns it to a variable. A Document object is an html web page, as a javascript object, just as the built-in object document that you have in client-side javascript. Then the script sets the title of the document to "first".

Then it shows the document. Showing a document means the yellow engine interrupts javascript execution, converts the document object to html, and sends the html to the user's browser.

In the browser, the document source will be something like this:

			<!DOCTYPE html>
			<html>
				<head>
					<title>first</title>
				</head>
				<body>
					<h1>Title</h1>
					<button id='back'><</button>
					<button id='next'>></button>
				</body>
			</html>	
		

The title is "first" as our script set it. (Browsers show the title above the page, in a tab.)

Document instantiation gave us a little bit for free: The document has an h1 element containing "Title" and buttons for back and next. It should look like this:

Title

run the story

See if you can run the story. You can click the link "run!" which will run the story in a fresh browser tab. In general, the URL of the story is http://www.yellow.cat/stories/ted/simple/ and that is the URL that you would have to communicate to the users that you want to run the story, by email, or messaging, or whatever channel.

When the user clicks the "next" button, the yellow engine resumes executing the script where it left off. It will set the title to "second", and show the resulting document. Again, that means interrupting script execution and sending the document as html to the user's browser.

The user will see a page that looks the same, but with a different title in the tab above the page: "second".

From the second page, the user can go back to the first page by clicking "back". Going back tells the yellow engine to recover the state of the script as it was at the previous d.show() and send that document to the browser.

Going next from the first page to the second, and going back from the second page to the first works, and makes sense. But going back from the first page or going next from the second page does not, so let's change the script to disable these movements. While we're at it, let's make it all a bit more obvious by setting the h1 element too. Edit simple.js to get something like this:

			var d = new Document()
			var h1 = d.getElementsByTagName("h1").item(0)
			var back = d.getElementById("back")
			var next = d.getElementById("next")

			d.title = "first"
			h1.textContent = "first"
			back.disabled = true
			d.show()

			d.title = "second"
			h1.textContent = "second"
			back.disabled = false
			next.disabled = true
			d.show()
		

After editing the script, run it again. However, the story that a user is running, is stored in their session. Even if the story author edits the story, that does not the users that are already running it, because they have a copy in their session. The editing will only affect future users with new sessions. Therefore, in order to see these change, you have to open the story URL (similar to https://www.yellow.cat/stories/ted/simple/) again, but with a fresh session. You can use a different browser or open it in a fresh anonymous browser window.

In this example, you have seen methods like getElementsByTagName and setDisabled. How can you know which methods there are? The methods that are available here are (almost) exactly the ones specified in DOM level 3 Core. And by the way, the version of javascript used here is old: ECMAScript (ECMA-262 3rd edition).

document templates

Now, in principle, you could construct the html document that you want in the script by calling all the methods described in the DOM-API. But that would be completely unpractical. It is only really practical for small changes to existing documents, such as the ones we have seen.

It is much better to construct a document from a template. Let's make a new story with a template. Make a story called, say "template". In your story, create a text file called "template.html" and open it for editing. Give it content like this:

			<!DOCTYPE html>
			<html>
				<head>
					<title>ted</title>
				</head>
				<body>
					<h1>ted</h1>
					<button id="back" onclick="back()"><</button>
					<button id="next" onclick="next()">></button>
				</body>
			</html>
		

In this template, there are the two buttons for next and back. They call javascript functions back() and next() which are not defined in the template. They are part of the yellow engine. Before the yellow engine sends this template to the user, it will insert definitions for these functions. (If you want, you can define buttons or widgets differently. Whenever you want to advance, your template must call next() and for going back, back().)

Also edit the script template.js, so that it instantiates a document from the template. You only need to change the first line.

			var d = new Document("template.html")
			d.title = 'first'
			d.show()

			d = new Document()
			d.title = 'second'
			d.show()
		

When you run the story, you'll see that the first page that is shown to the user is our template, with only the title changed.

For convenience of the story author, there is a better way of injecting data into the template. It can show current values of script variables. Edit the script to add a first line like this:

			var name = "Edward"
		

and change the template, substituting "ted" by "${name}", like this:

			<!DOCTYPE html>
			<html>
				<head>
					<title>${name}</title>
				</head>
				<body>
					<h1>${name}</h1>
					<button id="back" onclick="back()"><</button>
					<button id="next" onclick="next()">></button>
				</body>
			</html>
		

Now, if you run the story, the h1 element will show the name "Edward".

input

By now we have:

We still need a simple way to get data from the user. We will use input elements for that. When a script execution is interrupted, and the current document is shown to the user, the user can enter data into the input elements on the document. The yellow engine sends the data to the server, and puts it into javascript variables in the script.

Let's see an example. The simplest case is a boolean variable, for which we use a single checkbox. Make a fresh story called "input", say. Add to it a template called "input.html" like this:

			<!DOCTYPE html>
			<html>
				<head>
					<title>input</title>
				</head>
				<body>
					<h1>input</h1>
					<input type="checkbox" name="accept"/> Do you accept?
					<button id="back" onclick="back()"><</button>
					<button id="next" onclick="next()">></button>
				</body>
			</html>
		

The template has a checkbox element with name "accept". When the user checks the checkbox (or doesn't), the yellow engine will set a javascript variable accept to true (or false). Let's add a second template to observe the result. Add a template called "result.html" like this:

			<!DOCTYPE html>
			<html>
				<head>
					<title>result</title>
				</head>
				<body>
					<h1>result</h1>
					Do you accept? ${accept}
					<button id="back" onclick="back()"><</button>
					<button id="next" onclick="next()">></button>
				</body>
			</html>
		

Connect the two templates in the script "input.js":

			var d = new Document("input.html")
			d.title = 'first'
			d.show()

			d = new Document("result.html")
			d.title = 'second'
			d.show()
		

Now run the story. The user can check the checkbox and go next, to see the result true. Then user can go back and uncheck the checkbox and go next, to see the changed result false.

It is as if in the script, during d.show(), we had a line like accept = true or accept = false. We could use the variable, and make the story sequence depend on it, for example in an if-statement like

			if(accept){
				contract.show()
			} else {
				goodbye.show()
			}
		

more about input

By now we have:

In the following, I'll explain in a more condensed format, more ways of getting data from the user, and later, more ways of constructing documents.

boolean input

For a boolean, use a checkbox. Its name-attribute is a javascript variable or left-hand-side expression, that is, an assignable expression like an object property or an array element. It must be the only input element with that name in the document. It must not have a value attribute.

			<input type="checkbox" name="accept"/>
		

It will be rendered checked or unchecked depending on the value of the name-variable. If there is no such variable, the variable will be created and initialised with true if the html has a checked-attribute, with false if it does not.

When the user checks or unchecks the checkbox, the javascript variable will be set to true or false, as if one of these assignments is carried out:

			accept = true
			accept = false
		

It is also possible to use a boolean checkbox with different left-hand-side expressions, like an object property (like person.accepted) or an array cell (like accepted[12]). Examples:

			<input type="checkbox" name="person.accepted"/>
			<input type="checkbox" name="accepted[12]"/>
		

In these cases, when the document is rendered, the object or the array must already exist, so that the left-hand-side expressions already are valid. During document.show(), they are assigned a boolean value.

text input

For a string, use an input of type text. Its name-attribute is a javascript variable or left-hand-side expression.

			<input type="text" name="surname" value="Smith"/>
			<input type="text" name="person.surname" value="Smith"/>
			<input type="text" name="names[2]" value="Smith"/>
		

It will be rendered with the value of the javascript variable. If there is no such variable, it will be initialised with the value attribute of the input element, or "" if there is none.

When the user edits the text in the input, the javascript variable will the set to the new value as soon as the focus leaves the input element.

password input

The password input works the same as text input, except that the string is rendered by blobs.

			<input type="password" name="pw" />
		

other input types

You can use the modern HTML5 types of input elements. Whether they are rendered well or not, depends on the browser. It seems that date, time, range, and color are more supported than the others. The yellow engine will process them in the same way as text input.

		search:	<input type="search" name="search"/>
		tel:	<input type="tel" name="tel"/>
		url:	<input type="url" name="url"/>
		email:	<input type="email" name="email"/>
		emails:	<input type="email" multiple name="emails"/>
		date:	<input type="date" name="date"/>
		time:	<input type="time" name="time"/>
		month:	<input type="month" name="month"/>
		week:	<input type="week" name="week"/>
		number:	<input type="number" name="number"/>
		range:	<input type="range" name="range"/>
		color:	<input type="color" name="color"/>
		

radio group or select

If you want to let the user choose one from a fixed list of values, you can use a group of radio buttons like this:

			<input type="radio" name="country" value="A"> Argentina
			<input type="radio" name="country" value="B"> Bolivia
			<input type="radio" name="country" value="C"> Chile
		

During document.show(), this will have the same effect as an assignment like country = "B".

Alternatively, you can use a select element.

			<select name="country">
				<option value="A">Argentina</option>
				<option value="B">Bolivia</option>
				<option value="C">Chile</option>
			</select>
		

The name-attribute has to be a left-hand-side expression in the javascript program, for example just a variable. When the select element is rendered, the LHS-expression determines which option should be rendered as selected. When the user selects a different option, the LHS-expression is assigned the value of that option.

textarea

If you want to collect a multi-line text from the user, use a textarea. It works the same as a text input. (In correct html, textarea requires a separate closing tag.)

			<textarea name="description"></textarea>
		

more about document construction

This subsection presents more details about how to write html templates.

$-expressions

We have already seen the $-expression ${name}. In general, you can write any valid javascript expression, using any variables that are defined at the point of document.show(). For example:

			${name}
			${person.age}
			${results[12]}
			${ (fahrenheit − 32) * 0.55}
		

A $-expression can appear inside the body of an element or inside the value of an attribute. It cannot appear as element tag or attribute key.

If the attribute is a boolean attribute according to the html5 specification, you can set it with a boolean value. For example:

			<input disabled="${b}" type="checkbox" name="accepts" />
		

will generate one of these two, depending on the boolean value of b:

			<input disabled="disabled" type="checkbox" name="accepts" />
			<input                     type="checkbox" name="accepts" />
		

data-if

We can make appearance of an element depend on some condition. For example, if the user has not yet accepted the conditions, we may want to show the checkbox again.

			<div data-if="!accept">
				<input type="checkbox" name="accept" /> I accept the conditions.
			</div>
	    

The condition ist written as a javascript expression as the attribute of the attribute "data-if". The attribute value is directly the expression, without ${ }. The value is evaluated on the server. If its result is true, then the element is rendered (without the attribute data-if). If its result is false, then the element is not rendered.

data-for-x

The attribute data-for-x allows us to repeat an element several times. The attribute key is completed by a variable, like x. The attribute value is a javascript expression that evaluates to an array. The html element is repeated once for each array element. During rendering of the element, the variable is assigned to one of the array cells. For example, within this script

			var xs = [0,1,2,3,4,5,6,7,8,9]
			var d = new Document('template.html');
			d.show()
		
this makes a list of items from 0 to 9:
			<ul>
				<li data-for-x='xs' >Item ${x}</li>
			</ul>
	    
You can combine data-for and data-if. This lists the even items 0, 2, 4, 6, 8:
			<ul>
				<li data-for-x='xs' data-if="x % 2 == 0" >Item ${x}</li>
			</ul>
	    

The example shows that an element can have both data-for and data-if. In that case, data-for is evaluated first. (Regardless of the textual order of the attributes in the html text.)

data-var-

We can define a local variable only for the rendering of a single element. Example:

			<span data-var-name = "first + ' ' + surname" >
				${name}
			</span>
		
After first = "Ted"; surname = "Williams" this will render
			<span>
				Ted Williams
			</span>
		

data-substitute

We can include one template in another. Example:

			<footer data-substitute="footer.html">
				Does not appear.
			</footer>
		

The element that carries the data-substitute attribute will disappear completely, and be substituted by the output of rendering the referenced other template. The value of data-substitute must be a literal string (not a javascript expression) that references another template. If could be in a subdirectory: data-substitute = "includes/footer.html".