Classes & OOP: Fields and Methods

Roger PoonBy Roger Poon

JS++ Designer and Project Lead

Creating and Rendering an Animal

Open 'src/Animals/Cat.jspp' and enter the following code:

        external $;

        module Animals
        {
            class Cat
            {
                void render() {
                    var $element = $(
                        """
                        <div class="animal">
                            <i class="icofont icofont-animal-cat"></i>
                        </div>
                        """
                    );

                    $("#content").append($element);
                }
            }
        }
        

One thing you may immediately notice is the multiline string ("""..."""). When we surround our string in triple quotation marks, we can write a "multi-line" string. Multi-line strings are a feature of JS++ that are especially useful when dealing with HTML. You'll notice that, with multi-line strings, we didn't have to escape our double quote characters (") or newline characters.

Besides the multi-line string, we're just passing some HTML to jQuery via the $(...) as usual. jQuery will recognize this as HTML and dynamically create the HTML elements for us. However, the dynamically-created HTML elements are still only held in memory unless we render it to the page. We do that with this line:

$("#content").append($element);

You'll notice we named our variable for the jQuery element as $element. It is a common naming convention to prefix variables holding jQuery objects with the $ sign.

You may also have noticed that we declare the data type for the jQuery $element variable as 'var'. As we discussed in previous chapters, there may not always be a corresponding JS++ data type for complex objects so we can just use 'var' in these cases (or even just for convenience).

Side Note: jQuery and the Document Object Model (DOM) API for manipulating web pages can be classified as "highly-dynamic." Some large companies were incorrect to try to add static data types in these cases as there are many corner cases where static data types will be incorrect or fail – such as for ECMAScript "host objects, " which includes the DOM API, where "implementation-defined" behavior, such as different browser implementations of the same DOM API methods, is valid according to the language specification (and indeed occurs in real-world implementations). Another example is the garbage collector implementation in versions of Internet Explorer where the GC can reclaim non-cyclic, in-use host objects (such as the "htmlfile" ActiveXObject used for real-time streaming) too early, a scenario that is not amenable to static analysis. Despite their convenience, the static data types in these cases create a "false sense of security." However, discussion of these topics is outside the scope of this tutorial.

You will notice that we declared a function inside our 'Cat' class. A function declared inside a class is commonly called a "class method" or just "method" for short. Functions that are not members of a class are known as "free functions."

We declared a class inside a module, so this file by itself will not compile. We need to create a "main file" to act as the program entry point. In your 'src' folder (the parent folder of the 'Animals' folder), you'll recall we created an empty main.jspp file. Open the main.jspp file and enter these lines of code:

        import Animals;

        Cat cat = new Cat();
        cat.render();
        

We first instantiate the class to get a class instance. Once we have a class instance, we call the 'render' method on that specific class instance.

Compiling

Compilation is going to be slightly different compared to our previous tutorial examples due to our deep directory structure. Mac/Linux users won't have to make much of an adjustment, but, for Windows users, we have to start using the Command Prompt.

For Mac/Linux users, just open your terminal to the 'OOP' code folder and enter the following command:

$ js++ src/ -o build/app.jspp.js

For Windows users, open the Command Prompt. Navigate to the directory of the OOP 'code' folder (using the 'cd' command). Once we navigate to the folder, we enter the same command as the Mac/Linux users:

> js++ src/ -o build/app.jspp.js

If everything worked, you should see a green "OK" as shown in the screenshot above (for all operating systems: Windows, Mac, and Linux).

Open index.html (in the root 'OOP' folder) in your web browser. You should see a small cat icon rendered to the page:

Styling the Animals

Right now, our cat is quite small and hard to see. Let's modify the styling to make our cat bigger and clearer.

Open style.css in the 'style' folder. It should be empty, but we'll add one simple CSS rule to make our cat clearer:

.animal i { font-size: 50px; }

Save style.css and refresh index.html. You should see a larger cat.

Class Fields

Beyond methods that define the behavior for a class, classes can also hold data of their own. At its core, you can think of classes as data plus methods that operate on that data. For example, with pets like cats, we may want to be able to give the cat a name. The name is the data and examples of methods might be functions that allow us to set or change the name.

Class fields allow us to specify data that is exclusive to the class. Fields are just variable declarations:

        class Cat
        {
            string name = "";
        }
        

Class fields differ from regular variable declarations because they can only be accessed via the class or class instances. Furthermore, we declared a 'name' field that is exclusive to a class instance above; this means that each unique instance of the class will have its own unique name data.

Data that is unique to a specific instance can be very useful, especially when dealing with a large number of similar objects. However, our current code that renders the cat to the page actually doesn't keep data unique to an instance! Let's observe; change the main.jspp code as follows to call render() a second time:

        import Animals;

        Cat cat = new Cat();
        cat.render();
        cat.render();
        

Compile the code and open index.html. You should see two cats rendered, but we have only one instance of the 'Cat' class. This is not ideal; rather, we should like to require two 'Cat' instances if we want to render two cats to the page. In order to do that, we have to examine our render() code:

        class Cat
        {
            void render() {
                var $element = $(
                    """
                    <div class="animal">
                        <i class="icofont icofont-animal-cat"></i>
                    </div>
                    """
                );

                $("#content").append($element);
            }
        }
        

Do you see what's wrong?

Each time the render() method is called, we are calling jQuery to create a new object, and then we render that new object to the page via jQuery's append() method. What we need to do is to make the creation of the page elements exclusive to class instances. We can do this by simply moving the variable declaration in render() to a class field. Here's our full code:

        external $;

        module Animals
        {
            class Cat
            {
                var $element = $(
                    """
                    <div class="animal">
                        <i class="icofont icofont-animal-cat"></i>
                    </div>
                    """
                );

                void render() {
                    $("#content").append($element);
                }
            }
        }
        

We've done nothing in the above code other than move the variable declaration from render() to the class itself so it becomes a field. Now, the field will only be initialized when we instantiate the class. In other words, the jQuery function call you see that turns HTML into a jQuery object will not be executed until we instantiate our class using the 'new' keyword.

Keep your code exactly as you had it with the two render() calls for the same class instance. However, with the updated Cat.jspp, compile your code again. Open index.html. You should see only one cat rendered to the page.

Now let's try creating two cats by updating main.jspp to use two class instances:

        import Animals;

        Cat cat1 = new Cat();
        cat1.render();
        Cat cat2 = new Cat();
        cat2.render();
        

You should now see two cats, one for each class instance:

Naming our Animals with Fields and Methods

While our first example for class fields involved naming, we never actually added a name field to our classes. However, rather than just naming our cats, let's also make it tangible by making the name visible in the web browser when we hover over the animal. As we showed in our previous example, fields like the name should be unique to the class instance – no two cat elements on the page should be showing the same name if they were given different names.

Insert the following code into Cat.jspp:

        external $;

        module Animals
        {
            class Cat
            {
                string name = "";

                var $element = $(
                    """
                    <div class="animal">
                        <i class="icofont icofont-animal-cat"></i>
                    </div>
                    """
                );

                void setName(string name) {
                    this.name = name;
                }

                void render() {
                    $("#content").append($element);
                    $element.attr("title", name);
                }
            }
        }
        

We added a new field: 'name' with type 'string'. We also added a new method 'setName'. It contains only one line of code:

this.name = name;

You'll notice the method takes one parameter called 'name':

        void setName(string name) {
            this.name = name;
        }
        

In order to disambiguate the 'name' parameter from our 'name' class field, we need to use 'this'. When two conflicting names appear in the same method, this is known as "variable shadowing." It's a convenience so that we don't have to come up with different variable names to describe the same concept. Thus, what our statement is saying is: set the 'name' class field ('this.name') to the 'name' value passed in as an argument to our method.

The 'this' keyword inside a class refers to the class instance. Class instance methods, like the 'setName' method we just defined, cannot be called unless the class has been instantiated first. Once we've instantiated the 'Cat' class, we can call the 'setName' method. When we call the 'setName' instance method, the 'this' keyword refers to the class instance that executed the method. Thus, the above statement will set the 'name' field of the class instance that 'setName' is being executed on; it is always unique to the class instance; thus, no two class instances will end up having the same 'name' field set after a 'setName' method call.

Finally, we added the following statement to our 'render' method:

$element.attr("title", name);

This will set the HTML 'title' attribute of our cat HTML element that we render to the page. By setting the 'title' attribute, we can set the text that appears when we hover over an HTML element. In this case, when we hover over our cat element, we will see its name.

Before we can see our results, we must set the names of our cats in main.jspp. Let's do that:

        import Animals;

        Cat cat1 = new Cat();
        cat1.setName("Kitty");
        cat1.render();
        Cat cat2 = new Cat();
        cat2.setName("Kat");
        cat2.render();
        

Make sure 'setName' is called before the 'render' method.

Compile the code. Once again, for Windows, Mac, and Linux users, the command is now all the same:

> js++ src/ -o build/app.jspp.js

If the program compiled successfully, open index.html. Hover your name over each cat. You should see its name.