Changing Room Programming Library (libcirce)
Posted: Thu Dec 27, 2007 5:30 pm
Okay, so it's not exactly the nicest looking page for the time being, but the new and old changing rooms are slowly starting to get updated and moved to http://www.magicalsailorfuku.net/changingrooms/. Autumn, you might want to take a look at the examples (especially the original changing room, since it's relatively simple) to see how the little Javascript library I wrote up (libcirce) works to ease making these things.
(Technological ramblings ahoy!)
Basically, I split up content from style and code, so that each page is now split into three files. For example, there's
http://www.magicalsailorfuku.net/changi ... gRoom.html
http://www.magicalsailorfuku.net/changi ... ngRoom.css
http://www.magicalsailorfuku.net/changi ... ingRoom.js
The HTML file is more or less a template. As you can see from the code, about all it is is the HTML framework that the data is pushed into.
A bunch of <span> elements are used to serve as placeholders for generated data. In particular, anything with a class name that begins with 'var' corresponds to a placeholder for a particular variable. For example the class name 'varFirstName' is used as a placeholder for the firstName variable. You can reuse the class name and have the same value placed in multiple locations, such as if you want to have several sentences that have the victim's name. You can also style based on the class name.
The CSS file is simple. The stylesheet information is just spun off into a different file.
The JavaScript file is where the action's at, and ties in with the order of scripts in the HTML header.
The main library that does the magic we need is lib/libcirce.js. You don't need to know the details, other than that it provides all the functionality we need. It depends on lib/prototype.js (the Prototype library), and other scripts can be used as well as needed. Thus, prototype must be included first, followed by libcirce, and lastly, the actual script file.
The script file itself is actually pretty easy to write. You just need to create an instance of the ChangingRoom class, which you will use for customizing the specific room.
The class provides a handy helper function, addRandomVariable() that takes, as its first argument, the name of the variable you're adding (e.g. 'firstName' or 'FirstName' will correspond to 'varFirstName' in the HTML file), and the remaining arguments are the possible values that variable may take. The arguments are saved as an array to (instance).randVars.(property), (e.g. this.randVars.FirstName). This helper function also adds a convenient getter function to the instance, 'get(variablename)'. By this I mean that a variable with the name 'firstName' or 'FirstName' will have a function created, 'getFirstName()', which will fetch a random value from the possible first name values.
The values you specify are evaluated with 'eval()', so it IS possible to nest randomization values, as in vg0.js's gen1 variable (You will need to make sure that quotes are balanced. You don't have to add quotes to each side of a possible value. It's already 'quoted' before a random value is plucked out, so "this\" + that + \"okay" will be evaluated as eval("\"this\" + that + \"okay\"")).
The default 'getter' functions can be called easily by way of '(classname).(functionname)' so that you could easily call this.getFirstName() in an instance method, or even changingRoom.getFirstName() from outside the class itself. You can also overwrite the default getters and make new ones if you have some dependencies or special logic you want to implement, as both changing rooms have.
When it comes down to actually updating the page however, you need to take a look at the bottom of the Javascript files. In particular, note the presence of 'function refresh()'. This function (or any other function name really) serves as the 'refresher' function for the particular page. The general principle of the 'refresh' function is that it a) sets up the refresh links so that the page need not be reloaded, and b) it actually updates the page. How it does so is with the simple two-part dance:
you = (changing-room-instance).doTransformation();
you.updateDocument();
The former line generates a victim by calling all functions that start with the string 'get', and assigning these values to appropriate properties. For example, getFirstName() would be called, and the value created would be set to you.firstName. All other 'getters' are called in this fashion until exhausted, after which the 'transformed victim' is returned.
It is important to note that all functions that begin with 'get' should take no arguments. Why? These functions are called automatically when updating the page, and the library is not smart enough to realize that arguments must be passed. If you absolutely must pass an argument to a function, you may want to create a wrapper-getter, like getMoves() in vg0.js that wraps the arguments needed for processing and creating moves.
updateDocument, on the other hand, takes all string and array valued properties of the object and updates the document. For example, it would note the property firstName, and then update ALL elements of class 'varFirstName' in the document to have innerHTML equal to the value in property firstName. Some special handling is done for arrays. An array, like move from vg0's 'getMove()' function, is translated to a series of classes 'varMove_0', 'varMove_1', and so on. These are updated similarly. The only difference is the underscore syntax to represent array values.
A couple of things to be careful of:
It is possible to create dependencies (e.g. how zodiac sign depends on the birthday) through two steps:
1. The getter function should be added to an array, (changing-room-instance).dependents. Be careful though, as the library doesn't resolve dependencies. All it does is withhold processing of the functions listed in the array until after all other getters are processed. It then processes dependents in order. For example, 'getWesternZodiac' depends on 'getBirthTime' to have been executed before it can execute. It, hence, goes into the dependents array, so that it is postponed until all others have been processed.
2. Before the victim is complete, you can access values generated through 'this.curVictim.(propertyname)', if it's an instance method, or '(instance).curVictim.(propertyname)' outside the class. For example, getWesternZodiac gets the generated birth time by getting it from 'this.curVictim.birthTime'. You can then manipulate it normally, but don't forget to add it as a dependent.
Also, be careful of capitalization as much of the code is case-sensitive:
- Things that have the first letter of the variable, capitalized (e.g. 'firstName' -> 'FirstName'): getters (getFirstName), variable holders (varFirstName), and the array of random values (this.randVars.FirstName).
- Things that have the first letter of the variable, downcased (e.g. 'FirstName' -> 'firstName'): properties of the victim (you.firstName, this.curVictim.firstName).
Finally, the last and perhaps most important question is how to actually set up the changing room to work:
Prototype makes this easy: 'Event.observe(window, 'load', refresh);'. This just sets up the refresh function to run at page-load.
(Technological ramblings ahoy!)
Basically, I split up content from style and code, so that each page is now split into three files. For example, there's
http://www.magicalsailorfuku.net/changi ... gRoom.html
http://www.magicalsailorfuku.net/changi ... ngRoom.css
http://www.magicalsailorfuku.net/changi ... ingRoom.js
The HTML file is more or less a template. As you can see from the code, about all it is is the HTML framework that the data is pushed into.
A bunch of <span> elements are used to serve as placeholders for generated data. In particular, anything with a class name that begins with 'var' corresponds to a placeholder for a particular variable. For example the class name 'varFirstName' is used as a placeholder for the firstName variable. You can reuse the class name and have the same value placed in multiple locations, such as if you want to have several sentences that have the victim's name. You can also style based on the class name.
The CSS file is simple. The stylesheet information is just spun off into a different file.
The JavaScript file is where the action's at, and ties in with the order of scripts in the HTML header.
The main library that does the magic we need is lib/libcirce.js. You don't need to know the details, other than that it provides all the functionality we need. It depends on lib/prototype.js (the Prototype library), and other scripts can be used as well as needed. Thus, prototype must be included first, followed by libcirce, and lastly, the actual script file.
The script file itself is actually pretty easy to write. You just need to create an instance of the ChangingRoom class, which you will use for customizing the specific room.
The class provides a handy helper function, addRandomVariable() that takes, as its first argument, the name of the variable you're adding (e.g. 'firstName' or 'FirstName' will correspond to 'varFirstName' in the HTML file), and the remaining arguments are the possible values that variable may take. The arguments are saved as an array to (instance).randVars.(property), (e.g. this.randVars.FirstName). This helper function also adds a convenient getter function to the instance, 'get(variablename)'. By this I mean that a variable with the name 'firstName' or 'FirstName' will have a function created, 'getFirstName()', which will fetch a random value from the possible first name values.
The values you specify are evaluated with 'eval()', so it IS possible to nest randomization values, as in vg0.js's gen1 variable (You will need to make sure that quotes are balanced. You don't have to add quotes to each side of a possible value. It's already 'quoted' before a random value is plucked out, so "this\" + that + \"okay" will be evaluated as eval("\"this\" + that + \"okay\"")).
The default 'getter' functions can be called easily by way of '(classname).(functionname)' so that you could easily call this.getFirstName() in an instance method, or even changingRoom.getFirstName() from outside the class itself. You can also overwrite the default getters and make new ones if you have some dependencies or special logic you want to implement, as both changing rooms have.
When it comes down to actually updating the page however, you need to take a look at the bottom of the Javascript files. In particular, note the presence of 'function refresh()'. This function (or any other function name really) serves as the 'refresher' function for the particular page. The general principle of the 'refresh' function is that it a) sets up the refresh links so that the page need not be reloaded, and b) it actually updates the page. How it does so is with the simple two-part dance:
you = (changing-room-instance).doTransformation();
you.updateDocument();
The former line generates a victim by calling all functions that start with the string 'get', and assigning these values to appropriate properties. For example, getFirstName() would be called, and the value created would be set to you.firstName. All other 'getters' are called in this fashion until exhausted, after which the 'transformed victim' is returned.
It is important to note that all functions that begin with 'get' should take no arguments. Why? These functions are called automatically when updating the page, and the library is not smart enough to realize that arguments must be passed. If you absolutely must pass an argument to a function, you may want to create a wrapper-getter, like getMoves() in vg0.js that wraps the arguments needed for processing and creating moves.
updateDocument, on the other hand, takes all string and array valued properties of the object and updates the document. For example, it would note the property firstName, and then update ALL elements of class 'varFirstName' in the document to have innerHTML equal to the value in property firstName. Some special handling is done for arrays. An array, like move from vg0's 'getMove()' function, is translated to a series of classes 'varMove_0', 'varMove_1', and so on. These are updated similarly. The only difference is the underscore syntax to represent array values.
A couple of things to be careful of:
It is possible to create dependencies (e.g. how zodiac sign depends on the birthday) through two steps:
1. The getter function should be added to an array, (changing-room-instance).dependents. Be careful though, as the library doesn't resolve dependencies. All it does is withhold processing of the functions listed in the array until after all other getters are processed. It then processes dependents in order. For example, 'getWesternZodiac' depends on 'getBirthTime' to have been executed before it can execute. It, hence, goes into the dependents array, so that it is postponed until all others have been processed.
2. Before the victim is complete, you can access values generated through 'this.curVictim.(propertyname)', if it's an instance method, or '(instance).curVictim.(propertyname)' outside the class. For example, getWesternZodiac gets the generated birth time by getting it from 'this.curVictim.birthTime'. You can then manipulate it normally, but don't forget to add it as a dependent.
Also, be careful of capitalization as much of the code is case-sensitive:
- Things that have the first letter of the variable, capitalized (e.g. 'firstName' -> 'FirstName'): getters (getFirstName), variable holders (varFirstName), and the array of random values (this.randVars.FirstName).
- Things that have the first letter of the variable, downcased (e.g. 'FirstName' -> 'firstName'): properties of the victim (you.firstName, this.curVictim.firstName).
Finally, the last and perhaps most important question is how to actually set up the changing room to work:
Prototype makes this easy: 'Event.observe(window, 'load', refresh);'. This just sets up the refresh function to run at page-load.