ID:34133
 
Keywords: tutorial
Click here for Part 2.
Programming & Designing Browser Interfaces: by Unknown Person

Programming & Designing Browser Interfaces

by Unknown Person

Controls

The great thing about using HTML with BYOND is that you get all of the advantages of form items such as buttons and dropdown menus. These items can be much more user friendly than just using alert() or input() and send a regular generic gray box to the user. Using the browser has much more potential of being more dynamic and customizable.

Simple Buttons

Buttons are going to be a common type of form item you may use when designing browser interfaces. Creating buttons is as easy as using the button tag, and writing the linking code on the event it is clicked. A generic button would looks like this:

<button>My Button</button>

Of course, this itself isn't very useful. You want the button to actually do something, such as buying an item or closing a window. In the previous chapter we talked about BYOND protocol URLs. Since we can't exactly put a hyperlink on a button, we simulate it by defining a javascript function on the event that the button is pressed. We will use the onclick parameter to write our javascript code that will send the user the BYOND URL.

<button onclick="window.location='byond://?action=myaction';">My Button</button>

This button would send the user the BYOND URL and would have the exact effect as a user clicking a hyperlink that linked to that URL. This itself will work fine, but when you start to have more buttons and functionalities for buttons, you will find it challenging to keep track of what each button does and what values are being sent through Topic(). To organize your button actions, you can define a javascript function to handle sending the links. When the button is clicked, you can just call the function with your specific parameters, and the function will construct and send the link for you. Here is a simple example of buttons that use a javascript function.

mob
Topic(href, list/href_list)
..()
var/action = href_list["action"]
var/value = href_list["value"]

switch(action)
if("shout")
world << "[src] shouts, '[value]!'"
if("close")
src << browse(null, "window=shoutbox")

