ID:36767
 
Keywords: html, tutorial
Click here for Part 3.
Programming & Designing Browser Interfaces: by Unknown Person

Programming & Designing Browser Interfaces

by Unknown Person

Data Verification

As easily as BYOND URLs can be made, they can be easily spoofed. Nothing is stopping a player from entering the same protocol that they clicked before. Neither is anything stopping the player from selecting something that should not be able to be picked, or changing who the data goes to. In order to prevent data from being sent at the wrong times, or sent incorrectly, you will have to verify everything that gets sent by anything. Data Verification is an essential security that should always be kept in mind, especially when dealing with browser interfaces and their dynamic potential.

The Three Conditions

Chances are the average users using your browser forms won't bother trying or even know how to send faulty data or modify your HTML and give themselves an invalid name. However, you cannot assume your data is safe or reliable, or even whether the sent data can run a proc. For example, you are making a game where players are able to choose six classes. Four of the classes are available to regular users, and two of them are only available to subscribers to your game. You program your class-picking into a browser interface using forms, and only give the option of the four regular classes to the regular players. A couple of days later, you suddenly see regular players playing as subscriber classes. Uh oh. There was no data verification.

These types of scenarios are far more common than many would think. The pitfall was that the programmer did not forsee the fact that players are able to forge links, and assumed the player only knew about what he or she was given in the form. What the player did was figure out how the links that told the program to select a class were constructed, and the values that let the player select a certain class. The player fabricated the same link that he or she was allowed to click, but just changed the value of something that the player was not supposed to know about nor click. You can never assume the player only knows what is in front of them, and this assumption is what generally causes these types of exploits to be possible.

If you noticed, none of the previous examples in this article had any significant form of data verification. The simple way to denote verification is defining who is a valid user, what is valid, and when it is valid.

In order to verify who sent the data, you can start by verifying that the clicker of the Topic() is the reference that was sent, so you have to check if the clicker of Topic() (usr) is the same as the reference whose topic proc was executed (src). By doing this, you prevent other people from using your own topic links that were created to suit you, so someone else will not be able to change your class. The who check is the simplest of the three because it can be verified with a simple if statement on datum/Topic(). This only needs to be done once, if you keep consistent in your usage of Topic().

In order to verify the what, you need to check if the data sent is something that your browser form would normally accept as input, or if it is a correct type of value. This verification is done when the who check has passed, and you want to check if the data you are receiving is valid. This is an especially important step to general program security because without this type of verification, people could be using values meant only meant for subscribers or administrators.

The when check is done when the form is generated, and when the command is sent. This verification checks if the user is allowed to do a specific action. During the time that the form was sent and any action was sent, many factors may have changed such as changes in variables and timing. The when verification is sometimes overlooked by programmers because usually those parameters are taken care of when the command has been initiated. It is important to remember what changes could have been made between the time the command has been initiated and when the action was sent. The when check can be done for a whole form, and you do not need individual when checks unless you don't want a certain variable to be modified in a form relative to other variables.

Is my Data Safe and Reliable?

So how can we determine if our data is safe and reliable? Given these three concepts, we can define rules to determine if something is correct or if it isn't. Now that we have these three concepts of who, what and when down, let's analyze our class selection example from a previous section.

