The ArgDef and RtnDef properties explored so far, such as MaxLen and MinLen, apply only if the argument or return-value in question is of the relevant type. If not, those properties are ignored. But AJS_Validator also allows you to constrain the class of a given argument or return value, such that it is only ever, say, an array, and this is the purpose of the AllowClasses property (to which the DisallowClasses property forms a complement).
This feature pivots on detecting the class of an object reliably, which in JavaScript is a troubled affair; the typeof operator is near useless, and the instanceof operator is little better because it yields the constructor-reference for the object in question. Technicalities may prevent this from working in a browser context, where two objects that are created by syntactically-identical constructor-calls originate from different frames.
Contents |
Specifying Classes The AllowClasses Property Host Class-Names The DisallowClasses Property |
This is compounded by the distinction that JavaScript draws between boolean-, number- and string-objects (i.e. where the objects in question are created using a constructor call), and boolean-, number- and string-literals (where the object in question is created by assigning a boolean, numeric or string value to a symbol directly), as Example 31 illustrates.
Intuition suggests that the object and literal versions of such entities are of the same type (i.e. that they implement the same behaviour), but this is not true, a key distinction being that it is impossible to add user-defined properties to the literal forms, whereas the object forms do allow this. This means that the literals cannot be 'tagged' (see the appropriate sections for more information on tagging), whereas the object forms can. Moreover, the object forms can be frozen etc., yet this is impossible with the literals.
// -- Example 31 ------------------------------------------------------------- var MyBoolean = true; // Boolean-literal. var MyOtherBoolean = new Boolean (false); // Boolean object. var MyNumber = 42; // Number-literal. var MyOtherNumber = new Number ("24"); // Number object. var MyString = "Hello World"; // String-literal. var MyOtherString = new String ("Hello Again"); // String object.
Nevertheless, there is a way through these stygian woods, which pivots on the use of object.prototype.toString, and which returns always the correct class-name for a given object. Given these points, the range of class-names for the native (or 'built-in') types defined by ES5 (other than 'Null' and 'Undefined', which are considered in another section) is shown in the list below.
Happily, AJS_Validator can also distinguish between boolean-/number-/string-literals and boolean-/number-/string-objects (it needs this ability in order to make certain areas of its functionality operate properly), and so, if you wish to specify one of the literals in your use of the AllowClasses and DisallowClasses properties (covered below), you should use one of the following identifiers:
These points in hand, the AllowClasses property can be understood as an optional array-property of an ArgDef or RtnDef that must possess one or more string-members (of the literal or object form) describing the classes of object that are allowed for the argument or return-value in question. It follows that, to allow objects of native type, you should use one or more of the values listed in the Specifying Classes section above, ensuring that you start the class-name with a capital letter.
Given this, Example 32 illustrates the use of the AllowClasses property in an ArgDef. Similarly, Example 33 shows an AllowClasses property in the context of a RtnDef (where, if you wished to allow boolean and number objects, rather than their literal forms, you would state 'Boolean' and 'Number' in the AllowClasses property).
Note that AllowClasses arrays that do not contain one or more string-objects/-literals exclusively will cause AJS_Validator to call its exception handler. Note also that AJS_Validator converts any string-objects to string-literals (prior to the freezing of the Arg-/Rtn-Def to which the AllowClasses property belongs) because stating class names using string objects would cause subsequent validation by AJS_Validator to fail. This is because the following comparison:
("Hello Doppelganger" === new String ("Hello Doppelganger"))
...evaluates to false, and this is because such code compares different objects, even though those objects embody the same value (which is why the use of the String constructor in JavaScript is discouraged).
The necessity of this conversion step means in turn that you must not use the freeze method of the Object constructor on an AllowClasses property prior to submission of its enclosing ValidationDef to AJS_Validator (because this will preclude the conversion from string-object to string-literal).
Remember also that empty AllowClasses arrays are prohibited because such a thing would imply that only those objects that have no class at all were possible. This would be a paradox because every object is of a particular class.
Notably, this encompasses undefined and null, because ES5 defines these as having the class-names of 'Undefined' and 'Null' respectively, and these are absent from the list presented in the Detecting Classes section above because AJS_Validator proscribes these within an AllowClasses property.
The reason for this is clear if we re-approach the NeverUndefined and NeverNull properties, which is to say that you could express the semantics of those two properties by listing 'Undefined' and/or 'Null' in an AllowClasses property. However, were AJS_Validator to operate in this way, it would require you to list all the other classes that should be allowed for a given argument or return-value. Satisfying such a criterion would become exceedingly tedious, especially when all you wanted to say is that, for example, an argument is optional (i.e. that it can be undefined).
Given this, providing false values for the NeverUndefined and NeverNull properties can be viewed as convenient shorthand for stipulating 'Undefined' and/or 'Null' in an AllowClasses property, and their existence precludes them therefore as class-names because allowing them would allow for redundant and/or contradictory Arg- and Rtn-Defs such as the following:
{ AllowClasses : ["Null"], NeverNull : true }
Finally in this section, note that the class of the value NaN is 'Number', which is an out-and-out contradiction – after all, NaN means literally 'Not a Number' (we have the IEEE Floating-Point Number standard to blame for that particular 'sound of one hand clapping'). Despite this, AJS_Validator invokes its exception handler when an AllowClasses property permits numbers, and where a corresponding argument or return-value is NaN. This is because the NaN value usually arises because something has gone wrong within the application in question, even though the class of the value concerned satisfies the AllowClasses stricture.
// -- Example 32 ------------------------------------------------------------- var MyObj = { method_A : function (BooleanOrNumericArg) { } }; AJS_Validator.applyValidationDef (MyObj, { MethodNames : ["method_A"], CallDef : [ { AllowClasses : ["BooleanLiteral", "NumberLiteral"] } // The argument can be a boolean- or number-Literal. ] }); MyObj .method_A (true); // OK, method_A can accept boolean-literals. MyObj .method_A (42); // OK, method_A can accept number-literals too. MyObj .method_A ("I am neither a Boolean nor a Number"); // Error, the ArgDef disallows strings. -- Output -------------------------------------------------------------------- AJS_Validator_Exception: argument 0 (BooleanOrNumericArg) in call to method_A is of class StringLiteral (value: 'I am neither a Boolean nor a Number'), which is not included in the corresponding ArgDef's AllowClasses property in the corresponding ValidationDef. Permissible classes are: BooleanLiteral, NumberLiteral. (Object's AJS_Validator_Tag : undefined, Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).
// -- Example 33 ------------------------------------------------------------- var MyObj = { method_A : function () { return "I am not a number, I am a free string-literal"; } }; AJS_Validator.applyValidationDef (MyObj, { MethodNames : ["method_A"], RtnDef : { AllowClasses : ["NumberLiteral"] } // The method must return only number objects. }); MyObj.method_A (); // Oops! The method returns a string-literal. -- Output -------------------------------------------------------------------- AJS_Validator_Exception: the value returned from call to method_A is of class StringLiteral (value: 'I am not a number, I am a free string-literal'), which is not included in the corresponding RtnDef's AllowClasses property in the corresponding ValidationDef. Permissible classes are: NumberLiteral. (Object's AJS_Validator_Tag : undefined, Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).
You can also constrain a given argument or return-value to be of a host class, such as an instance of a DOM object. If, for example, you wish that callers of a given method pass only references to, say, <table> elements, you should use 'HTMLTableElement' in your AllowClasses array within the ArgDef in question.
In Example 34, the method_A method of MyObj is designed to accept either a <div> or a <span>, to which it appends a textNode object, such that to supply anything other than a <div> or <span> constitutes a defect on the part of the caller. Accordingly, the ValidationDef for method_A stipulates that the PageElement argument must be of class HTMLDivElement or HTMLSpanElement only.
Note that AJS_Validator validates the class-names you provide, to ensure that they conform to the rules for JavaScript symbol-names (meaning that a class-name cannot be the empty string, and must begin with an alphabetic character, an underscore or a dollar).
Note also that you can determine the correct class-name to use in an AllowClasses property (or DisallowClasses) by passing a reference to a given page-element to the getClass method, which is one of the ancillary methods that AJS_Validator provides.
// -- Example 34 ------------------------------------------------------------- var MyObj = { appendTextToElement : function (PageElement) { var TextObj = document.createTextNode ("Some Text"); PageElement.appendChild (TextObj); } }; AJS_Validator.applyValidationDef (MyObj, { CallDef : [ { AllowClasses : ["HTMLSpanElement", "HTMLDivElement"] } // Only spans and divs are allowed. ] }); var MyDiv = document.createElement ("div"); var MySpan = document.createElement ("span"); var MyTable = document.createElement ("table"); MyObj.appendTextToElement (MyDiv); // OK, the ArgDef's AllowClasses array allows divs. MyObj.appendTextToElement (MySpan); // OK, the ArgDef's AllowClasses array allows spans. MyObj.appendTextToElement (MyTable); // Not OK, AllowClasses array does not allow tables. -- Output -------------------------------------------------------------------- AJS_Validator_Exception: argument 0 (PageElement) in call to appendTextToElement is of class HTMLTableElement (value: [object HTMLTableElement]), which is not included in the corresponding ArgDef's AllowClasses property in the corresponding ValidationDef. Permissible classes are: HTMLSpanElement,HTMLDivElement. (Object's AJS_Validator_Tag : undefined, Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).
To complement the AllowClasses property, the DisallowClasses property of Arg- and Rtn-Defs operates exactly as you would expect: it allows you to preclude certain classes of object as argument- and return-values, by giving the relevant class-names as an array of strings.
Without this Arg-/Rtn-Def property, you could disallow a given object-class only by listing in the AllowClasses property every other class that was permitted. At the very least this would be exceptionally tedious, and could lead to bloated and unwieldy ValidationDef-code.
Moreover, given that the host classes that are available to an application may differ between execution environments (remember that AJS_Validator is not limited to running solely within browsers), this could also render your code non-portable because of your inability to anticipate all the host classes that other platforms support.
The DisallowClasses property side-steps these issues neatly by allowing you to state that everything except a given class of object is permitted as an argument- or return-value. It is identical to AllowedClasses, with one exception, which is that is must be an array of zero or more string-object/-literals (i.e. it can be empty), and that it must not be frozen.
Note that an empty DisallowClasses property means that no object of any class is disallowed, which implies that all objects are allowed. It follows that such an ArgDef property is redundant, but AJS_Validator permits them in order to allow you to simplify any algorithm that you might implement to generate ValidationDefs dynamically (which is the same reason that empty ValidationDefs are allowed).
Example 35 demonstrates the DisallowClasses property strutting its funky stuff.
Note that a DisallowClasses property cannot appear alongside an AllowClasses property. See the Property Concurrence and Collisions section for the reasoning that underlies this stricture.
Note also that AJS_Validator will call its exception handler if a DisallowClasses property disallows numbers where the value of any argument or function-return is NaN (because, as noted above, the class of NaN is 'Number').
// -- Example 35 ------------------------------------------------------------- var MyObj = { appendTextToElement : function (PageElement) { // Contents identical to Example 34. } }; AJS_Validator.applyValidationDef (MyObj, { CallDef : [ { DisallowClasses : ["HTMLImageElement"] } // Images are disallowed. ] }); var MyImage = document.createElement ("img"); MyObj.appendTextToElement (MyImage); // Error - you cannot pass an image element. -- Output -------------------------------------------------------------------- AJS_Validator_Exception: argument 0 (PageElement) in call to appendTextToElement is of class HTMLImageElement (value: [object HTMLImageElement]), which is precluded by the corresponding ArgDef's DisallowClasses property in the corresponding ValidationDef. The classes that are disallowed are: HTMLImageElement. (Object's AJS_Validator_Tag : undefined, Method-Owner's Class: Object, Method-Owner's AJS_Validator_Tag: undefined).