
2.1.Definition #
object ValueObject.define(string name, function|object definition);
A value object can be defined with either a validate function as second argument or an object representing the definition.
1 2 3 4 5 6 |
ValueObject.define('Email', function(value) { return /(.+)@(.+){2,}\.(.+){2,}/.test(value); }); |
Using a definition object allows you to set more options. Only the validate method is required and the definition will fail if the method is not found.
1 2 3 4 5 6 7 8 9 |
var Email = ValueObject.define('Email', { validate: function(value) { return /(.+)@(.+){2,}\.(.+){2,}/.test(value); } }); |
The object is returned when defining it, but is also stored in the ValueObject library were it can be accessed by name at any time.
1 2 3 |
var Email = ValueObject.Library.Email; |
2.1.1.Validate input #
bool validate(mixed value);
The validate method is the only required property of the definition. It will test the input value for validity; returning true will allow the object to exists, returning false will throw an InvalidArgumentException (or a custom exception if defined).
1 2 3 4 5 6 7 8 9 |
ValueObject.define('Age', { validate: function(value) { return value >= 0 && value < 120; } }); |
2.1.2.Parse input #
mixed parse(mixed value);
parse method allows you to alter the input value before validating it. This might be useful when a proper value could be presented in different forms.
1 2 3 4 5 6 7 8 9 10 11 12 |
ValueObject.define('Age', { parse: function(value) { // Also allow the value to be given as a string or a float. return parseInt(value); }, // ... }); |
2.1.3.Throw custom exception #
void throwException();
Custom exceptions might help you to understand errors in your application. In this method you can throw your own exception, which will be preferred to the default InvalidArgumentException when the validate method returns false.
1 2 3 4 5 6 7 8 9 10 11 |
ValueObject.define('Age', { throwException: function() { throw new InvalidAgeException(); }, // ... }); |
2.1.4.Extend #
string extend
Custom value objects might actually be based on simple data types like String or Number. Being able to use the native methods on those types (like substr, match, split, toFixed, etc.) improves the usability of the value object. With this property you can define the native data type, or even another value object, that methods will be inherited from.
1 2 3 4 5 6 7 8 9 10 11 12 |
var URL = ValueObject.define('URL', { extend: String, // ... }); var url = new URL('http://www.google.com'); console.log(url.split(':').shift()); // logs 'http' |
2.1.5.Custom methods and properties #
mixed *any
Improving the usability of value objects can be done easily by adding custom methods that help you to work with the actual value. Any property you define (function or otherwise) will be passed as-is to the value object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var URL = ValueObject.define('URL', { foo: 'bar', getScheme: function() { return this.value.split(':').shift(); }, // ... }); var url = new URL('http://www.google.com'); console.log(url.getScheme()); // logs 'http' console.log(url.foo); // logs 'foo' |
2.2.1.Instantiation #
Value objects are constructors and can be instantiated using the new
keyword, passing the value as an argument. If the value is invalid an InvalidArgumentException (or a custom one, if defined) will be thrown.
1 2 3 4 5 |
var age = new Age(130); // Throws exception var age = new Age(90); // Works! |
2.2.2.Type casting #
Value objects can be used in any operation. If the object represents a numerical value it can be used in calculations and when it represents a string value it can be concatenated to any other string. This is done using the native valueOf
and toString
methods.
1 2 3 4 5 6 7 8 9 |
var age = new Age(90); console.log(age / 2); // logs 45 var url = new URL('http://www.google.com'); console.log('Please visit ' + url); // logs 'Please visit http://www.google.com' |
2.2.3.Comparison #
Comparing objects has always been kind of an issue in JavaScript. But with value objects it is possible:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var age1 = new Age(90); var age2 = new Age(90); console.log(age1 == 90); // logs true console.log(age1 === 90); // logs false (object === number) console.log(age1 == age2); // logs true console.log(age1 === age2); // logs true |
2.2.4.Type checking #
Value objects can be tested on type using instanceof
in the same way as you are used to with native data types. And similar to native data types, when not using the literal notation, using the typeof
operator will result in ‘object’.
1 2 3 4 5 6 7 |
var age = new Age(10); console.log(age instanceof Age); // logs true console.log(typeof age); // logs 'object' |
2.2.5.External validation #
Sometimes you do not want to try to create a value object, because it might result in an exception. It is possible to use the validate function on the value object constructor to test a value, making sure you have your validation code only in one place.
Please notice! The passed value will not be parsed with the parse method when you use the validate function like this.
1 2 3 |
console.log(URL.validate('foobar')); // logs false |
2.3.1.Multiple arguments #
Sometimes it might seem convenient to pass more than 1 argument to a value object. This can be done, but it requires some special handling in the parse method. Multiple arguments will be received in value
as an array and can be returned from the method in any way you want. But keep in mind that this might affect type casting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var DateRange = ValueObject.define('DateRange', { parse: function(value) { return { start: value[0], end: value[1] }; }, validate: function(value) { return typeof value === 'object' && value.start != 'Invalid Date' && value.end != 'Invalid Date' && value.start < value.end; } }); var range = new DateRange(new Date(2012, 1, 1), new Date(2012, 12, 31)); |
2.3.2.Immutability #
One of the basic properties of value objects is that they are immutable. This means the value can not be changed once it is initialized. This is the case for this library, but there is a weak spot. Objects and arrays (also objects) are passed by reference and are therefor a bit tricky to use as the main value of a value object. Even though you can not override the value
property you can still access its value and alter that (in case of objects). Also if you pass a variable to the constructor and alter that later it will still be a reference.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var Address = ValueObject.define('Address', function() { ... }); var obj = { town: 'Rotterdam' }; var address = new Address(obj); foo.value.town = 'Hilversum'; obj.streetnumber = 45; console.log(address.value); // logs { town: 'Hilversum', streetnumber: 45 } |