- JSON (JavaScript Object Notation)
- Your First Byondclass
- Configuring your byondclass
- [ Download Lesson 2 Project Files ]
Lesson #1 | Lesson #2
In the previous instalment of the Webclient Tutorial series, we learned how to get a very basic, barebones interface up in the webclient as well as how to actually run a webclient game. In this next lesson we'll talk about two big ideas: byondclasses and JSON. To give you a brief overview before we delve any further, a "byondclass" is simply a type of control that we can use in our interface markup. They help organize our interface into objects that have their own properties, functions, etc. For example, "output" and "map" are byondclasses we're familiar with from the old dmf format.
JSON is a more advanced but fundamental topic that we'll have to tackle before anything else, as defining a new byondclass requires you to know how a JavaScript anonymous object works. Basically, JSON is just a format that we'll use to store and transfer data.
JSON (JavaScript Object Notation)
We use JSON heavily when writing code in JavaScript, and consequently, in defining behaviors for our byondclasses. If by now you don't already know some basic JavaScript I highly, highly urge you to take a few basic courses over at CodeAcademy or W3 Schools. For those that wish not to, or are comfortable progressing; let's start with a very basic object in JavaScript.
{
message: "Hello Web!"
}
/*
Whitespace is ignored, so it can also be written in a single line:
*/
{message:"Hello Web!"}
The first thing to note are the opening and closing brackets. In JavaScript, they are the tokens used to let the code know that we're starting to make a new object. You can think of it as serving the same purpose as indentation in DM. What this code is saying is that inside the object we're creating there is a variable called message and it contains "Hello Web!". If we want to read the variable, then we can simply do object.message to retrieve it. Alternatively you can treat the object as an associative container and do object["message"]. Whatever works best for you.
Now let's define a list in JSON:
["Apple", "Orange", "Tomato", "Grape"]
It's pretty simple, and can be loosely translated to list("Apple", "Orange", "Tomato", "Grape") in DM. A list type is dynamically-indexed, meaning you don't need to worry about static array lengths. An object in this array be retrieved by performing array[index]. Be careful! Outside of BYOND most languages will start arrays at 0. Therefore, if you want to get "Orange", you won't look in index 2. Instead, you'd look at index 1 since index[0] = "Apple". This may confuse and disorient you, but as long as you remember to subtract your indexes by 1, you'll be fine.
Let's get really tricky and wrap this all together into one big object:
var Bob = {
name: "Bob",
age: 37,
gender: "male",
employment: {
company: "Professional Business Inc.",
salary: 40000,
boss: "Chief Executive Rodger Smith"
},
thoughts: [
"I need to go to the store.",
"What's the wife cooking this evening?",
"Videogames are fun."
]
};
This object is defining a person (variable name Bob) with the name of "Bob", whose age is 37 and gender is "male". But you'll also notice that the employment and thoughts members are complex containers, not just values. They can be thought of as sub-objects and arrays within the main object, kind of like a player's inventory!
Bob.employment contains information about his company, salary, and his boss. Bob.thoughts is a small array of random thoughts on Bob's mind. In JSON objects can be infinitely-complex. Arrays can contain objects, objects can contain many layers of internal objects, and etc. JSON is really just a neat way of organizing data anonymously - that is, without requiring the definition of a class beforehand.
Before proceeding to the next section, try to:
- Define an item object complete with fields for a name, weight, description, etc.
- Use those fields to organize a bunch of random objects into a single JSON array, so you have an array of objects.
- Create a player object with some random members, and include that array as part of the player's "inventory" member.
Your First Byondclass
Next up, we'll tackle creating our very own byondclass. A byondclass can be used for anything in a UI, from just organizing internal data to actually displaying information. For the most part, we'll be using it to create custom UI elements.
For our example environment, let's create a byondclass that is:
- Invisible until called upon.
- Overlays everything on the screen.
- Can have its text set via DM code.
- Can display the player's icon.
- Can inform the player when a button has been clicked.
I'm basically describing a custom alert popup that displays text, an icon, and a button to close it with some confirmation on the server-end that the client closed the screen. For this, we'll create a new file called first_byondclass.dms [+]. In this file, we'll add the following code:
<!-- I'm an HTML comment! Just so you know, if you see me around again. Don't actually include me in your code. -->
<byondclass name="firstclass">
<!-- Our CSS styling goes here -->
<style>
</style>
<!-- Our JavaScript code goes here -->
<script>
</script>
<div id="content" class="content"></div> <!-- The div that will be containing our text. -->
<div id="button" class="button">Okay</div> <!-- The div representing our confirmation button. -->
</byondclass>
Anything in the byondclass tags can be considered as part of a separate "document" which contains information about that specific class. Meaning whenever we actually use this class, all code within will be included along with it. Inside the class we have style and script tags. We also have some stray divs, which help divide our control into separate nodes. We'll need them when we want to individually update one or both with separate text.
Within the script tags, we'll start to include our basic code. Everything within script must be in the form of a single JavaScript object. Meaning it will always begin and end with brackets:
<script>
{
fn: {
}
}
</script>
Here we're defining a single object with an fn member. This is one of the few key members in a byondclass' code structure. We'll use it to define code that can be called via DM. For instance, we'll now define a function called whenever DM tries to output() to it:
<script>
{
fn: {
output: function(obj) {
if(obj.hasOwnProperty("text")) {
this.ui.content.innerHTML = obj.text;
}
}
}
}
</script>
What we have now is an output member containing a function. JS objects can also contain functions much like BYOND objects! This function accepts the argument obj which can but won't necessarily always contain the text member. So obj is, itself, a JSON object. The hasOwnProperty() function simply makes sure the object has text before moving onto the next step:
Setting the text to the "content" div to obj's text data. Previously we defined a div by the id of "content", which can be pointed to within the scope of our entire object through this.ui, which is a container of all named items in the byondclass.
Configuring your byondclass
Next up, we'll add some fun styling to our element:
<style>
#firstclass {
width: 200px;
height: 150px;
position: absolute;
left: calc(50% - 200px/2);
top: calc(50% - 150px/2);
background: #fff;
box-shadow: 0px 0px 25px #fff;
border-radius: 15px;
padding: 15px;
text-align: center;
}
.button {
width: 80px;
height: 25px;
margin: auto;
border: 1px solid grey;
background: #BABABA;
}
</style>
This gives our element some nice definition, and also begins to flex some muscles that would be ostensibly impossible or extremely difficult to do on DreamSeeker. You'll see what I mean in a bit.
Next, let's actually add the byondclass to index.dms. It doesn't matter where, as long as it's under the topmost scope.
<div id="firstclass" byondclass="firstclass" skinparams="is-visible=false"></div>
But what does matter is that the fact that we're initializing the byondclass with is-visible set to "false". We don't want it to be visible by default, only when we need it. Next, we add a macro, doesn't matter what key (remember Lesson #1) pointing to a "showsubprompt" verb which calls the following code:
verb/showsubprompt()
winset(src, "firstclass", "is-visible=true")
src << output("I'm a prompt!", "firstclass")
Compile it and run, and you should get the following result: [+].
Congratulations, we've got our first custom byondclass psuedoprompt up and running! Unfortunately there's no way to close it, and we haven't yet fulfilled the previously-defined requirement of being able to communicate back with the server, and display the player's sprite. We'll finish our control and delve into the more complex tasks in Lesson #3! Stay tuned.