Aside from the principal methods that can be used to manage the application of ValidationDefs, the AJS_Validator object also presents a clutch of ancillary methods that are intended as an aid to Pre- and Post-Validators, but which can be used by anything within the application in question. This means that they can be used separately from, and without the use of ValidationDefs or anything else covered up to this point in this guide.
Obviously, AJS_Validator needs to perform many checks on ValidationDef objects (to ensure that they are valid), along with checks on the arguments passed to methods to which those ValidationDefs have been applied, which, of course, is the entire point of AJS_Validator. To do this, it uses an internal set of primitives that determine whether a value is, say, undefined or null, or whether, for example, a given object is of a particular class.
Given that Pre- and Post-Validators may need to perform such essential checks while performing their user-defined policing, it would be absurd to cause developers of those functions to re-invent the wheel. Hence AJS_Validator provides a set of ancillary methods that make much of the functionality of its internal primitives available to client code, and this part of the AJS_Validator user-guide covers the nature and use of those ancillaries. In essence, they are a 'Swiss Army Knife' of validation tools, and this page describes them and their use.
Note that these methods are unaffected by the suspend and resume methods of AJS_Validator – you can still test an object using the ancillaries even when AJS_Validator is suspended globally, or when a given ValidationDef has been suspended locally through the use of its associated ValidationDefCtrl. See below for more information on this.
The API documentation presents the signatures of the entire set of ancillary methods formally, and their functionality is presented in the table you see here (which excepts the getClass method because that always returns simply a string).
The key factor is that the names of all the ancillary methods other than getClass start with 'is', and embody the same paradigm, in that they invoke AJS_Validator's exception handler (the function you passed to createAJS_Validator as its onException argument, referred-to herein as 'the exception handler') if the object under examination does not satisfy the condition that the trailing part of the respective method-name symbolises. That is to say that they implement a 'Guilty until Proven Innocent' policy, and this, as the examples below demonstrate, allows you to perform a mass of critical checks on a given object without a lot of messy and verbose...
if (...) { throw ... } else if (...) { throw ... } else if (...) { throw ... }
...constructions.
In other words, you do not need to implement such conditional logic explicitly, because calling a given 'is...' method will ask the question for you.
That is: return from a call to a given 'is...' method means that the condition in question was satisfied, meaning in-turn that your code can progress simply to call another 'is...' method (if appropriate), and so on. However, if the condition in question goes unsatisfied, the corresponding AJS_Validator ancillary-method will call the exception handler.
This paradigm goes a very long way to making Pre- and Post-Validator code more-compact and readable; and the interrogative nature of the 'is' prefix aids code-comprehension to boot. (Do note here that the 'is' prefix does not mean that a given ancillary returns a boolean value – see below.)
Furthermore, more than half of the ancillaries accept a trailing and optional NeverUndefined-argument, which, if true where the object under consideration is undefined, will cause the method in question to return silently. This obviates confirming the presence of a non-critical object before you test its nature/value, and this reduces code-verbiage still further.
Note that the is-Boolean/-Number/-String methods (and their _Not complements) exist because of the distinction that ECMAScript draws between boolean/number/string objects and boolean/number/string literals (as mentioned in the Specifying Classes section of this guide). AJS_Validator needs to distinguish between the object and literal forms, because the object forms can carry an AJS_Validator_Tag, and can be frozen etc., whereas the literals cannot. You, however, may not care, at a given point in an application's execution, which flavour of boolean/number/string that a given Pre-/Post-Validator of yours is testing.
In other words, within a given Pre-/Post-Validator, you may wish to confirm that a given object is a boolean, number or string logically, irrespective of implementation-variance. Yet, were you to pass the object in question to isOfClass, passing 'String' (for example) as the name of the class you seek, that ancillary method would call the exception handler were the object a string literal. Hence the is-Boolean/-Number/-String family of methods, and their _Not complements, are dedicated to resolving this problem, in that they treat the object and literal types as being of the same class.
Note that there is no isValidIdentifier method because ECMAScript method-names can be constructed dynamically by using strings, making it possible to create object properties that would be invalid were they stated using the 'dot' syntax. That is:
var MyObj = { }; MyObj[" "] = function () { };
...is entirely legal (yes, you really can have a method name that is just a space-character, although such a method is callable only through use of the square-bracket syntax). This would render any isValidIdentifier-ancillary redundant because it would have to allow essentially anything, and so AJS_Validator does not support such a method. This is also the reason that the AJS_DefMgr objects that the AspectJS client-components use do not validate the method names carried in the MethodNames properties of definition objects.
Ancillary Name | Invokes the exception handler if... |
isDefined | ...the first argument is undefined. |
isNull_Not | ...the first argment is null or undefined. |
isOfClass | ...the first argument is not of the class described by the second argument, or is null or undefined. |
isOfClass_Not | ...the first argument is of the class described by the second argument, ior is null or undefined. |
isBoolean | ...the first argument is not a boolean or boolean-literal, or is null or undefined. |
isBoolean_Not | ...the first argument is a boolean or boolean-literal, or is null or undefined. |
isNumber | ...the first argument is not a number or number-literal, or is null or undefined. |
isNumber_Not | ...the first argument is a number or number-literal, or is null or undefined. |
isString | ...the first argument is not a string or string-literal, or is null or undefined. |
isString_Not | ...the first argument is a string or string-literal, or is null or undefined. |
isInteger | ...first argument has a fractional component, is not a number, or is null or undefined. |
isRegExMatch | ...first argument does not match the regular expression represented by the second argument, is not a string/string-literal, or is null or undefined. |
isIn | ...the first argument does not describe a property of the object referred to by the second argument. |
isIn_Not | ...the first argument does describe a property of the object referred to by the second argument. |
isIn_Array | ...the value described or referred to by the first argument is a member of the array referred to by the second argument. |
isIn_Array_Not | ...the value described or referred to by the first argument is not a member of the array referred to by the second argument. |
isConformant | ...the first argument is not of class Object, is null or undefined, or if the number of properties in the object referred to by the first argument is fewer than the number indicated by the optional fifth argument, or greater than the number indicated by the optional sixth argument. The user-defined test-function passed as its second argument should call the exception handler it is passed if a given property of the object referred to by the first argument does not conform to an arbitrary condition. |
isConformant_Array | ...the first argument is not of class Array, is null or undefined, or if the number of members in the array referred to by the first argument is fewer than the number indicated by the optional fifth argument, or greater than the number indicated by the optional sixth argument. The user-defined test-function passed as its second argument should call the exception handler it is passed if a given member of the array referred to by the first argument does not conform to an arbitrary condition. |
As intimated above, the simplest ancillary method is getClass. which accepts a single argument of any value, and returns a string describing the class of the argument provided. Note that if the argument is a boolean-, number- or string-object then it will return 'Boolean', 'Number' and 'String' respectively, and if it is a boolean-, number- or string-literal, it will return 'BooleanLiteral', 'NumberLiteral' and 'StringLiteral'. Example 59 demonstrates these points.
The next most-complex method, isDefined, accepts three arguments, which are:
Beyond isDefined, the next eight most-complex ancillaries, isNull_Not, isInteger, isBoolean, isBoolean_Not, isNumber, isNumber_Not, isString and isString_Not all take the same-three leading arguments as isDefined, along with a fourth argument 'NeverUndefined' (as noted above), which is optional (i.e. can be undefined), and for which the default is true. Passing the value false for this argument causes the method in question to return silently should the object under test be undefined.
Note that AJS_Validator validates the three trailing arguments (using internal ValidationDefs – see below) and will call the exception handler if they fall outside normal bounds. Examples 60 to 62 show three of these methods in operation.
Of the nine methods that remain: isRegExMatch takes four arguments: a string/string-literal, a regular expression against which the first argument is tested, the same Message argument as considered above, a NeverUndefined argument that performs the same role as in the other ancillaries that take the same argument, and a StackOffset argument.
Beyond that, the isOfClass... and isIn... methods, do not accept a NeverUndefined argument, but they accept a mandatory argument in the second place nevertheless. In the case of isOfClass and isOfClass_Not, this is the class-name in question and, in the four isIn... methods, it must be a reference to the object within which the member/property represented by the first argument may or may not reside.
Note here that the isIn... ancillaries that have an '_Array' suffix to their names are equivalent to the non-suffixed versions, and will ascertain membership or otherwise of an array rather than an object of class Object.
Note also that instruction on the final-two ancillaries, isConformant and isConformant_Array, is a little more involved, and so is given separately in the final sections below.
// -- Example 59 ------------------------------------------------------------- console.log (AJS_Validator.getClass (true )); console.log (AJS_Validator.getClass (new Boolean (false ))); console.log (AJS_Validator.getClass (123456 )); console.log (AJS_Validator.getClass (new Number (654321))); console.log (AJS_Validator.getClass ("Aspect-Oriented Programmers do it Transparently" )); console.log (AJS_Validator.getClass (new String ("Real Programmers do it with Passion"))); -- Output -------------------------------------------------------------------- BooleanLiteral Boolean NumberLiteral Number StringLiteral String
// -- Example 60 ------------------------------------------------------------- AJS_Validator.isOfClass ("Some test-string", "Number", "MyArgument"); -- Output -------------------------------------------------------------------- AJS_Validator_Exception: MyArgument is of class StringLiteral not Number.
// -- Example 61 ------------------------------------------------------------- var MyObject = { method_B : function () { } }; AJS_Validator.isIn ("method_A", MyObject, "MyObject does not possess a property by the name of method_A"); AJS_Validator.isIn ("method_C", MyObject, "MyObject", false); // Will not throw because the // NeverUndefined argument is false. -- Output -------------------------------------------------------------------- AJS_Validator_Exception: MyObject does not possess a property by the name of method_A.
// -- Example 62 ------------------------------------------------------------- var SqrRoot_2 = 1.41421356237; AJS_Validator.isInteger (SqrRoot_2, "Putatively-integral argument"); -- Output -------------------------------------------------------------------- AJS_Validator_Exception: Putatively-integral argument is a non-integer number. Value given: 1.41421356237.
As pointed out above, on failure of the principal test that a given is... ancillary performs, the method in question will call the exception handler that your code passed to createAJS_Validator initially.
If the handler is one of those that form part of the AspectJS distribution set, the exception object it generates will be as shown in the AJS_Validator Exceptions section, where the message property will hold the message fragment you passed to the ancillary, to which the ancillary will have concatenated as a suffix a message-stub that describes the essential nature of the problem.
For example, if you pass a non-integer number, say 2.718, to isInteger, and you pass also a message fragment that reads:
'My strange value'
...then isInteger will generate the following notification:
'My strange value is a non-integer number. Value given: 2.718.'
Most of the ancillaries generate only one message stub, but the isConformant... methods generate different stubs depending on the nature of the failure (see below), and the isIn... methods generate simply a full-stop (a 'period' if you hail from the western side of the Atlantic). In other words, the full message that they generate is simply the message that you provide, terminated with the punctuation character that lies at the end of this sentence.
The reason for this is that there are two ways in which an isIn test can report the absence of, say, A within B. You could say:
A does not possess B
...Alternatively, you could say:
B is not a property of A
Experience shows this to be a non-trivial issue, and so the isIn methods leave the wording of the nub of a given isIn exception-notification to you.
These points in hand, the full set of message stubs that the ancillaries generate is given in the table. Note that, with the exception of the full-stop-only stubs, they all start with a space character, meaning that you do not need to append a space to the message fragments that you supply.
AJS_Validator validates the Message and StackOffset arguments by means of internal ValidationDefs, in order to simplify its implementation (which, as pointed out elsewhere in this site, bears witness to the internal-consistency of AJS_Validator – if it were flawed, the test-regimen for the ancillaries would fail at numerous points). Moreover, with some of the ancillaries, it also validates in the same way some of the arguments that relate to the principal test that they perform.
For example, the isIn method uses the 'in' operator to determine if a given property resides within a given object. In this case, you must state the property-name as a string, where failure to do so constitutes a failure on the part of your validation code (the code that calls the ancillary in question), not a failure on the part of application code (i.e. the code that you are using AJS_Validator to validate or debug).
Given this, passing, say, a number as the property-name in a call to isIn will generate an exception notification that reads just like any other that AJS_Validator generates, and which will not include the Message argument that you provided. However (to continue the example), if you call isIn correctly, but where the property in question does not reside within the object in question, then the exception notification that results will include the value of your Message argument (because this is the point at which you need to tell yourself which element of the application has gone awry).
Example 63 demonstrates these points.
Note that, as pointed out in the overview above, calling AJS_Validator.suspend disables the effect of all applied ValidationDefs, but it does not disable the internal ValidationDefs that that object uses to police some of the arguments that the ancillaries accept. AJS_Validator maintains unrelenting vigilance in this respect whether you want it or not (which is as things should be), because the internal ValidationDefs are not placed in the set of user-defined ValidationDefs that have been applied to application methods (which is the set that is affected by calls to AJS_Validator.suspend and AJS_Validator.resume).
Ancillary Name | Message Stub | |
isDefined | " is undefined." | |
isNull_Not | " is null." | |
isOfClass | " is of class <Class of Object> not <Class-Name Passed by Caller>." | |
isOfClass_Not | " is of class <Class of Object>." | |
isBoolean | " is of class <Class of Object> not BooleanLiteral or Boolean." | |
isBoolean_Not | " is of class BooleanLiteral or Boolean." | |
isNumber | " is of class <Class of Object> not NumberLiteral or Number." | |
isNumber_Not | " is of class Number or NumberLiteral." | |
isString | " is of class <Class of Object> not StringLiteral or String." | |
isString_Not | " is of class String or StringLiteral." | |
isInteger | " is a non-integer number. Value given: <Value of Number Passed by Caller>." | |
isRegExMatch | " does not match the following RegEx: <RegEx Passed by Caller>. Value tested: <Value of String Passed by Caller>." | |
isIn | "." | |
isIn_Not | "." | |
isIn_Array | "." | |
isIn_Array_Not | "." | |
isConformant | Either: Or: Or: | " has <Number of Object-Properties> properties, which is fewer than the minimum of <MinSize Passed by Caller>." " has <Number of Object-Properties> properties, which is greater than the maximum of <MaxSize Passed by Caller>." "." |
isConformant_Array | Either: Or: Or: | " has a length of <Length of Array>, which is smaller than the minimum of <MinLength Passed by Caller>." " has a length of <Length of Array>, which is greater than the maximum of <MaxLength Passed by Caller>." "." |
// -- Example 63 ------------------------------------------------------------- try { AJS_Validator.isInteger (42.42, "my lovely number", -42); } catch (E) { console.log (E.name + ": " + E.message); } try { AJS_Validator.isInteger (42.42, "my lovely number", 1); } catch (E) { console.log (E.name + ": " + E.message); } -- Output -------------------------------------------------------------------- AJS_Validator_Exception: argument 2 (StackOffset) in call to AJS_Validator.isInteger is a number with the value of -42, which the corresponding NeverNegative property within the corresponding ValidationDef disallows (Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: AJS_Validator). AJS_Validator_Exception: my lovely number is a non-integer number. Value given: 42.42.
Food for Thought |
Tangentially: the notion of the AJS_Validator object employing internal ValidationDefs to validate a proportion of its own ancillary-method's arguments parallels very closely the nature of, for example, mitochondria and chloroplasts in eukaryotic cells (the kind of cells that make up the bodies of countless species including ourselves). Such organelles were once free-living bacteria that were ingested benignly by other cells, which then used those organisms internally for their own ends, such as photosynthesis and the generation of ATP. This comparison may be of little value for the developer who desires only that a resource like AJS_Validator 'just works' (an entirely reasonable position), but it does hold considerable allure for the philosphical programmer on a quest for deeper meaning. Indeed, one can argue strongly that the internal use of ValidationDef-objects within a tool that is predicated on the application of such objects to other elements of a software system qualifies as a design pattern in its own right. By what name would such a pattern go? 'Auto-Manifestation' or 'Auto-Utilisation' come to mind, but 'Endosymbiosis' is an excellent candidate too, as biology classifies mitochondria and chloroplasts as endosymbionts. Then again, some would argue that this is no more than a particular, albeit intriguing, flavour of recursion. |
As with the calling-signature of the exception handler that is passed to Pre- and Post-Validators, the optional StackOffset argument that the ancillaries accept enables you to ensure that any exception notifications you receive will always indicate the point at which the application stepped out of line, irrespective of the point from which the ancillary in question was called (assuming that you are using throwException or throwException_Remote).
The default value for this argument is zero, meaning that, as with the exception handler that is passed to Pre-/Post-Validators, failure to pass a value for this argument (or passing the value of zero explicitly) in a call to a given ancillary will cause any ensuing exception notification to report the location of that call as the source of the trouble, which is only the point at which the problem was detected. This is not what we want.
Given this, and when calling an ancillary from within a Pre-\Post-Validator, you need to pass the StackOffset argument that AJS_Validator passed to that validator, making sure that you increment it first.
Example 64 is entirely contrived in order to clarify the principles at hand (the classes of the arguments passed to method_A could be enforced using ArgDefs alone), and shows the use of the StackOffset argument.
That is: the postValidator increments the StackOffset, and then calls externalValidator, passing StackOffset. The externalValidator then increments StackOffset itself before calling isNumber. If that validator calls another external validator, it too should pass StackOffset, which the recipient should increment also before doing anything else, and so on (in just the same way as explained in the Pre-/Post-Validators and Exceptions section).
01 02 // -- Example 64 ------------------------------------------------------------- 03 04 var Obj = 05 { 06 method_A : function (StrArg, NumArg) { } 07 }; 08 09 AJS_Validator.applyValidationDef (Obj, 10 { 11 postValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset) 12 { 13 StackOffset++; // Always increment StackOffset 14 // before you do anything else. 15 AJS_Validator.isString (Args[0], "StrArg in call to " + MethodName, StackOffset); 16 externalValidator (Args, MethodName, StackOffset); 17 18 } 19 20 }); 21 22 function externalValidator (Args, MethodName, StackOffset) 23 { 24 StackOffset++; // Again, remember to increment 25 // the StackOffset before doing 26 AJS_Validator.isNumber (Args[1], "NumArg in call to " + MethodName, StackOffset); // anything else. 27 28 // If the code call another validator function from here, 29 // it should pass StackOffset, and that validator should 30 // increment StackOffset by one, and so on. 31 32 } 33 34 Obj.method_A ("The answer to life, the universe and everything is", "42"); -- Output ----------------------------------------------------------------------- AJS_Validator_Exception: NumArg in call to method_A is of class StringLiteral not NumberLiteral or Number. Invalid call occurred during execution of Example_64 at line 34, column 1, in Example_64.js
In order to cut Pre- and Post-Validator workload to a minimum, calls to internal methods are 'chained', where the return-value from one forms the input argument for another. This means that, when testing for a particular property (say, being a non-integer number), you do not have to confirm first that the object in question is defined, and then that it is non-null, and then to confirm that it is of class Number, to test only at the very end for a non-integer value.
That is to say, you do not need to write code that performs a tedious train of underlying-primitive tests (as Example 65 demonstrates) before it gets stuck-in to the really juicy stuff.
Instead, the ancillary function isInteger (to continue the example) will perform all the underlying tests for you first, before it tests for a non-whole number. At all stages the Message argument that you provide is passed-on to the 'deeper' primitive in the chain (along with a correctly-adjusted stack offset), and these points are demonstrated in Example 66. Here isInteger is called successively, and is passed, for each call, the values undefined, null, a string and the square-root of 2 respectively.
// -- Example 65 ------------------------------------------------------------- var MyPutativeInteger = 3.14159; AJS_Validator.isDefined (MyPutativeInteger, ... ); // This... AJS_Validator.isNull_Not (MyPutativeInteger, ... ); // ...this... AJS_Validator.isOfClass (MyPutativeInteger, "Number", ... ); // ...and this, are all unnecessary... AJS_Validator.isInteger (MyPutativeInteger, ... ); // ...Because this call performs all those tests for you.
// -- Example 66 ------------------------------------------------------------- try { AJS_Validator.isInteger (undefined, "Arg"); } catch (E) { console.log (E.message); } try { AJS_Validator.isInteger (null, "Arg"); } catch (E) { console.log (E.message); } try { AJS_Validator.isInteger ("", "Arg"); } catch (E) { console.log (E.message); } try { AJS_Validator.isInteger (1.41421356237, "Arg"); } catch (E) { console.log (E.message); } -- Output -------------------------------------------------------------------- ...Arg is undefined... ...Arg is null... ...Arg is of class StringLiteral not NumberLiteral or Number... ...Arg is a non-integer number. Value given: 1.41421356237...
The complete succession of such chained-tests performed by AJS_Validator's ancillary methods is illustrated in the diagram; and do note that, while the internal implementation of isOfClass affirms first that the object is question is defined and non-null, the public-ancillary implementation does not. This means that you can ascertain that a value is of class Undefined or Null in the same way that you would test for other class-names.
In line with the internal call-chaining that AJS_Validator implements, most of the the 'is...' ancillaries complement their Guilty-until-Proven-Innocent predicate by returning the object under consideration when all is well (that is: despite the 'is' prefix, they do not return a Boolean). The ones that differ are isIn and isIn_Array, which return the sought-after property/member; where isIn_Not and isInArray_Not return the object/array in which the property/member to which the first argument refers should not reside.
This return-policy allows you to chain calls to the ancillary methods together, where the output of one forms the input for another (in the same way that AJS_Validator chains the calls to its internal primitives), thus harmonising the code-scape still further, and Examples 67, 68 and 69 demonstrate this point.
// -- Example 67 ------------------------------------------------------------- var Characters = { Father : { Name : "Homer", Gender : "M" }, Mother : { Name : "Marge", Gender : "F" }, FirstChild : { Name : "Bart", Gender : "M" }, SecondChild : { Name : "Lisa", Gender : "F" } // No ThirdChild. }; AJS_Validator.isIn ("Gender", AJS_Validator.isOfClass ( AJS_Validator.isIn ("ThirdChild", Characters, "Characters does not possess a 'ThirdChild' property"), "Object", "ThirdChild of Characters"), "ThirdChild of Characters does not possess a 'Gender' property"); -- Output -------------------------------------------------------------------- AJS_Validator_Exception: Characters does not possess a 'ThirdChild' property.
In each example, the code creates an object called CartoonCharacters that has a number of properties (Father, Mother etc.), where each property is, in turn, an object that carries two properties called Name and Gender.
However, in each such object, something is missing or defective, such as no ThirdChild property in Example 67; a ThirdChild that is not class-of Object in Example 68; and a ThirdChild that possesses no Gender property in Example 69. Duly therefore, each example performs a 'composite' test, and detects each deficiency accordingly.
They do this by calling isIn initially on the CartoonCharacters object (to test for presence of a ThirdChild property), and then piping the output from that into isOfClass (to confirm that the ThirdChild is an instance of the Object class). When that method returns, its output is piped finally into isIn again, to confirm that the ThirdChild property has a property called Age.
At each point, the ancillary in question may call the exception handler if something is wrong (including the presence of null and undefined values, as pointed out above); and do note the passing of the verbose 'ThirdChild of Characters' Message-argument to the 'outer' isIn call, and to the call to isOfClass. As the output shows, this results in thoroughly meaningful, natural-language exception notifications.
// -- Example 68 ------------------------------------------------------------- var Characters = { Father : { Name : "Homer", Gender : "M" }, Mother : { Name : "Marge", Gender : "F" }, FirstChild : { Name : "Bart", Gender : "M" }, SecondChild : { Name : "Lisa", Gender : "F" }, ThirdChild : 42 // ThirdChild is not of class Object. }; AJS_Validator.isIn ("Gender", AJS_Validator.isOfClass ( AJS_Validator.isIn ("ThirdChild", Characters, "Characters does not possess a 'ThirdChild' property"), "Object", "ThirdChild of Characters"), "ThirdChild of Characters does not possess a 'Gender' property"); -- Output -------------------------------------------------------------------- AJS_Validator_Exception: ThirdChild of Characters is of class NumberLiteral not Object.
// -- Example 69 ------------------------------------------------------------- var Characters = { Father : { Name : "Homer", Gender : "M" }, Mother : { Name : "Marge", Gender : "F" }, FirstChild : { Name : "Bart", Gender : "M" }, SecondChild : { Name : "Lisa", Gender : "F" }, ThirdChild : { Name : "Maggie" } // ThirdChild does not have a Gender property. }; AJS_Validator.isIn ("Gender", AJS_Validator.isOfClass ( AJS_Validator.isIn ("ThirdChild", Characters, "Characters does not possess a 'ThirdChild' property"), "Object", "ThirdChild of Characters"), "ThirdChild of Characters does not possess a 'Gender' property"); -- Output -------------------------------------------------------------------- AJS_Validator_Exception: ThirdChild of Characters does not possess a 'Gender' property.
The final methods in the AJS_Validator ancillaries, isConformant and isConformant_Array, iterate across the properties/members of an object/array that you provide as the first argument, calling a user-defined function (i.e. a function that you provide, a template for which you can see in Example 70) for each property/member in that object/array. This means that isConformant_Arrray is, at its heart, equivalent to the 'every' method of the Array prototype, but not equal as 'every' does not accept a StackOffset argument, and isConformant_Array does not accept a 'thisArg' argument.
isConformant and isConformant_Array allow you to verify that the contents of an object or array respectively satisfy some arbitrary criterion. For example, you may wish to ensure that all property-values of an object are strings, or that an array carries only prime numbers, in which case these are the methods to use.
Essentially, these methods use a standard for loop to perform the iteration, where isConformant operates on an array-version of the object passed (which it generates by means of the Object.keys method, or a shim when the run-time does not support that method). This means that isConformant will not necessarily iterate across the properties of the object in question in the order in which they are defined within that object (but also that it will not iterate over properties in any prototypes).
Note here that it is entirely possible to use the isConformant... methods as simple iterators, wherein your 'test-function' does not actually perform a test, but does something else that is useful in the context of each object it is passed. Given that the isConformant... names are validation-oriented, this does constitute a minor subversion semantically. However, it does work in practice, but if you follow this path, do avoid using the isConformant... methods to detect the presence/absence of an object within an array or class-like object. This is because the isIn and isIn_Array methods will do that for you more quickly, and this is because they use a fast 'count-back' while-loop rather than the slower for-loop that the isConformant... methods employ.
isConformant and isConformant_Array accept two mandatory leading-arguments that represent respectively a reference to the object/array in question, and a reference to a user-defined function that should perform a test on the properties/members of the object/array that it receives. They also accept a mandatory Message argument, and a fourth, optional StackOffset-argument. These serve the same purpose as their equivalents in the other ancillaries.
Do note that the Message argument is used in exception notifications pertaining to the object under test, not to the property or member that is the cause of the non-conformancy. That is: if you call isConformant_Array (for example) and the object you pass is not an array, then AJS_Validator will incorporate the Message argument into the exception message that informs you of that object's non-arrayness. However, assuming that it is an array, but that one of its members fails the test that your test-function applies, it is the test function that should alert you to the problem with the array-member in question.
The isConformant methods also accept two optional trailing Min-Size/-Length and Max-Size/-Length arguments, and will invoke the exception handler if the object/array in question has too few/many properties/members respectively (again, incorporating the value of your Message argument into the exception notification).
The isConformant... arguments aside, every test-function that you pass should accept at the least a leading Value-argument that represents the value of a given property/member of the object/array under consideration. The function may also accept a second argument, which is the identifier needed to access the value within the corresponding property/member in the object/array under consideration.
That is: when the test function is passed to isConformant, each value for the second argument will be the names of the property within the object, and when passed to isConformant_Array each value for the test function's second argument will be a digit representing the position of the value in the array in question. This means that, in the former case, it is advisable to call that argument 'Key', and to call it Position in the latter.
Your test function should return normally if the property/member passed does conform to the condition in question (no return-value is required). However, if the property/member does not pass the test, your function should throw an exception. It can do this natively by using the throw keyword, but, as pointed out in the Pre-/Post-Validators and Exceptions section, doing this will require that you implement propretary logic to extract file and line-number information from the exception object that results.
For this reason, the isConformant... methods pass a onException and StackOffset argument to test-functions in the third and fourth place, and these play the same role as their namesakes in Pre- and Post-Validators, and in the ancillaries considered above. Here the rule that applies to the StackOffset argument is the same as stated in the section on Pre-/Post-Validators and Exceptions, which is to say that you should always increment StackOffset by one at the start of any function to which it is passed.
These points in hand, Examples 71 and 72 show isConformant and isConformant_Array pirouetting gracefully across the stage.
In Example 71, the code creates an array of cartoon-character objects, each of which have a Name and a TitleOfShow property. It then applies a ValidationDef to method_A of Obj. That ValidationDef has a preValidator that, when executed, increments the StackOffset argument, and then calls isConformant_Array, passing TestCharacterObj as the test-function reference.
Each time isConformant_Array calls TestCharacterObj, that function verifies that the Character object in question is of class Object, before confirming that it carries a Name and TitleOfShow property. It then checks the value of TitleOfShow, which should be 'Flintstones'; but the third property of the Characters object has a TitleOfShow-value of 'Simpsons', and thus the test function call the exception handler upon encountering this.
Example 72 really goes to town, and creates a 'song' object and a preValidator within a ValidationDef, as in Example 71. The Prevalidator calls isConformant, passing a test-function that performs a basic isString test on all the properties of SongObject, except for the SampleLyrics property.
This gets special treament: the test-function invokes isConformant_Array itself, passing a test-function of its own that validates each array in the SampleLyrics array against a regular expression that matches exclusively-alphabetic strings. However, the final member of the SampleLyrics array has an egregious defect, in that a particular, ubiquitous integer has found its way into the words. Duly, that test-function invokes the exception handler upon encountering the problem, thus ensuring that this flawed-monster never escapes the laboratory.
// -- Example 70 ------------------------------------------------------------- // Note that the name of the second argument here (Key) makes this function // more suitable for passing to isConformant. If you were to pass it to // isConformant_Array, it would be better to rename that argument to // 'Position'. See Example 71 for clarification of this. function myTestFunction (Value, Key, onException, StackOffset) { StackOffset++; // Always increment StackOffset before doing anything else. // Test property here and, if it does not satisfy the criteria // applied, call onException, remembering to pass StackOffset. }
// -- Example 71 ------------------------------------------------------------- var Obj = { method_A : function (ArrayArg) { } }; // -------------------------------------------- var Characters = [ { Name : "Fred", TitleOfShow : "Flintstones" }, { Name : "Barney", TitleOfShow : "Flintstones" }, { Name : "Marge", TitleOfShow : "Simpsons" }, { Name : "Wilma", TitleOfShow : "Flintstones" } ]; // -------------------------------------------- AJS_Validator.applyValidationDef (Obj, { preValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset) { StackOffset++; // Always increment the StackOffset first. AJS_Validator.isConformant_Array (Args[0], TestCharacterObj, "Characters", StackOffset); } }); // -------------------------------------------- // Obviously, this could be defined inside // the preValidator, but defining it function TestCharacterObj (Character, Position, onException, StackOffset) // externally allows it to be used by other { // code. StackOffset++; // Again, always increment StackOffset first. AJS_Validator.isOfClass ( Character, "Object", "Character " + Position + " in Characters", StackOffset); AJS_Validator.isIn ("Name", Character, "Character " + Position + " in Characters", StackOffset); AJS_Validator.isIn ("TitleOfShow", Character, "Character " + Position + " in Characters", StackOffset); if (Character.TitleOfShow !== "Flintstones") { onException ("Character " + Character.Name + " at position " + Position + " in Characters is from the " + Character.TitleOfShow + " not the FlintStones", StackOffset); } } Obj.method_A (Characters); -- Output -------------------------------------------------------------------- User_Defined_Validation_Exception: Character Marge at position 2 in Characters is from the Simpsons not the FlintStones.
// -- Example 72 ------------------------------------------------------------- var Obj = { method_A : function (SongObject) { } }; // -------------------------------------------- var SongObject = { Title : "Working at Perfekt", Album : "My Favorite Headache", Artist : "Geddy Lee", RunningTime : "4.59", Notes : [ "Reflects accurately the huge challenge of developing meaningful software", "Catch the cello" ] SampleLyrics : [ "And when it's right, it's right as rain", "And when it's right, there is no pain", "And when it's right, you start again, yeah...", "Working at perfect, got you down on your knees", "Success to failure, just a matter of 42 degrees" // 42 should NOT appear in the lyric. ] }; // -------------------------------------------- AJS_Validator.applyValidationDef (Obj, { preValidator : function (Args, MethodOwner, MethodName, MethodOwnerName, onException, StackOffset) { StackOffset++; // Remember to increment the StackOffset. AJS_Validator.isConformant (Args[0], function (PropertyValue, PropertyName, onException, StackOffset) { StackOffset++; // Again, increment the StackOffset // before doing anything else. switch (PropertyName) { case "Title" : case "Album" : case "Artist" : case "RunningTime" : case "Notes" : AJS_Validator.isString (PropertyValue, PropertyName + " property of song object", StackOffset); break; // -------------------------------------------- case "SampleLyrics" : AJS_Validator.isConformant_Array (Args[0].SampleLyrics, function (Member, Position, onException, StackOffset) { StackOffset++; // Tedious as it may seem, always // increment the StackOffset first. AJS_Validator.isRegExMatch (Member, /^[\D\s]*$/, "lyric-member at position " + Position + " in SampleLyrics property of song object", StackOffset); }, "SampleLyrics", StackOffset); break; // -------------------------------------------- default : onException ("unrecognised property in song object", StackOffset); } }, "song object", StackOffset); } }); Obj.method_A (SongObject); -- Output -------------------------------------------------------------------- AJS_Validator_Exception: lyric-member at position 4 in SampleLyrics property of song object does not match the following RegEx: /^[\D\s]*$/. Value tested: 'Success to failure, just a matter of 42 degrees'.
Note that, when working with an array of strings, string-literals, numbers, number-literals, function references or regular expressions, there is a very useful technique that you can use in conjunction with isConformant_Array (or, indeed, when iterating over an array natively) to ascertain whether or not each member of the array is unique, but without incurring excessive performance penalties when working with large sets. Indeed, AJS_Validator uses the technique internally in the interests of performance.
The naive approach to checking array members for uniqueness is to iterate across the array, and, for a given member, to iterate across the remainder of the array, testing for equality between that member and the other members. This, however, is verbose and somewhat ungainly, as it requires a nested loop; moreover, performance degrades disproportionately as the number (N) of members increases.
In fact, when using this technique, the total number of comparisons (C) performed for a given N can be expressed as follows:
C = (N * (N - 1)) / 2
...Which means that the number of comparisons for a given N expands as follows:
N : 1 2 3 4 5 6 7 8 9 10 50 100 1000 C : 0 1 3 6 10 15 21 28 36 45 1225 4950 499500
Clearly, things start getting out of hand beyond non-trivial member counts. However, we can do away with the nested loop, thus rendering the comparison expansion-rate linear, which improves performance (as tests confirm), and Example 73 demonstrates the technique.
Here, the code instantiates an array of strings, and defines a function that instantiates an empty Checklist object before returning a reference to an inner comparison-function. The code then calls AJS_Validator.isConformant_Array, passing as the test function the reference generated by calling get_isUniqueString (thus forming a closure, causing the Checklist object to persist as isConformant_Array executes).
On each invocation, the comparison function receives the string residing at a given slot in the array, along with its position in the array (as explained above). Ignoring for the moment the comparison logic (the 'if' statement), the function makes the string a property of the Checklist object, assigning the position value to that new property.
This means that, after five calls to the comparison function, the CheckList object looks like this:
{ "They can bill me!" : 0, "Are you a programmer?" : 1, "Shut your pie-hole." : 2, "Unclouded by conscience, remorse or delusions of morality." : 3, "What do you mean, like, Europe?" : 4 }
For each execution of the comparison function, the comparison logic uses the 'in' keyword to determine if the string array-member it has received resides in the Checklist object (this is the part that takes the place of a nested loop, and is the source of the performance improvement).
If it does reside in the Checklist object then the array must have at least two members that are of identical value, and this is is what happens in the example. On its sixth execution, the comparison function finds that the string 'Are you a programmer?' does exist already in the CheckList object, and so it calls the exception handler.
This approach means that the number of iterations scales only linearly with the number of array-members, rather than expanding exponentially, and this prevents performance from dropping-off at an untenable rate. In other words, with this approach:
C = N
...Which is eminently satisfying.
// -- Example 73 ------------------------------------------------------------- var MovieQuotes = [ "They can bill me!", "Are you a programmer?", "Shut your pie-hole.", "Unclouded by conscience, remorse or delusions of morality.", "What do you mean, like, Europe?", "Are you a programmer?" ]; // -------------------------------------------- function get_isUniqueString () { var Checklist = { }; return function (Member, Pos, onException, StackOffset) { if (Member in Checklist) { // Increment StackOffset StackOffset++; // before passing it. onException ("The value at position " + Pos + " (" + Member + ") " + "is identical to that at position " + Checklist [Member], StackOffset); } Checklist[Member] = Pos; }; } AJS_Validator.isConformant_Array (MovieQuotes, get_isUniqueString (), "MovieQuotes"); -- Output -------------------------------------------------------------------- User_Defined_Validation_Exception: The value at position 5 (Are you a programmer?) is identical to that at position 1.
Note that, as mentioned at the start of this section, this technique allows you to test for uniqueness within an array of numbers, number-literals, function references and regular expressions, as well as string/string-literals. This is because, while we usually think of object property-values in JavaScript as being mapped to key-values that we express as string-literals, it is entirely possible to use those other types as object-property keys.
That is to say that the code in Example 74 is entirely legal (if somewhat unconventional), and thus the technique explored above will work with array member-types other than simple strings, because you can populate the Checklist object with key-values that are, say, function references.
// -- Example 74 ------------------------------------------------------------- var MyObj = { }; MyObj[/^B[AEIU]T$/] = 42; MyObj[/^C[AOU]T$/] = 43; // Map the value 43 to the /^C[AOU]T$/ RegEx. MyObj[/^D[AIU]D$/] = 44; function f () { } function g () { } function h () { } var YourObj = { }; YourObj[f] = 42; YourObj[g] = 43; // Map the value 43 to the reference to function g. YourObj[h] = 44; console.log (MyObj [/^C[AOU]T$/]); // Absolutely fine. console.log (YourObj[g]); // Likewise here. -- Output -------------------------------------------------------------------- 43 43