HTML has existed in BYOND for quite a while, but the idea of using HTML to enhance a program's interface has not been commonly heard of in BYOND games. This library will let you create and define the behaviour of HTML interfaces without having to re-invent the wheel every time you write some browser interface code.
To define a form, all you need to do is define a child type from the datum /upForm and define some variables and procs. Here is a simple definition of a form that would display "hello" to the user's default browser.
The proc GenerateBody() is where you should write your code to generate the body of the page, implied by the name. GenerateBody() should always call UpdatePage() with the body text of the page being sent through the parameter. This proc must be implemented in any form you wish to define.
The above code won't do anything, since it's just a declaration of a new form. We can do something with this datum by using the upForm() proc defined in the library. This proc essentially creates and handles a form for you. It is the hub of all form instantiation. To create an instance of a form, call upForm(), sending the required data through the args. The following will display the form we defined above when the player logs in.
There are four to use upForm(), but the most common method is to specify the target of the form (a mob, or a client) as the first arg, and then the path of the form (such as /upForm/mypage) as the second arg. You may call upForm() as many times as you want with the limitation that another page will not be interrupted.
On another note, upForm() returns the reference of the newly instantiated form. Therefore, you can store this elsewhere to handle it, such as the case if you want to externally close the form. To close a form, the most simple way is to delete the instance. Deleting it handles all of the final closing code. More of this will be explained in the next sections.
There are a total of four types of forms you may define with the /upForm datum. This is determined by what the value of the /upForm variable form_type is. The variable may be one of the four constants:
The default value for form_type is UPFORM_BROWSER, so if it is not specified, the form will display the page to the default browser of the user's interface. The example in the previous section displays the page to the user's default interface. The only difference between UPFORM_BROWSER and UPFORM_INTERFACE is that you specify the interface control's name to output to using the form_name variable.
Windows are a fairly common way of displaying data, because you can have as many windows as you want on the screen, and they have the flexibility of being resizable, movable, and closable. To create a windowed form, set the form_type of the form to UPFORM_WINDOW.
Windows also have other important settings that you may want to configure. The three variables used to configure window settings are window_title, window_size, and window_params. The window_title variable is what will be displayed as the title of the window. window_size is the size of the window using a "[width]x[height]" format. window_params is a variable that takes flags as settings that determine the nature of the window. The variable may take the following flags: UPFORM_CANNOT_CLOSE, UPFORM_CANNOT_RESIZE, UPFORM_CANNOT_MINIMIZE, and UPFORM_NO_TITLEBAR.
Because there is no way to catch the event that a window has been closed by the client, two form types have been defined for you. If you want the user to have the ability to keep the window up for as long as he or she wants, then you should define the form as a UPFORM_CLOSEWINDOW. By defining a window as a regular UPFORM_WINDOW, then you will need to handle the closing yourself, either by external or internal means. Close Windows are deleted right after their pages are displayed, so upForm() will return null.
The upForm datum handles all of the link handling for you in Topic(). You will not and should not touch the Topic() proc. The library has defined a Link() proc for you to handle communication from the player to the form. Its usage is similar to how you would override Topic() if you were to handle your own communication. Here is a simple example showing how you could have a window have a close link.
Link() has two args, one that is actively demonstrated. The href_list var is an associative list holding a name=value list of the params sent through the link. The second arg is the client who sent the link, so it is possible to have distinct behaviour of your forms depending on who sent it.
The great thing about the library is that it handles all automated verification for you. The client sent through Link() is guaranteed to be valid, as it is verified by the form datum. Nothing will happen if an unauthorized user attempts to send a link to a form that the user is not viewing.
Because of the numerous ways that you can represent a form's purpose and functionality, this library has defined a few variables for you to utilize externally from the form. When you call upForm(), the first arg of the proc is always the target of the form. The target can either be a client, or a list of clients (if a mob or list of clients or mobs is sent in a list, they will be automatically converted if possible). The target of a form is essentially the client or clients that the form will be directed at. Therefore, you can send an instance of a form to the screen of many clients if you want them all to view and control the same thing. More information about multi-viewer forms is discussed in the next section.
If the target of an owner is an individual client or mob, then a form's owner is set to that client. By defining owner for form objects, we can identify internally who the form belongs to. There is no owner if the target is a list of clients.
Another concept of forms is the host. The host of a form is what the form is about, or what the form is modifying. The host variable holds any type of datum, and is completely optional. The variable should be specified in the arg after the owner when calling upForm(). The purpose of defining host is if you want convenient and reliable access to a certain object that the form will commonly use. For example, if you want to create a browser interface that reads or modifies a certain object, such as another character in your game, then you will want to send the character reference through the second arg of upForm(). The following is an example on how you would use the host variable to view the health of another player.
Notice how the verb calls upForm() with three arguments instead of two. If you want a host to be specified, then the second arg should be the reference of a host. For easy reference, here are the two main ways to call upForm():
When you are trying to access the host or the owner of a form, you should use getOwner() and getHost() respectively. You should use these procs to ensure that you are not going to access something that does not exist. If you did not specify a host (or in the case of multi- viewer forms or global forms, an owner), then you should not be calling getHost(). However, in the case the host is deleted while the form is running, you will want to manually check the host variable before calling getHost(), as the proc will call an assertion if a host does not exist. You will have to handle the possibility of the host being deleted while the form is still running.
You can enhance your interface by including CSS and JavaScript. All forms have the variables page_css and page_js. Respectively, they represent the CSS code and JavaScript code that will be displayed in that form. The upForm Library defines a predefined set of commonly used JavaScript functions, all prefixed with upForm_. Refer to the Reference at the end of this documentation for information about the predefined JavaScript functions. JavaScript can also be written by sending text through the second arg of UpdatePage(). When you generate your page, you may want in a certain case to send JavaScript code.
If you want all of your pages to look similar, you can define a global CSS by defining the global_css var belonging to the /upForm datum. The below example shows the usage of global_css, page_css, page_js, and sending JavaScript code through UpdatePage()
upForm not only easily lets us define and associate forms, but it has some unique features unheard of from other browser or html libraries on BYOND. These features include easy support for resource handling, multiple viewers, form handling, and timers.
Another convenient feature in the library is the ability to easily handle and send resources such as images and sounds to the client. The resources associative list defined in the datum is a list of runtime resources you want the form to send to the user when it is created. The key of the list is the resource, while the association is an optional name for the file you want to send.
You are also able to dynamically send resources to the viewers of a form. If you have a dynamically generated resource such as a custom icon, then you can use the LoadResource() proc defined in the form to send a resource that you could not send through the resources list. The first mandatory arg for the proc is the resource itself, and the second optional arg is the name of the resource. You should specify the name of the resource if it is dynamically generated. The following is a form demonstrating both predefined and dynamic resource handling methods.
The above example shows how you could use a combination of predefined and dynamic resources to display a dynamic image between two star icons on top of a banner png. LoadResource() can be called anywhere. However, it is up to you to decide when it is appropriate to call it and to think ahead of the necessity of sending the resource.
The upForm Library gives you a fairly good amount of control over the creation and display of the form objects. For example, if a certain form could not be sent to a client, then upForm() will return null. Knowing this, we can handle the case where a form could not be sent to a user, such as the attempt to send a Battle Challenge page to a player who is already in a Battle. In this case, you could store the return value of upForm(), and check whether a form was returned by checking if a reference was sent. If it wasn't, then the form was not able to be sent to that client.
But wait, how do we know whether a form can or can't be sent to a client? By default, an interface that displays to the same interface control in the client's form list, and a browser interface that is being sent when another browser interface is being displayed will not be allowed to display the form to that client. A form can't be sent if it interrupts another form object. This prevents the output coming from another form from being interrupted by a new form.
If you want to define your own conditions, such as the incomplete battle challenge request page, then you will need to override the existing canDisplayForm() proc. This proc belongs to the /upForm datum, and the arg is the client whom the form is being sent to. Here is how you could disallow a battle challenge to be sent if the player is currently in a battle:
The important part of the code is the implementation of canDisplayForm() in the above example. Notice how the proc calls and sets the default return value to . to further extend the behaviour of the original functionality. That way, we do not overwrite the original library functionality that prevents forms from interrupting other forms. Using this method, you can easily add more and more conditions to whether a form can be displayed.
Most commonly, you will want not want two windows of the same type to be displayed to the player. The library defines a client proc, upForm_isViewingForm() to easily check whether a certain player is viewing a certain form, or type of form. The arg for the proc accepts either an instance, a path, or a tag. It will return true a valid instance is found in the list. A window that should not be displayed more than once to the user at one given time would use this proc to check if any instances of the same type of the window we are trying to send is in the client's form list (to be explained in the next section). A definition of a window of such would look like this:
There may be several cases when you will want to have many players be viewing the same page. With this in mind, the library has support for multiple viewers. To display a form to many players, you will want to specify the target when you call upForm() as a list of clients and/or mobs. When you send a list, the library will know whether to display the form to one or many players. Here's a small example on how to display "Hello" to everyone in the world.
Fundamentally, the only difference between having a form that is only displayable to one player opposed to many players is that you will need to handle the case when a page is not successfully sent to a set of clients. The library makes it as easy as possible to catch these types of errors. When a form has failed to be sent to a client, then it will not be inside the upForms client variable. upForms is a list that contains all of the instances of forms that the client is currently viewing. To catch any cases where a form is incorrectly sent, you can loop through the list of clients you generated to check whether the form instance is located in the list by using the upForm_isViewingForm() proc, and sending the reference of the stored form through the arg.
The code above displays the clients who would not have that form displayed to their screens. You can use this to handle the case when a form is not possible to be displayed to a client's screen. By default, a client can't have an interface that interrupts an existing interface that is on the client's screen. For example, you cannot have two form objects that display to the same browser interfaces.
There are many ways to handle multiple viewers, and incorporate it with what the body displays. The /upForm datum stores the viewers in the viewers list variable. By looping through the list, you can create a form that will display all of the viewers currently viewing the form.
Global interfaces are a fancy way of saying a type of interface that would be shared by many viewers, yet will still exist when there are no viewers. These types of forms are created with the purpose that viewers will be freely added or removed from the form at a later time.
To create a global interface, you must: define the UPFORM_SET_SHARED flag in the settings var of the form definition, and call upForm() with a null target. Here is how you would define a global interface that displays "Hello world" by adding the player to the viewer list.
Note that the way that we are handling the form is by calling AddViewer() and RemoveViewer() respectively to display and close the form for the viewer. Although this is not a practical usage of a shared interface, you could use this model for something much more usable, such as a player list that updates when a player logs in or out. If you want the page to regenerate when a viewer is added or removed, then you will want to turn on the UPFORM_SET_RELOAD_ON_VIEWER_UPDATE flag. This example does not need it because it displays a constant message, but a dynamic player list interface would definitely want to be updated.
Handling HTML forms is the main purpose of this library, hence its name. HTML forms are commonly used in websites that allow you to enter a set of information, and process it all at one time. The upForm datum gives you a plethora of functions and resources to make form handling as easy and intuitive as possible. There are three procs that you must implement when handling forms: FormInitTempVars(), FormSetTempVars(), and ProcessVariable().
To tell the datum that you want it to handle forms for you, you will have to turn on the UPFORM_SET_HANDLE_FORMS flag in the settings var. When the flag is on, the form object will know to separately handle any form data through its form functions. Next, you will have to implement the FormInitTempVars() proc. This proc uses the initFormVar() proc to set the initial values of the entries of a form control that you will eventually define in the body of your page. initFormVar() takes three args in this order: the form name, the variable name, and the value of the variable. For every single form, and form input, you will have to call initFormVar() to initialize and declare the value of the input of a certain form. Internally, this creates indexes for the values of a form to be recalled later. The form stores all of the values, whether they are valid or invalid, for future reference.
You also will have to implement the mirrored function, FormSetTempVars(). Opposed to the above proc that was explained, this proc gets called when a form is successfully submitted, and you want to set the variables that were submitted, or perform some other action. The argument that is sent through the proc is the name of the form that was submitted, so you can set the correct values of the variables. To get the values of the internally-stored variables, you must use the proc getFormVar(), which takes the form name and input name as the args, respectively. The proc returns the value of the input control, given the form name and input name that you have sent through the args. In FormSetTempVars(), you will set the variables that you intended to change using the HTML form. getFormVar() is also used when you write the HTML code for your form. You use this proc to get the value of the control in the page.
The other main proc that you will need to override is ProcessVariable(). This proc gets called when a form is submitted, and variables need to be processed. It is called for every single input that you have defined in your form. The proc's args hold three variables: the form name, the input name, and the value of the variable as a text string. You are expected to set the value of the input that was sent by using the setFormVar() proc, which also requires the same 3 args. If the variable is not valid, you must return a text string with the error, which you may choose to display on the screen.
When handling forms, GeneratePage() will have a list sent to it if there are errors in the page. This list is an associative list containing the values for the name of the input, and the error associated with the input which was returned by ProcessVariable(). This gives you the option of displaying an error on a screen for a particular variable such as the case of having too many characters in your name, or if you attempt to write a negative number for your age.
Writing an HTML form is not simple, so here is a relatively simple example for a page that has a form that will accept a name and a password.
There are some things you may have noticed in the above example. When you are generating a page with an HTML form, you must have two hidden inputs sending information about the form reference, and the name of the form. This will allow the HTML form to be handled by the form functions rather than Link(). A generic form would look like this:
Another thing you may have noticed is that we can override the FormSubmitSuccess() proc to do something on the event of a successful form submission. The name of the form is sent through the arg. By default, it reloads the page. In the above example, we close the form when the submission is successful. There is also the FormSubmitError() proc, which is called when a form with a validation error is sent. By default, it reloads the page with the errors. The form name and list of error is sent through the args.
HTML forms are a useful and powerful tool to quickly submit and review information all at one go. The library can support as many forms as you like, but you will have to specify the form values for each of them.
Timers are a simple yet useful feature for forms, and have been supported in this library due to the convenience of having an internal timer rather than having to manually implement timing or handling the time externally. Time is mainly handled with the time_left variable. This is the time (measured in ticks) left that until the page will be deleted. If you define an upForm datum with the time being a positive non-zero integer, then the form will be deleted in that duration.
This is the most common usage of a timed interface, which closes the page after a certain time. If you want finer control over the time, then you can modify the time_interval variable. By default, it is 10 ticks, which means that the timer will update every 10 ticks. This variable usually does not need to be changed, but if you want the form to be deleted in an interval of less than 10 ticks, then you will want to modify this number so time_left % time_interval = 0.
However, the main purpose of the time_interval variable is to control the refresh rate of a timed form that you want updated within the interval you defined. If the time_update variable is 1, then the page will be regenerated once per time_interval ticks. This is useful if you want the viewers to know the value of the counter. Below is an example showing a countdown from 10 to 0.
The above example uses two new procs: TimeUp() and hasTimeStarted(). As the names of the procs imply, TimeUp() is called when the timer reaches zero. By default, it will delete the form when the time runs out. The time bomb example overrides the functionality to prevent the timer from deleting the form so a message could instead be displayed. hasTimeStarted() returns true if the timer is running. This proc is useful if you want to display something different when the timer is running.
Two other procs exist for timers. TimeStarted(), similar to TimeUp() is called when the timer is started. If time_left is defined in the form definition, then it will be called when the form is created. If you don't want the timer to start when the form is created, then you can call StartTimer() manually, submitting the time in ticks through the arg. This will manually start the timer if it hasn't started already. StartTimer() will not sleep, so you do not need to call spawn().
The features explained in this section do not require as much depth as the above features, and are mainly used for convenience.
The interface_bgstyle variable is used for the specific case of an form with the form_type of UPFORM_INTERFACE or UPFORM_BROWSER. By default, when a form is closed, it will remove all of the text from the browser control. BYOND will display a blank white screen. In the case that you don't want a white background to be displayed, interface_bgstyle is used to display an empty page with a custom CSS definition. So if your game has a dark background, then you may want to define the variable as a dark grey background for the blank page.