verb/shoutbox()
var/text = {"
<html>
<head>
<title> Shout Box </title>
<script language="javascript">
function send(action, value) {
window.location="byond://?src=\ref
[src]&action="+action+"&value="+value;
}
</script>
</head>
<body>
Shout out some words: <br />
<button disabledclick="send('shout','Hello');">Hello</button>
<button disabledclick="send('shout','Hi');">Hi</button>
<button disabledclick="send('shout','Goodbye');">Goodbye</button>
<button disabledclick="send('shout','See you Later');">See you Later</button> <br />
<button disabledclick="send('close');">Close Window</button>
</body>
</html>
"}


src << browse(text, "window=shoutbox;size=500x300")
Example 3.1: A simple page where the user clicks a button that calls a command.

The send() function in JavaScript does the same thing as a regular hyperlink. The difference is that this javascript function is required when sending the user to a link on an event other than a hyperlink click, such as inputs. These functions are well suited for these controls (text boxes, check boxes, radios, etc). You may also make buttons by using the input tag.

<input type="button" name="mybutton" value="My Button" />

There are also other types of inputs, such as Submit and Reset buttons that go with forms. Those types of buttons will be explained in the next chapter.

Textboxes

In order to create other interesting form objects, we can use the <input> tag. The input tag supports many different types of controls such as buttons, text boxes, radios, check boxes, and other sorts. The type parameter of the input tag determines what type of control it is. So if we wanted a box for the player to type their name, it would be a text box and would look like this:

<input type="text" name="name" value="John Doe" />

name is the name we have defined this text box's id to be, which will be used to determine what variable we will modify when it is sent through Topic(). value is the initial value the text box will hold. The bit of HTML wouldn't do anything since we did not define a javascript function to go with it like what we did with our buttons using the onclick parameter. We wouldn't be able to use our old send() function because the parameters were defined for static values. Inputs store and let the users input dynamic values, so we will need to define a new function that sends the values of the input's "name" and "value" variables. The javascript function would look like this:

function set(input) { window.location="byond://?src=\ref[src]&action="+input.name+"&value="+input.value; }

The javascript function takes the reference of the input object through the parameter, and sends the input object's name and value through a BYOND link. The program will then be able to catch those values, and do the appropiate checks through Topic(). The final <input> tag would look like this with the function defined in onchange.

<input type="text" name="name" value="John Doe" onchange="set(this);" />

With this information in mind, you can use these inputs to create a type of interface you may see on web registration sites. Other types of inputs you may see are radios, checkboxes, password inputs, and dropdown lists. The construct of a basic text box input applies to many other simple controls. Radios and password inputs use the same implementation as regular text box inputs since each has a name and a given text value that is assigned to it. Radios may seem to need a different implementation, but it is not required because you can use the same set() function in these as well.

Checkboxes

Checkboxes use a slightly different method to handle with DM and JavaScript. A checkbox's input is defined as checked, or not checked. You can see if a checkbox is checked by looking at the input's checked parameter. This way, we will have to define another javascript function and send whether the checkbox is checked through the value parameter in Topic(). The following shows the implementation of a new check() function defined for checkbox inputs, and how you would write a checkbox input in HTML.

<!-- Checkbox implementation --> function check(input) { window.location="byond://?src=\ref[src]&action="+input.name+"&value="+input.checked; } <input checked type="checkbox" name="happy" onclick="check(this);" /> Happy

The checkbox's "checked" variable can either be "true" or "false". In the above code, if you supply the "checked" parameter, it will have a value of "true". You need to be aware of this, since the value sent through Topic() will actually be the text string "true" or "false". You also should realize that you will need to use the onclick parameter rather than onchange since a checkbox's state changes when it is clicked. The value sent through Topic() is the new value of the checkbox's checked variable, so you can set whatever variable you want to the value of the checkbox's checked value (1 for "true", 0 for "false")

Radios

Radios are similar to checkboxes in their syntax, but have separate functions. Unlike checkboxes, only one radio of the same name may be checked at the same time. These are very useful types of controls for letting the user select one value from a list. To create a radio, we use the input tag with "radio" as the type.

<input type="radio" name="number" value="one" /> One <br /> <input type="radio" name="number" value="two" /> Two <br /> <input type="radio" name="number" value="three" /> Three <br />

Like checkboxes, you assign a javascript function on the event a radio is clicked. You must do it for every single radio you define. A radio is defined as selected if the selected parameter is written.

<input selected type="radio" name="number" value="one" onclick="set(this);" /> One <br /> <input type="radio" name="number" value="two" onclick="set(this);" /> Two <br /> <input type="radio" name="number" value="three" onclick="set(this);" /> Three <br />

Dropdown Lists

Dropdown lists or dropdown boxes are another method of allowing the user to select from a list of options. They are very alike to radios in the way that they only allow users to select what is given to them. Dropdown lists uses two tags: the <select> tag, and the <option> tag. The following is the anatomy of a dropdown list:

<select name="number" onchange="set(this);"> <option value="one"> One</option> <option value="two"> Two</option> <option value="three"> Three</option> </select>

The select tag is essentially the body of the dropdown list. The options of the list are determined by the contents inside of the select tag. The option tag resides inside, and the text shown in the list is what is in the option tag, sharing its syntax with buttons. The javascript function is defined inside of the select tag, and uses the same set() function we used in previous examples, since all that needs to be sent is a name and value. The name must be defined in the select tag and every option tag, while the value for each is defined inside of the option tag. The value for the option does not need to be the same as the text that is displayed in the HTML.

Dropdown lists also have the potential of being able to select multiple items. This is a selection list, and they will be explained in detail in the next chapter.

Other Controls

There are a number of other types of controls that you can use in your browser forms. Most of the simple ones such as password inputs and textarea boxes are very similar to a regular text input, and will use the same javascript functions. The <textarea> input looks slightly different and the syntax looks like a button. Your initial value for a textarea resides inside the opening and closing tag. Even though there is no value parameter inside of the textarea tag, the parameter is essentially what is inside of the tags.

<textarea rows="5" cols="50" name="comments" onchange="set(this);">Here is my comment</textarea>

Other more subjects such as dynamic control events and content in synchronization with BYOND will be discussed in the later chapters. The following snippet is an example of a practical usage of all controls that have been discussed in this chapter, excluding dropdown lists.

var/const
VEGAN = 1
RICH = 2
TIRED = 4
HUMBLE = 8

mob
var
firstname = "John"
lastname = "Doe"
age = 18
stats = RICH | TIRED
desc = "I am the average joe."

Topic(href, list/href_list)
..()
var/action = href_list["action"]
var/value = href_list["value"]

switch(action)
if("firstname") firstname = value
if("lastname") lastname = value
if("age") age = text2num(value)
if("gender") gender = value
if("desc") desc = value

// turn flag on when box is checked (value is "true"), off otherwise
if("vegan") stats = (value == "true") ? (stats | VEGAN) : (stats & ~VEGAN)
if("rich") stats = (value == "true") ? (stats | RICH) : (stats & ~RICH)
if("tired") stats = (value == "true") ? (stats | TIRED) : (stats & ~TIRED)
if("humble") stats = (value == "true") ? (stats | HUMBLE) : (stats & ~HUMBLE)

if("close")
src << browse(null, "window=settings")
src << "Welcome, [firstname] [lastname]."

verb
settings()
var/html = {"
<html>
<head>
<title> Settings </title>
<script language="javascript">
function set(i) {
window.location="byond://?src=\ref
[src]&action="+i.name+"&value="+i.value;
}
function check(i) {
window.location="byond://?src=\ref
[src]&action="+i.name+"&value="+i.checked;
}
function send(action, value) {
window.location="byond://?src=\ref
[src]&action="+action+"&value="+value;
}
</script>
</head>
<body>
<b>Settings</b><br />
First Name: <input type="text" name="firstname" value="
[src.firstname]" disabledchange="set(this);" /> <br />
Last Name: <input type="text" name="lastname" value="
[src.lastname]" disabledchange="set(this);" /> <br />
Age: <input type="text" name="age" value="
[src.age]" disabledchange="set(this);" /> <br />
Gender: <input
[src.gender == "male" ? " checked" : ""] type="radio" name="gender" value="male" disabledchange="set(this);" /> Male
<input
[src.gender == "female" ? " checked" : ""] type="radio" name="gender" value="female" disabledchange="set(this);" /> Female
<input
[src.gender == "neuter" ? " checked" : ""] type="radio" name="gender" value="neuter" disabledchange="set(this);" /> Neuter <br />
I am ... (check those that apply): <br />
<input
[src.stats & VEGAN ? " checked" : ""] type="checkbox" name="vegan" disabledclick="check(this);" /> Vegan <br />
<input
[src.stats & RICH ? " checked" : ""] type="checkbox" name="rich" disabledclick="check(this);" /> Rich <br />
<input
[src.stats & TIRED ? " checked" : ""] type="checkbox" name="tired" disabledclick="check(this);" /> Tired <br />
<input
[src.stats & HUMBLE ? " checked" : ""] type="checkbox" name="humble" disabledclick="check(this);" /> Humble <br />
About Me: <br />
<textarea name="desc" rows="5" disabledchange="set(this);">
[src.desc]</textarea> <br />
<button disabledclick="send('close');">Close</button>
</body>
</html>
"}


src << browse(html, "window=settings;size=500x320")
Example 3.2: This segment of code uses all of the forms of controls we have discussed in this section, and simulates a generic "user settings" form you may encounter.

The example above uses simple text boxes, radios, check boxes, and a text area. The input for the first name, last name, and age uses regular set() javascript function we had defined earlier. The radios also use the same method as the text boxes since a radio is very similar to them; the only difference being that your choice is limited to the values given for each radio. You can see the implementation of the check boxes use the check() function we also defined earlier. This example uses bit flags to see whether one of those characteristics apply to the user. In Topic(), we can see that every checkbox has its own action text referred to it. When the checkbox is checked, the value sent is "true" and the appropiate flag is turned on while when the checkbox is false the flag is turned off. Optionally you can modify your check() function to make all checkboxes refer to one action, have a value for each, and then send its checked value for a total of 3 variables to send through Topic(). Remember that you are not limited to the amount of parameters you send through your BYOND URL, and it is up to you to decide how you would like to organize your links and structure of your code.

Section Review

  1. Rewrite example 3.2 using dropdown lists instead of radios when selecting your gender.
  2. Write a browser interface that displays all of the items in the player's inventory. A "Drop" button should be displayed beside the item, including its cost. If the item is a quest item, the drop button should be disabled. A "Close" button should be located at the bottom of the page. If the player has no items in his or her inventory, the form should display "No items available".

Forms

In the previous chapter, we have discussed about browser controls you may want to add into your various browser interfaces. These controls themselves are all independent, and take care of their own values. By using individual controls, it would not be easy to create a page that would set all user-inputted data once the user presses a submit button. However, by taking advantage of the <form> tag we can handle all of our controls very easily without using JavaScript and sending all data through one link.

The Anatomy of a Form

HTML forms are defined by using the <form> tag. Every form has a unique name that is used to identify it. A form itself isn't a control, but an object that handles all the controls inside of it for easy access. To make use of a form, we must define the form tag, and place controls inside of it.

<form name="myform"> <input type="text" name="mytext" value="Hello" /> </form>

However, just defining a form and some input controls would not do anything. We need to tell how the form is going to send the data, and how the data is going to be received. Since the form tag is something done in HTML, we need to tell the form to be able to send a BYOND URL. Easily enough, the action parameter of the form defines what URL the form will be sent to. The other thing we need to specify is the method of sending the data. BYOND only supports the GET method, and it is the only method where it is sent through client/Topic().

The last things we need are the buttons to manipulate the form data. If you've seen any generic form, almost all of them have a Submit and a Reset button. These are already defined by setting the type of input as "submit" or "reset", and already have their predefined behavior. A submit button sends all the data through client/Topic(), while the reset button sets all of the controls to their initial value. Here is a fully functioning HTML form with two text controls, a submit, and reset button that you may put in a web page.

<form name="myform" action="byond://" method="get"> Name: <input type="text" name="name" value="John Doe" /> <br /> Description: <input type="text" name="desc" value="I am a person." /> <br /> <input type="submit" value="Submit" /> <input type="reset" value="Reset" /> </form>

Form Communication

Like any other BYOND URL link, forms with the "get" method send the same type of data through client/Topic(). The form handles all of the data, and takes every input and sends it as a name=value associative list. This means that if we defined 3 text inputs named "a", "b", and "c", and we submitted the values "1", "2", "3" inside the respective boxes, the form would send the link "byond://?a=1&b=2&c=3" through client/Topic() when submitted. Parsing this data is very simple, and is just like sending regular data through a link. The only difference in this method is that the value of data is sent all in one link, and you can expect links to be large than usual if you are expecting to parse a lot of data. Inputs that are disabled or are not selected (unchecked checkboxes, unselected radios, etc) do not have their values sent through the link. Empty text inputs are sent with no value. One minor difference between forms and regular inputs is that when a checkbox or radio is selected, "on" is sent instead of "true".

client/Topic(href, list/href_list)
..()

src.mob.name = href_list["name"]
src.mob.desc = href_list["desc"]
src << "My name is [src.mob.name], and here is my description: [src.mob.desc]"

mob/verb/settings()
var/html = \
{"
<form name="myform" action="byond://" method="get">
Name: <input type="text" name="name" value="John Doe" /> <br />
Description: <input type="text" name="desc" value="I am a person." /> <br />
<input type="submit" value="Submit" />
<input type="reset" value="Reset" />
</form>
"}


src << browse(html, "window=settings;size=500x300")
Example 4.1: An example showing how you can parse the data sent from a form.

From this example, you probably would notice, "Hey, why are we using client/Topic()? How can we specify the src parameter in a form if we can't preprogram what parameters are sent?" The answer is to add hidden controls. Since forms take all names and values from any control, we can create a dummy control to hold data, such as the src variable and any other information we want sent through the form. We specify this input type as "hidden". Here is a rewritten example of the above code using mob/Topic() by creating a dummy input.

mob
Topic(href, list/href_list)
..()

src.name = href_list["name"]
src.desc = href_list["desc"]
src << "My name is [src.name], and here is my description: [src.desc]"

verb
settings()
var/html = \
{"
<form name="myform" action="byond://" method="get">
<input type="hidden" name="src" value="\ref
[src]" />
Name: <input type="text" name="name" value="John Doe" /> <br />
Description: <input type="text" name="desc" value="I am a person." /> <br />
<input type="submit" value="Submit" />
<input type="reset" value="Reset" />
</form>
"}


src << browse(html, "window=settings;size=500x300")
Example 4.2: A rewritten example of example 4.1 using mob/Topic() by defining a dummy input.

By using dummy inputs, we can send additional information when the user presses the submit button. For example, what if you wanted two forms or more in the same page? You can create a dummy input and send the name of the form with the data order to identify which form was submitted. Then when you are handling the data, you can handle two different functions depending on which form was submitted. If you did not send additional form data when submitting, you would not know which form was submitted.

Handling Data

Data can be handled in many ways. Since all of the data comes from Topic(), we generally don't need to have a different way to handle the data. However, unlike how we have been handling data with individual controls, we need to handle it in a more direct way. When we were parsing data , the controls always sent two individual items: and action, and a value. Depending on the action, we would change a certain variable. In forms, all of the information we receive is sent at the same time, which means if we handled the data the same way, we would need to assume that a certain piece of data was sent. A good way to handle data sent by a form is to change it into a list that we can loop through and handle each variable individually.

When form data is sent, the href_list variable holds all the data we need, and places everything in a very convenient list. What we need to do is split up the list so all of the form data is in a new list, while the src parameter is cut out. The new list would be all the data that was sent through the form. The list can then be looped through and taken care of separatly. This method is much more dynamic, and is much easier to expand on. It also is easier to implement data verification (which will be discussed in the next chapter). The following is a simple implementation:

mob
var
age = 20

Topic(href, list/href_list)
..()

if(href_list["form"]) // determine if the link is a form if the "form" param was sent
var/list/data = href_list.Copy(href_list.Find("form") + 1) // split the 'src' parameter
ProcessForm(href_list["form"], data) // process data

// display data
src << "My name is [src.name]."
src << "I am [src.age] years old."
src << "I am [src.gender]"

// close the form when submitted
src << browse(null, "window=settings")

proc
ProcessForm(formname, list/params)
for(var/item in params) // loop through each name=value item individually
ProcessVariable(formname, item, params[item])

ProcessVariable(formname, name, value)
// use the form name in order to have different functionality for different forms
if(formname == "settings")
switch(name)
if("name")
src.name = value
if("age")
src.age = text2num(value)
if("gender")
src.gender = value

verb
settings()
var/html = \
{"
<form name="settings" action="byond://" method="get">
<input type="hidden" name="src" value="\ref
[src]" />
<input type="hidden" name="form" value="settings" />

Name: <input type="text" name="name" value="
[src.name]" /> <br />
Age: <input type="text" name="age" value="
[src.age]" /> <br />
Gender: <select name="gender">
<option value="male">Male</option>
<option value="female">Female</option>
<option value="neuter">Neuter</option>
</select> <br />
<input type="submit" value="Submit" />
<input type="reset" value="Reset" />
</form>
"}


src << browse(html, "window=settings;size=500x300")
Example 4.3: Handling data individually by cutting href_list starting where the data is sent. The form name is sent via a hidden input.

The above structure of handling form data is very useful and is much easier to handle data. The variables are very easy to manipulate, and you are free to handle all of the data outside Topic(). This method makes data verification much easier to handle. Forms are great for letting users input data and have it sent all at once. Using forms also gets rid of the need to use javascript or generate complicated BYOND URLs that can change.

Controls in Forms

There are certain differences that you need to take note of when you decide to put controls inside a form. Unchecked values are not sent when you press Submit, and in Topic() checked controls (radios and checkboxes) return the value of "on" instead of "true" when it is sent in a form. Additionally, dropdown lists that allow multiple values to be selected must be parsed in a different way.

When the "multiple" parameter is set in a form, users are able to select more than one item inside of the dropdown list. The dropdown list turns into a selection list. When a form with a selection list is submitted, each value is sent with the name of the selection list separately. This means that a list named "myselection" with the values "a", "b", and "c" selected, the link would be "byond://?myselection=a&myselection=b&myselection=c". The href_list version essentially changes all of that into one element with an association of a list with a, b, and c. Therefore, the list equivalent would be list("myselection"=list("a","b","c")).

With this information about selection lists, you can take the appropriate actions when you are expecting values to be sent through a select list that allows multiple values to be selected. The following example shows how you can create a selection list to be able to drop multiple items at once via a browser form in a window.

obj/item/New()
..()
src.name = pick("Apple", "Orange", "Banana", "Grapes", "Pineapple", "Cherry")

mob
Login()
..()
for(var/i = 1 to 10)
new /obj/item (src) // generate 10 items for testing

Topic(href, list/href_list)
..()

if(href_list["form"] == "items")
// we don't know the data type of "items" yet. It is either a list or a text string
var/item = href_list["items"]
if(istype(item, /list))
// if it's a list, more than one item was selected
var/list/items = item
// loop through the items list and get the reference of the item using locate()
for(var/i in items)
var/obj/item/O = locate(i)
del(O) // remove found items
else
// not a list, it is just a regular text string
var/obj/item/O = locate(item) // locate and remove the item
del(O)
/// update the form
src.drop_items()

verb/drop_items()
var/items = ""
for(var/obj/item/I in src.contents)
items += {"<option value="\ref[I]">[I.name]</option>"}

var/html = \
{"
<form name="items" action="byond://" method="get">
<input type="hidden" name="src" value="\ref
[src]" />
<input type="hidden" name="form" value="items" />
<select multiple name="items" size="10" style="width: 100%;">
[items]
</select> <br />
<input type="submit" value="Drop Items" />
</form>
"}


src << browse(html, "window=dropitems;size=250x250")
Example 4.4: A simple example on how you would handle data from a dropdown list that allows users to select multiple items.

In this example, the data sent when the user presses the submit button is taken care of by taking the value of the item list. Since we don't know if more than one item was selected, we must check if the value we grabbed is a list. There we loop through all the items, dereference the text references, and remove the item by deleting it. When only one item is selected, we can just dereference that item and remove it. Whenever handling selection lists that allow multiple items to be selected at once, you should remember to not assume that a value can only be one data type.

Forms are a very powerful tool that you can use in combination of other controls and forms that may communicate with each other. There are many varieties of forms and combinations of controls that you can experiment with. Forms work very well for sending data all at once and simulating those types of user registration pages you find on websites. They do not require a lot of dynamic content, and are completely functional without any javascript.

Section Review

  1. Rewrite example 3.2 in the previous section as a form using the same method of handling individual variables through another function.
  2. Write a shopkeeper program where a user is able to pick a selection of items from a selection list. When the user selects items and presses the "Add to Shopping Cart" button, the selected items selected appear in the user's shopping cart, which is displayed beside the shop stock as a selection list. The user has the option of either cashing out or removing items selected from the shopping cart. Your money and the amount of money that it costs to buy all of the items in your shopping cart should be displayed in the window. The user cannot buy more than he or she can afford, and the Cash Out button should be disabled if the user does not have enough money. (Hint: The cash out button does not require using a submit button, as you would want to reserve the submit button for clearing selected items in the buy list.)
Stay tuned for Part Three: Piecing it Together, coming soon!
Something I noticed about both your (UP) lessons is that you're never escaping data that is sent. This can be potentially destructive if someone's trying out your lessons and puts an input box to send data. You can actually create DM injections (I coined it :)) very easily that way, or you can make it really easy to just break the program. And as a suggestion for perhaps a future lesson, it'd be much better to let datums control forms. For one it provides a little more security, because a new form datum per form means people will have to do a bit of extra work to figure out how to break it, and the datum won't live forever so you can pretty much stop any sort of tampering there. It looks like a really good tutorial that BYOND's needed for a while (I say look because I only skimmed through the code). Excellent job.
Those issues will be discussed and dealt with in Part Three. Just be patient! :P