This is an object-oriented Servoy tutorial on how to use object-oriented programming in Servoy. Javascript’s core data type is the object, along with five simple data types: Number, String, Boolean, Undefined and Null. Objects are mutable and can be changed, while the primitive types are immutable.
An object can be thought of as an unordered list of name-value pairs. The name is referred to as the property, it must be unique, and it can either be a string or a number. The value can be an actual value (String, Number, Boolean, Date), or it can be a method (inner function).
A simple object looks like this:
1 | var oFruit = {type: "Apple", color: "Red"}; |
Another way to write the same thing, that is easier to read and maintain, is like this:
1 2 3 4 | var oFruit = { type: "Apple", color: "Red" }; |
In this example, the object represents a type of fruit, and its properties can be referenced using object.property syntax like this:
1 2 | application.output("I am an " + oFruit.type + " my color is "+ oFruit.color); // I am an Apple and my color is Red |
If you wanted to use a number for the property name (try to avoid this as it is not recommended), then you would need to refer to the property using bracket notation like this:
1 2 3 4 5 6 7 8 9 | var oAgeGroup = { 0: "Infant", 1: "Toddler", 5: "Child", 13: "Teenager", 18: "Adult" }; application.output(oAgeGroup["5"]); // Child |
Objects are useful because they can store values and functions for us, but also because they can be used as a template to clone new objects from.
Objects can be created two different ways:
1. Object Literals
The simplest way to create an object is using the literal notation like this:
1 2 3 4 5 6 7 8 9 10 11 | // An empty object created using object literal notation var oData = {}; // This is a literal object with 3 properties, one of which is a function var oPerson = { firstName: "Frank", lastName: "Smith", fullName : function(){ return this.firstName + " " + this.lastName; } }; |
2. Object Constructor
The object constructor is a function that is typically used to create a new object from an existing one. It can also be used to create a new object, and then add properties to it, but this is less common.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // Create a new empty object using the constructor, then assign properties to it var oFruit = new Object(); oFruit.type = "Apple"; oFruit.color = "Red"; application.output("I am an " + oFruit.type + " my color is "+ oFruit.color); // I am an Apple and my color is Red //Create a new object from an existing one //A function that will build an object function Fruit (theType, theColor){ this.type = theType; this.color = theColor; } // Then use the new constructor to clone it var oApple = new Fruit("Apple", "Red"); application.output("I am an " + oApple.type + " my color is "+ oApple.color); // I am an Apple and my color is Red |
In the first example, we use the new constructor to create an empty object, and then assign some properties to it. Although this is a valid approach, it is not as elegant (nor as efficient) as using the literal notation shown earlier.
In the second example, we created a constructor that sets its properties to parameters passed in. We do this using the keyword “this”. It refers to the function object that will be returned from the constructor. All functions return something, even if you don’t specify a return, so it is not necessary to say “return this” at the end of the constructor. However, keep in mind, that because you may have inner method(s) inside the constructor , you can return something different from the inner method(s) if you need to (more on this in a moment). It’s also the accepted coding standard to capitalize the name of the constructor function to indicate it is returning an object and can be used as a constructor, whereas all other functions should use camel case.
In the next step, we call the new Fruit constructor and pass it our parameters to create an Apple object. The constructor creates a new Fruit object, assigns the properties to the parameters passed in for the Apple, and returns the object. We can now reference the new Apple object’s properties using the object.properties syntax.
As I mentioned, you can have functions inside your constructors. Here is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function Person (firstName, lastName, title){ this.firstName = firstName; this.lastName = lastName; this.title = title; this.fullName = function(){ return this.firstName + " " + this.lastName; } this.fullNameTitle = function(){ return this.fullName() + ", " + this.title; } } var oContact = new Person("Frank", "Smith", "President"); oContact.type = "Business Contact"; application.output(oContact.fullName()); // Frank Smith application.output(oContact.fullNameTitle()); // Frank Smith, President |
The advantage to this approach is that the method used to construct the full name is contained within the constructor making it easy to reference using the object.method() syntax. Additionally, you could add an unlimited number of methods inside the object, keeping all the methods related to the object in one place, making your code easy to maintain and extend. Its important to note that the methods within the object can call each other (as seen in fullNameTitle), before returning a result, so you can modularize your code inside the object.
You can also see that although the contact object for Frank Smith was constructed from Person, the object is still mutable, and we can add additional unique properties, and even new methods, customizing it further to suit our specific needs. This is why object orientated programming is so powerful; you can create new objects from an existing one, and then modify it further, or even use it to clone other objects from. For clarification, properties like “firstName” and “fullName” are defined in the constructor and are called inherited properties and methods, whereas the “type” property is referred to as the new objects own property (does not exist in the constructor or any other object created from the constructor; it is unique to the “Frank Smith” object in this example).
Finally, we see that we can invoke a method in the object by using the object.method() syntax.
In our example, we had methods inside the base object “Person” that we cloned contact objects from. But what if we had thousands of contacts that we were dealing with? Indeed we need individual properties for all the contact objects we create, but we don’t really need a copy of all our methods for each contact object (that would consume extra memory); we only need one copy of the method(s) that all contact objects can share.
Well, it turns out we can do this using something called the prototype. The prototype allows us to add on to the constructor, so there is only one copy, accessible by all objects that use the constructor. In Servoy, prototype is not handled elegantly, and we have to cheat a little, but here is how to do it with our “Person” constructor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function Person (firstName, lastName, title){ this.firstName = firstName; this.lastName = lastName; this.title = title; } var Person_proto = function(){ Person.prototype = { fullName : function(){ return this.firstName + " " + this.lastName; }, fullNameTitle : function(){ return this.fullName() + ", " + this.title; } } }(); |
What we have done is removed the methods from the constructor and put them in the prototype chain, as is conventional in object orientated programming. However, because Servoy does not read .js files the same way as a browser does (it does not evaluate the entire .js file), we have to wrap the prototype into an immediately invoked function that is assigned to an arbitrary variable (in this case “Person_proto”). What Servoy does when it reads this .js file, is it sees the variable assignment and evaluates the immediately invoked function (it is immediately invoked because of the “()”” at the end) adding the methods to the prototype chain of the “Person” constructor.
We can now use the same syntax to reference the methods on the object (object.method()), but there is only one copy of the method(s) for all objects created from the constructor. This is very important, particularly if you have many complex methods for manipulating the object, and you have thousands of objects that are going to be created using the constructor. When the method is called on the object you created, it looks to see if it can find the method. If the method is not found on the object, then it looks to its constructor. If the method is not found on the constructor, it begins looking up the prototype chain until it finds it.
Using the prototype still allows us to maintain all our methods for the constructor in one place, so our code is easy to follow, maintain, and extend.
In conclusion, use object-oriented programming to take your Servoy development to the next level. Learn to work with objects, the constructor, and the prototype. Once you start using them, you will see the power of the approach, and quickly become an advocate.
Gerard
Nov 5, 2014 -
Hi Gary,
Thank you for this great article! However when i implement your example in Servoy 7.x I noticed that autocompletion does not recognizes the prototype methods. Do you have similar results?
Regards Gerard.
Gary Dotzlaw
Nov 5, 2014 -
Thanks. I’m glad you like the article.
No, I have no problems, and am using 7.4.2. Please check out my slides from ServoyWorld 2014, because this post was written with an earlier version, and the SW2014 presentation uses 7.4.2. It should give you all the examples you need. Download from here:
https://www.servoy.com/forum/download/file.php?id=4833