mob
  var/class

  Topic(href, list/href_list)
    var/action = href_list["action"]
    if(action == "classpick")
      var/newclass = href_list["value"]
      if(newclass)
        src << browse(null, "window=classpick") // close the window
        src.SelectClass(newclass)

  verb/choose_class()
    var/text = \
    {"
      What class would you like to pick?
- Soldier
- Mage
- Archer "} src << browse(text, "window=classpick;size=500x300") proc/SelectClass(cls) src << "You are now a [cls]!" src.class = cls
Example 2.4: The previous example showing how you can create a simple class selection system using hyperlinks. Does not have any security.

In this example, the player is able to pick their class as many times as they want in the class picking example, and is also able to spoof the link to give his or herself a custom class. To stop this, you will have to state what value the class variable is allowed to take, and verify if you have chosen a correct class. You will also have to do appropriate checks before the form is updated, and after the user submits the data. To simplify this process, it may be easier to understand by writing down the who, what, and when.

Who - Only the player who gets the browser pop-up may be able to select what class they will become
What - The player may only choose a class that is verified as a valid class. This will be defined in the valid_classes list
When - The player may only choose a class when they do not already have an assigned class

Given these constructs, we can define conditions inside the code to do the data verification. In the general case, the verification usually takes place through procs that return true if the data is correct. In the following example, I verify if the player can pick his or her class, and if the class is correct in a mob proc. I do the check inside the SelectClass() function, and return 0 if the checks have failed. The check inside SelectClass() is both the when and what check. We want to check if the player is still able to select a class, and if the class selected is valid before we finally let him or her change classes.

var/list/valid_classes = list("Soldier","Mage","Archer")

mob
  var/class

  Topic(href, list/href_list)
    ..()
    // Is this the player who is supposed to receive the link?  The "who" check
    if(src != usr) return
    var/action = href_list["action"]

    if(action == "classpick")
      var/newclass = href_list["value"]
      if(newclass)
        if(src.SelectClass(newclass))
          // only close the window when the class was successfully picked
          src << browse(null, "window=classpick")
        else
          // an invalid value was sent
          src << "Invalid."

  proc
    canChooseClass() // Am I able to choose classes?  The "when" check
      return !src.class

    isValidClass(cls) // Is this a valid class?  The "what" check
      return (cls in valid_classes)

    SelectClass(cls)
      // verify that the player can choose the class, and if it is a valid class to choose
      if(!canChooseClass() || !isValidClass(cls))
        return 0 // verification failed, do not use the data

      src << "You are now a [cls]!"
      src.class = cls
      return 1

  verb/choose_class()
    if(!src.canChooseClass()) // do the "when" check right before sending the browser form
      src << "You already have an assigned class!"
      return

    var/text = \
    {"
      What class would you like to pick?
- Soldier
- Mage
- Archer "} src << browse(text, "window=classpick;size=500x300")
Example 5.1: The class-picking example with full data verification. This example is tamper-proof, and disallows any illegal values to be accepted.

The example shows us how you apply data verification to a very simple program. The only two additions are the isValidClass() and canChooseClass() procs, which verify if the class is valid and when the player can choose a class, respectively. These two additions are essentially the only new functions you need to take care of. Even though they are relatively simple, it is a good idea to define them in a proc because when you start to have more complicated rules to define what is correct and what isn't, you will have everything organized all at once place. Rewriting verification code is something you do not want to do, since if you decide to change one of your rules, you will have to change all the places where you wrote your rules.

A very common situation like the above example is when a program allows the user to pick a set of valid types of options. In the above example, the valid types of options were a certain set of classes the user could pick. The program before verification had the assumption that the user could only pick the options that were shown on the page, and did not handle any other cases. You should never overlook this issue, since it is very easy to fabricate links with data that players can write themselves.

Let's expand on our class changing example. You may need to write a type of system where you need to determine whether something is valid depending on another variable. For example, if you want to restrict certain genders to only be allowed to pick certain occupations, then you will have to have one verification to refer to another. When having more than one variable to verify, you can combine the "when" and "who" verification check, since both values are being verified at the same time. The following example shows a simple example on how you would approach verification when having two variables that have different true and false values depending on each other.

var/list
  occupations = list("Soldier", "Doctor", "Teacher", "Housewife", "Nurse")
  genders = list("male" = list("Soldier", "Doctor", "Teacher"),
                 "female" = list("Housewife", "Nurse", "Teacher"))

mob
  var/occupation

  Topic(href, list/href_list)
    if(src != usr) return // to verify the clicker of the link is supposed to click it
    ..()

    var/action = href_list["action"]
    var/value = href_list["value"]

    switch(action)
      if("gender")
        // verify gender before setting it
        if(isValidGender(value))
          src.gender = value
          src.occupation() // update the window to show the new occupations
      if("occupation")
        if(isValidOccupation(value))
          src.occupation = value
          src.occupation()
      if("submit")
        if(canSubmit()) // the "when" check for the whole form
          src << "You are a [src.gender] [src.occupation]."
          src << browse(null, "window=occupation")

  proc
    canSubmit()
      // the user can only press the submit button when he or she has a valid gender
      // and a valid class
      return isValidGender(src.gender) && isValidOccupation(src.occupation)

    isValidGender(value) // the user can only be a male or female
      return value == "male" || value == "female"

    isValidOccupation(value)
      // a valid occupation is defined in the occupation list, and as an association
      // in the gender list to determine which occupations a gender can choose
      return (value in occupations) && (value in genders[src.gender])

  verb
    occupation()
      var/list_occupations = ""
      for(var/i in occupations)
        if(isValidOccupation(i)) // the "what" check for occupations
          list_occupations += {" [i]
"} var/html = \ {"
Occupation <script language="javascript"> function set(input) { window.location="byond://?src=\ref[src]&action="+input.name+"&value="+input.value; } </script> <body> Gender:
Male
Female

Occupation:
[list_occupations]
</body> "} src << browse(html, "window=occupation;size=200x300")
Example 5.2: The occupation-picking example limits occupations to be picked depending on the player's gender, which he or she picks in the same page

The values of the player's gender and occupation are verified from an associative list globally stored, and are referred to in the verification procs. The page refreshes every time the user selects a gender. This updates the pages with the occupations the user is able to pick. When the user presses the "Submit" button, another check for all of the variables is done before it is deemed to be valid data. With this type of verification, selecting an invalid option is impossible, since the program discards all invalid data, and only acts when valid data has been sent.

Remember that when you are planning out your data verification, you should be thinking about the actual data, and not the nature of how it was sent. Topic() can get called at any time, regardless of what links are available or if any information of the parameters your forms normally take. If you are having trouble trying to figure out how you would prevent users from fabricating links, then you should be, since it's impossible to prevent a user from creating his or her own links. The only thing you can do is act upon the event an invalid link is created, and prevent it from happening. Otherwise, it's just Garbage In, Garbage Out.

Form Verification

In the previous section, the article introduced HTML forms. Forms are especially important to verify, since all of the information is sent at one time. It is also important to be able to handle invalid data in forms, since ensuring data is correct by using forms is a much tougher job. Forms are also fairly easy to verify, since all of the information is taken care of in one proc.

Verifying data in forms is not so different than verifying data in individual controls. Both generically represent one control for one variable, and a submit button verifying all data sent. The submit button verification is already done in a form, since everything is verified on the submission. There is no change in the way you verify data. The only difference is how you are going to handle invalid data. With regular controls, you can automatically correct the data, or discard user-inputted data such as names and numbers since controls are sent on the event a value is changed. With forms, you are going to have to validate data all at once. This is a good time to add error messages to your form.

Error handling is an important aspect of any type of user friendly interface. If a form can't successfully let the user know something has gone wrong, then your players will be left confused with what happened. In the Forms section, we used two procs to handle the form data: ProcessForm() and ProcessVariable(). Since the actual verification is done in ProcessVariable(), you can return an error message if the verification fails. This way, ProcessForm() knows which variables have had errors, and can add all the accumulating errors into an associative list (name being the name of the control with the error, value being the actual error message). When the form processing has finished, you can either resend the form with the errors from the list, or let the form be submitted and closed if no errors were found. The following is a simple example showing how this could be done:

mob
  name = "John Doe"
  desc = "I'm just the average joe."
  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

      var/errors = ProcessForm(href_list["form"], data) // process data
      if(istype(errors, /list))
        // there are errors.  resend the form with the error list
        DisplayForm(errors)
      else
        // close the form when submitted without any problems
        src << browse(null, "window=settings")

  proc
    ProcessForm(formname, list/params)
      var/list/errors = list()
      for(var/item in params) // loop through each name=value item individually
        var/alert = ProcessVariable(formname, item, params[item])
        if(alert) errors[item] = alert
      if(!length(errors)) return null
      return errors

    ProcessVariable(formname, name, value)
      // use the form name in order to have different functionality for different forms
      if(formname == "settings")
        switch(name)
          if("name")
            // let the name be set, but do not let the form be submitted if an invalid value was typed
            // this lets the user be able to keep their old input and modify it
            src.name = value
            if(!isValidName(value))
              return "Your name must be between 3 and 25 characters long"
          if("age")
            value = text2num(value)
            src.age = value
            if(!isValidAge(value))
              return "Invalid age"
          if("desc")
            src.desc = value
            if(!isValidDesc(value))
              return "Your description is too long";

    // verification

    isValidName(text)
      return length(text) <= 25 && length(text) >= 3

    isValidAge(age)
      return age > 0 && age == round(age)

    isValidDesc(text)
      return length(text) <= 300

    DisplayForm(list/errors=list())
      var/html = \
      {"

Settings <body>
Name: [errors["name"]]
Age: [errors["age"]]
Description: (up to 300 characters) [errors["desc"]]

</body> "} src << browse(html, "window=settings;size=500x300") verb settings() src.DisplayForm()
Example 5.3: A form that tells the user whenever any user-inputted data is sent incorrectly.

In the above example, the code for displaying the browser form has been written in a separate proc rather than a verb. This is because it is necessary to send error information through the argument. The HTML assumes the argument to be a list of errors. If an error isn't appended, no red text is displayed. The error list is sent through the return value of ProcessForm(), which is built individually through all of the ProcessVariable() calls. This process makes it much easier to handle error messages, and requires less code than trying to handle each error as its own variable, or generating a whole new form to display a different error message.

Keep in mind that this verification applies to only one variable. You must define other methods and procedures for each variable you take account for and want verification for (which should be all of them!). Data Verification uses the same method no matter what method you decide to send your BYOND URLs. It would make no difference if you changed the above HTML to use buttons or radios, the data is still verified the same way because the links are sent the same way. A good exercise to become accustomed to adding data verification to your existing browser forms is to take the previous examples in the article, and add basic verification to it.

On a final note, the reason for implementing data verification in your game is not only to prevent users from sending bad data (which is still pretty important), but to figure out potential logic errors in your input. By defining what is correct data, you think about what you want the user to input, and therefore secure the definition of what you want a certain value to be.

Section Review

  1. Create a game setting interface where you are able to select the mode and map of a game you are creating. Certain maps should support three modes, "Deathmatch", "Capture the Flag", and "Team Battle". Maps should only appear in a selection list if the map is able to be played in a mode. When the user changes the mode, the map should be reset. Create a "Submit" button that is diabled when a mode or map isn't selected. Create a game datum that will handle the form (you should define /game/Topic(), and all verification functions inside the datum) and will store the mode and map.

Organization

Throughout the whole article, all of the examples have been demonstrated by defining Topic() to the object that the action is going through, which in almost all the cases is the mob or the client. Using this method is fairly simple and convenient, but if you start to add more and more forms to your game, you will eventually realise that your Topic() proc will be overflowed with unorganized parameters and values. The ultimate solution to this problem is to split up your different forms into different Topic() procs. You can achieve this by using datums.

Meet the Form Datum

Programming forms using datums is not too different than the way we have been defining it up until now. Every form datum has all of the same functionality, except they are all split up into different procs. To define a form is to be able to identify what the form is doing. A generic form needs to keep track of the viewer or the owner of the form. A form also needs to have methods defined to display the form to the user, and close the window when it's deleted. All of this can be very simply implemented. The following example is what a very basic form datum would look like:

helloWorldForm
  var
    client/owner // the viewer of the form
    self

  New(client/_owner)
    ..()
    self = src // to prevent the garbage collector from deleting the unreferenced form
    src.owner = _owner
    src.DisplayPage() // display the page once it's created

  Del()
    // close the window when the form is deleted
    owner << browse(null, "window=helloworld")
    ..()

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

    if(href_list["action"] == "close")
      del(src) // delete the form when the close link is clicked on

  proc
    DisplayPage()
      // generate the text
      var/text = {"

Hello World <body> Hello World!

\[Close\] </body> "} // then display the page to the owner owner << browse(text, "window=helloworld;titlebar=0") mob/verb/hello_world() // create a new instance of the hello world form datum to handle all // form functions new /helloWorldForm (src.client)
Example 6.1: A closable form that tells the user "Hello World!". Demonstrates the basic usage of form datums.

The above infamous "Hello World!" form example demonstrates how a generic form could be structured. First, the form is displayed by instantiating the /helloWorldForm datum, and passing the player's client through New() to determine who the form will display to. The form stores the viewer of the form as a client, although it can also alternativly be stored as a mob. The form also defines the method DisplayForm(), which generates the to-be-displayed text and then displays it to owner. When the form is deleted, the window is closed. del is called when the player clicks on the close link.

As you can see, this example of programming forms using datums is not functionally nor fundamentally different than any other form. The only differences involve how the functionality is split up. The main difference is the reference data that is sent. Since the form's reference is sent, it is important to realise that the form's Topic() proc is called, rather than the mob like in our previous examples due to the fact that the src parameter points to the form. The program only knows to display the form to the player because the "owner" variable is stored and specified in the code.

The most noticeable advantage to using datums for forms is that it's much more organized, and it's easier to keep track of what each form does, since it's all defined in a different type. Another great advantage is that since the form is a datum, one could program a generic form datum, and then define new forms as children of the form datum. Use of inheritance and trees can reduce the amount of code and headaches you may encounter when defining forms the old way.

The examples from the previous sections can be translated from the old method to using datums quite easily. Let's rewrite our previous example using HTML forms.

infoForm
  var
    client/owner

  New(client/_owner)
    ..()
    src.owner = _owner
    src.DisplayForm()

  Del()
    owner << browse(null, "window=infoform")
    ..()

  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
      DisplayData()
      // close the form when submitted
      del(src)

  proc
    DisplayForm()
      var/html = \
      {"

Name:
Age:
Gender:

      "}

      owner << browse(html, "window=infoform;size=500x300")

    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)
      var/mob/M = owner.mob
      if(formname == "settings")
        switch(name)
          if("name")
            M.name = value
          if("age")
            M.age = text2num(value)
          if("gender")
            M.gender = value

    DisplayData()
      var/mob/M = owner.mob
      owner << "My name is [M.name]."
      owner << "I am [M.age] years old."
      owner << "I am [M.gender]"

mob
  var
    age = 20
    infoForm/form

  verb
    settings()
      if(src.form) return // don't let another form be opened if one is already present
      src.form = new /infoForm (src.client)


Example 6.2: Example 4.3 rewritten by using a form datum. Forms are kept track of through a mob variable holding the currently viewed form.



The above example is fairly simple to understand. It uses all of the basic functions of the old implementation of forms, except that all of the code is in the form datum, /infoForm, rather than defined in the mob. ProcessForm() and ProcessVariable() are defined to the form. It makes sense this way, since the form parsing and calculation methods should belong to the form, not the viewer of the form.
The noticeable difference between this example and the previous one is that we have defined a new variable called form to keep track of the form that the user is currently viewing. This is handy if you ever want to refer to a currently viewed form, or want to determine if the user is already looking at a form. This can prevent users from having more than one forms open at the same time. The above example will not let you open up more than one info form at a time. When the form is deleted (on the event the submit button is pressed), the form is deleted and the reference is nullified, letting the user open up a new form.

Handling Data Verification




Using a datum to handle all of our process is a much more organized and simple way to do things. The same can be said for handling data verification. Like the previous example, the implementation is very similar. The verification can be handled all the same way, and would be split up into the different form methods. Let's add verification to an example in the previous chapter.
infoForm
  var
    client/owner

  New(client/_owner)
    ..()
    src.owner = _owner
    src.DisplayForm()

  Del()
    owner << browse(null, "window=settings")
    ..()

  Topic(href, list/href_list)
    // usr is always a mob in datum/Topic(), so we must compare the owner
    // with its client to verify the 'who' check
    if(src.owner != usr.client) return
    ..()

    if(href_list["form"])
      var/list/data = href_list.Copy(href_list.Find("form") + 1)

      var/errors = ProcessForm(href_list["form"], data) // process data
      if(istype(errors, /list))
        DisplayForm(errors)
      else
        del(src)

  proc
    DisplayForm(list/errors=list())
      var/mob/M = owner.mob
      var/html = \
      {"

Settings <body>
Name: [errors["name"]]
Age: [errors["age"]]
Description: (up to 300 characters) [errors["desc"]]

</body> "} owner << browse(html, "window=settings;size=500x300") ProcessForm(formname, list/params) var/list/errors = list() for(var/item in params) // loop through each name=value item individually var/alert = ProcessVariable(formname, item, params[item]) if(alert) errors[item] = alert if(!length(errors)) return null return errors ProcessVariable(formname, name, value) var/mob/M = owner.mob // use the form name in order to have different functionality for different forms if(formname == "settings") switch(name) if("name") // let the name be set, but do not let the form be submitted if an invalid value was typed // this lets the user be able to keep their old input and modify it M.name = value if(!M.isValidName(value)) return "Your name must be between 3 and 25 characters long" if("age") value = text2num(value) M.age = value if(!M.isValidAge(value)) return "Invalid age" if("desc") M.desc = value if(!M.isValidDesc(value)) return "Your description is too long"; mob name = "John Doe" desc = "I'm just the average joe." var infoForm/form age = 20 proc isValidName(text) return length(text) <= 25 && length(text) >= 3 isValidAge(age) return age > 0 && age == round(age) isValidDesc(text) return length(text) <= 300 verb settings() if(src.form) return src.form = new /infoForm (src.client)
Example 6.3: Example 5.3 rewritten using a form datum. Verification is handled in a similar way, just split up a bit more and with slight logic modifications.

In the above examples, all of the original code from Example 5.3 was kept. The form processing procedures were redefined to the form datum, as it should be. However, the verification procs were kept defined to the mob. These verication methods belong to the mob because they are accessors that return whether a certain variable for an object is correct. These procs would also make sense if they were defined globally.

Another slight difference that is seen in this example is the who check. Since we are redefining all of the functionality from the mob to the datum, we need to rethink what is what. In this context, we want to see if the clicker of the link is the owner of the form. This means that we must compare /infoForm.owner to usr.client in /infoForm/Topic(). Since usr in this proc is always a mob, and we store the viewer of the form as a client, we compare with usr.client.

Any of the form examples shown in this article would not be realistic to implement in your project in this method because of its specificity. If we wanted to create a bunch of setting forms, and information forms, we would not define a whole new datum for it. This is not extensible code. If we wanted to handle dozens of different types of forms without rewriting our form code or verification code, we would define a more generic form.

Generic Forms

Up to this point in this article, all of the examples were written from the perspective that each was the only type of browser interface that was in the project. This isn't realistic for a large project. If you're writing an immense online RPG where browser interfaces will pop up every other event, then you would have headaches trying to handle all of your browser interfaces. At a certain point, your game's programming would be filled with the same repeated browser code. The solution is to define a generic form. This means that one parent form will define the behaviour and characteristics of all of the other forms in your game.

Good programming lets us easily reuse and extend the same code over and over again without having to modify it. This is as true to forms as to anything else. By defining a generic form, we can program all the common functionality of all of the forms you would see in your project. Let's start developing a generic form to use for the rest of this chapter.

When programming anything in general, we think about what all of the different implementations of the base object would use, and define the general blueprint by identifying the similarities between them all. All forms are viewed by a person, and all of them can have different title names, sizes, and have different window params. All forms also need to be able to display the page to the viewer, and also close it when the form is deleted. Given these constructs, we can define a generic form. Below is a simple implementation of a generic form.

// Generic Form version 1

mob/var
  form // the form the user is currently viewing

genericForm
  var
    client/owner // the viewer of the form

    window_name = "Generic Form" // name of title bar and browser window id
    window_params = "" // any other generic parameters sent through browse()
    body = "" // the html to be displayed to the viewer

  New(client/_owner)
    ..()
    src.owner = _owner
    DisplayPage() // display any page right when it's instantiated

  Del()
    owner << browse(null, "window=[src.window_name]")
    ..()

  proc
    UpdatePage(bodyText)
      src.body = {"

[src.window_name] <body> [bodyText] </body> "} GeneratePage() // Generate the page text and layout by calling UpdatePage() with the body // To be implemented in children types DisplayPage() // first generate the page text, then display the page to the owner GeneratePage() owner << browse(body, "window=[src.window_name]&[window_params]")
Example 6.4: Generic Form version 1. Demonstrates and provides a very basic interface for forms to be defined.

Version 1 of our generic form datum is a fairly simple datum that gives us freedom to define the functionality of children definitions. The window_params name lets us define params sent through browse(). In order to use the datum, GeneratePage() must call UpdatePage with the argument being the HTML that will be displayed in the form. Splitting up these functions makes the datum much more extensible, and easily updatable without your existing code breaking. DisplayPage() first generates the page, and then displays it to the user. And like our previous form datums, the window is closed when the form is deleted.

The above snippet doesn't do anything, since it's just a blueprint, but the purpose of it is to make it easier when defining multiple forms. For example, if you wanted to create a form, all you needed to do was extend the /genericForm type and define a new type under it. Let's create a simple HelloWorld form.

genericForm/helloWorld
  window_name = "Hello World"
  window_params = "size=200x100&titlebar=0"

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

    if(href_list["action"] == "close")
      del(src)

  GeneratePage()
    UpdatePage("Hello World!
\[Close\]") mob/verb/hello_world() if(src.form) return // instantiate the new type we declared above src.form = new /genericForm/helloWorld (src.client)
Example 6.5: Writing a simple form by extending our generic form datum we previously defined

Creating a form has never been this short before. All we need to do is instantiate our form datum, and let the form handle all of the functions itself. The helloWorld datum, which is defined under our genericForm datum, needs very little manual programming. We define our Topic() proc to catch the event the page is closed. GenericPage() is implemented to call UpdatePage() and defines what the page would look like. Our Generic Form V1 can handle many things, but let's add even more. What if you wanted it to support forms, and data verification? Let's add on our generic form to handle these things.

// Generic Form version 2

mob/var
  form // the form the user is currently viewing

genericForm
  var
    client/owner // the viewer of the form

    window_name = "Generic Form" // name of title bar and browser window id
    window_params = "" // any other generic parameters sent through browse()
    body = "" // the html to be displayed to the viewer

  New(client/_owner)
    ..()
    src.owner = _owner
    DisplayPage() // display any page right when it's instantiated

  Del()
    owner << browse(null, "window=[src.window_name]")
    ..()

  Topic(href, list/href_list)
    // When overridden, you must check if the parent implementation returns true
    // in order to ensure the code isn't run when there is an invalid Topic() call
    // EG: . = ..(); if(.) { ... }

    if(!isValidViewer(usr.client))
      return 0 // verification failed

    // if a form was submitted, handle it separately
    if(href_list["form"])
      HandleForm(href_list)

    return 1 // verification succeeded

  proc
    isValidViewer(client/C)
      // the 'who' check: ensures the link-sender is the form's owner
      return src.owner == C

    UpdatePage(bodyText, styleText, jsText)
      src.body = {"

[src.window_name] <script language="javascript"> [jsText] </script> <body> [bodyText] </body> "} GeneratePage(list/errors=list()) // Generate the page text and layout by calling UpdatePage() with the body // To be implemented in children types DisplayPage(list/errors) // first generate the page text, then display the page to the owner GeneratePage(errors) owner << browse(body, "window=[src.window_name]&[window_params]") // form functions HandleForm(list/href_list) // organize the data, and then process the form var/formname = href_list["form"] var/list/data = href_list.Copy(href_list.Find("form") + 1) var/errors = ProcessForm(formname, data) // process data if(istype(errors, /list)) FormSubmitError(formname, errors) else FormSubmitSuccess(formname) ProcessForm(formname, list/params) // Processes a single form on the event the submit button was pressed var/list/errors = list() for(var/item in params) // loop through each name=value item individually var/alert = ProcessVariable(formname, item, params[item]) if(alert) errors[item] = alert if(!length(errors)) return null return errors ProcessVariable(formname, name, value) // Called for each control when a form is submitted // If applicable, returns a string containing an error message // To be implemented in children types FormSubmitSuccess(formname) // Called when a form is successfully submitted FormSubmitError(formname, list/errors) // Called when a form has been submitted, but failed verification DisplayPage(errors)
Example 6.6: Generic Form version 2. Implements functions that can easily handle forms, and data verification. Modifies UpdatePage() to allow custom CSS

This new version of our generic forms adds a bit more functionality into the basket. Now, we have separate functions to handle all form data in a simple manner. Our old processing procs from our previous sections have been re-implemented, and have been split up a bit more as well. The methods common to all pages that require forms have been pre-implemented, such as ProcessForm(). Other interface methods, such as FormSubmitSuccess() and FormSubmitError() have been added for the convenience of the user using this generic form, and allows the programmer more control over the page in whole. An interface for handling data verification has also been considered, as the ProcessVariable() proc is open for overriding in children types. The 'who' check is hard-coded in the generic form's Topic() proc. On a minor note, a way to define custom CSS for the pages has also been implemented as another parameter through UpdatePage().

In contrast to all of these major additions, there is no new code that has been introduced, since it has all been seen in previous examples. If we're going to be using these functionalities more often, you may as well define it in a generic form to use whenever you feel like it without having to rewrite code. We can easily rewrite our /infoForm datum in the previous section to use our generic form implementation with much less code and much less hassle.

genericForm/settings
  window_name = "Settings"
  window_params = "size=500x300&titlebar=0"

  GeneratePage(list/errors=list())
    var/mob/M = src.owner.mob
    var/text = {"

Name: [errors["name"]]
Age: [errors["age"]]
Description: (up to 300 characters) [errors["desc"]]


    "}

    UpdatePage(text, "span.error { font-size: 8pt; color: #ff0000 }")

  ProcessVariable(formname, name, value)
    var/mob/M = owner.mob
    if(formname == "settings")
      switch(name)
        if("name")
          M.name = value
          if(!M.isValidName(value))
            return "Your name must be between 3 and 25 characters long"
        if("age")
          value = text2num(value)
          M.age = value
          if(!M.isValidAge(value))
            return "Invalid age"
        if("desc")
          M.desc = value
          if(!M.isValidDesc(value))
            return "Your description is too long";
    return null

  FormSubmitSuccess(formname)
    del(src) // close form when successfully submitted

mob
  name = "John Doe"
  desc = "I'm just the average joe."
  var/age = 20

  proc
    isValidName(text)
      return length(text) <= 25 && length(text) >= 3

    isValidAge(age)
      return age > 0 && age == round(age)

    isValidDesc(text)
      return length(text) <= 300

  verb/settings()
    if(src.form) return
    src.form = new /genericForm/settings (src.client)

Example 6.7: Rewriting our settings form by using our updated generic form



Relative to our old datum, this way is much shorter and much less of a pain in the neck to organize. This example does not need any more explaining than it has gotten already, so we can skip that. However, after looking at this example, you should see why defining a generic form can save you loads of time, and can also give you the freedom to do many things in a very organized manner.

Expanding on our Forms




With this newfound ability of organization, it is possible to do much more. What would you do if you wanted to write a form that had a timer, and would close in 30 seconds, or write an HTML alert box? You could define new functionality in the generic form, but you're also free to define a subcategory of generic forms. It would be a huge pain if you wanted to define a different form that displayed a slightly different message. If you wanted to write an HTML alert box to replace the old clunky alert() proc, you could define another generic form that is instantiatable, but also dynamic. By overriding New(), you can define new parameters and new functionalities. The below is how you could write a dynamic alert box that lets you display a message determined by what you send through New().
genericForm/alert
  window_name = "Alert"
  window_params = "size=300x150&can_close=0&can_minimize=0"
  var
    message // the message that is displayed in the alert box
    self

  New(client/_owner, _message)
    self = src // to prevent the garbage collector from deleting the unreferenced object
    src.message = _message
    ..()

  Topic(href, list/href_list)
    . = ..()
    if(.)
      if(href_list["action"] == "close")
        del(src)

  GeneratePage()
    var/js = {"
    function action(action) {
      window.location="byond://?src=\ref[src]&action="+action;
    }"}
    var/style = "body { text-align: center } button {width: 100px }"
    var/page = {"

[src.message]

"} UpdatePage(page, style, js)
Example 6.8: An extention of Generic Form V2 that gives us an interface for simple HTML alert boxes.

The above code can be brought into effect by just creating an instance of /genericForm/alert and passing through the correct parameters as easily as this: new /genericForm/alert (target, "Hello, this is my message!"). This alert form now does quite a bit with just a line of code since we predefined its behaviour as an extention of our generic form.

By taking advantage of inheritance, you can also define a new generic form under our generic form. For example, if we wanted to created a form that has a built-in timer, we would define a type of /genericForm/timed that would implement simple timer functions and variables. After that, you can do the same thing and write all of your timed forms under that type. The below example shows how you could implement a generic timed form as a child of our generic form.

genericForm/timed
  var
    time_left = -1
    tick_interval = 10
    update = 0

  New(client/_owner)
    // Start the timer if a time has been defined to the
    // timed form at compile-time
    if(time_left > 0)
      spawn() StartTimer(time_left)
    ..()

  proc
    StartTimer(time)
      // timer has started
      time_left = time
      TimeStarted(time)

      while(time_left > 0)
        sleep(tick_interval)
        time_left -= tick_interval
        // if specified, a timed form can update every tick interval
        if(update) DisplayPage()

      TimeUp() // bzzt, time's up!

    TimeStarted(time)
      // Called on the event the timer has started
      // Free to be implemented in children types

    TimeUp()
      // Called when the time has reached 0
      // Form by default closes at this point, so delete the form
      del(src)
Example 6.9: An extention of Generic Form V2 that implements a timer, and functions relating to it

Similar to our generic form, we can extend this form with any forms that we want a timer for. The following example demonstrates how you could use the above timed form in a practical way, and also demonstrates the usage of our previous alert form.

genericForm/timed/explodingLetter
  window_name = "Mysterious Letter"
  window_params = "size=300x200&can_close=0&can_resize=0&can_minimize=0"
  time_left = 100 // The letter explodes in 100 ticks (or 10 seconds)
  update = 1 // let the page be refreshed every second (time_interval is 10)

  Topic(href, list/href_list)
    . = ..()
    if(.)
      if(href_list["action"] == "dispose" && time_left > 0)
        var/message = "You have disposed of the paper and are spared of your life."
        new /genericForm/alert (src.owner, message)
        del(src)

  GeneratePage()
    var/text
    var/style = "body { text-align: center }"
    if(src.time_left <= 0)
      text = "BOOM!"
      style += "span.exploded { font-size: 36pt; color: #ff0000 }"
    else
      text = {"
        Top Secret: The hidden treasure is located in locker 101.

This letter will explode in [src.time_left / 10] second\s.

You may want to dispose of the letter before it explodes in your hand. "} UpdatePage(text, style) TimeUp() // 2 seconds after the letter explodes, close the form spawn(20) var/message = "The explosive letter detonates in your hand, and you are killed in the blast." new /genericForm/alert (src.owner, message) ..() mob/verb/read_secret_letter() if(src.form) return src.form = new /genericForm/timed/explodingLetter (src.client)
Example 6.10: An exploding letter form that uses the timed form extention we defined earlier

The exploding letter program uses the new functions we defined in the timed form datum. It also uses the alert functions we defined in a previous example. The alert is shown after the page has been closed. TimeUp() is overriden so that the form wouldn't close immediately after the timer struck zero, but 2 seconds after. In GenerateText(), the page text is specified according to what the timer is. Since the form is told to redisplay every tick interval, we can display how many seconds are left until the letter explodes.

There are a vast amount of other things you could add or extend from a generic form, and this chapter can go on and on demonstrating how to do such things, like allowing the user to view and handle more than one form, handling all downloaded data sent through browse_rsc(), or even having forms be able to communicate with each other. However, that would ruin the fun of you being able to implement it yourself. The purpose of this chapter is to let you understand that using a datum to represent forms makes a lot of sense, and can save you a lot of time. If used correctly, it can be a flexible tool to display browser interfaces.

Section Review

  1. Write a timed lock mini-game that allows players to play a game where he or she tries to open a door. A form should appear when the player attempts to open the door. The mini-game should let the player press a Start button to start the timer. During this time, he or she has 30 seconds to guess a 3 digit number with each digit being able to be a number between 1 and 5. Three selection lists (one list per digit) should show the numbers. A form should be used to submit the code. If the code is correct within the time limit, then the player may advance through the door. Otherwise, security gets the player and he or she loses. The player should have a "Give Up" button that is only accessible if less than 20 seconds has elapsed. When the player either wins, loses, or gives up, the form should close. The amount of time left should be displayed and updated on the form. You may use GenericForm.dm, TimedForm.dm and AlertForm.dm (located in the supplement) for the definitions of the generic form and timer.
Stay tuned for upForm, a library that lets programmers easily define their own browser interfaces. Coming soon!