Overview
FindIn is a standalone JavaScript-function that will locate a member
or members of an object, and members of those sub-objects, given a reference to a root object and,
optionally, a set of search criteria. FindIn also accepts a
reference to a call-back function that is invoked whenever an object-member that satisfies a
given search criterion is located.
While FindIn is a general purpose resource, it has been designed
specifically with AspectJS in mind. To clarify this, consider AspectJ (the AOP extension to the Java
programming-language), where a 'join point' is any method call, object instantiation or variable access in
a program. These can be intercepted in the same way that method calls in JavaScript can be intercepted using
AspectJS, and such interceptions are applied using expressions that are coded in AspectJ syntax. Critically,
these expressions can contain wildcards.
JavaScript, however, is evaluated dynamically. This means that, in the absence of proprietary extensions
to the interpreter, AOP must be implemented in a similarly dynamic fashion using JavaScript itself (eg:
the AspectJS library). Given this, and to permit the use of wildcards when applying affixes, a function
such as FindIn is necessary.
Counting Members
The simplest application of FindIn is to count the number of objects within
another object. To do this, call FindIn, passing it a reference to the object
whose members (and sub-members) you wish to search.
This is demonstrated in Example One, where a reference to the object called MyObj
is passed to FindIn. In Internet Explorer the function reports that
the object has four members, although in Firefox the number returned is five. This is because
ChildMethod has a prototype property that
Internet Explorer does not implement. In view of this, note also that there are differences between browsers
over what built-in types are actually enumerable.
// Example 1 - Counting Object-Members
function FindIn (...) { ... }
var MyObj =
{
ChildNumeric : 3.14159,
ChildStr : "Hello",
ChildMethod : function () { },
ChildBuiltIn : new Date ()
};
var Total = FindIn (MyObj);
alert ("Total found: " + Total);
--------------------------------------
Output (on Internet Explorer):
Total found: 4
Output (on Firefox):
Total found: 5
FindIn operates recursively. This means that, unless limited in the depth of its
search (covered below) it will also search any enumerable child-objects (and their children, and so on) of the
root-object specified. Example Two illustrates this, where MyObj has a member object
called ChildObj, which itself has three children.
Note that FindIn's default behaviour can be modified by the use of search criteria
and the call-back feature, and these points are explored in the sections below.
// Example 2 - Counting Object and Sub-Object Members
function FindIn (...) { ... }
var MyObj =
{
ChildNumeric : 3.14159,
ChildStr : "Hello",
ChildMethod : function () { },
ChildBuiltIn : new Date (),
ChildObj :
{
SubChild_A : 10,
SubChild_B : 20,
SubChild_C : 30
}
};
var Total = FindIn (MyObj);
alert ("Total found: " + Total);
--------------------------------------
Output (on Internet Explorer):
Total found: 8
Output (on Firefox):
Total found: 9
Stipulating Search Terms
FindIn can locate object-members by name, and search terms can be specified using
JavaScript regular-expression meta-characters and meta-sequences. Example Three shows six examples of this.
Note that the second, where the dot meta-character means 'anything', is the functional equivalent of the code in
examples One and Two above.
Readers who need more information on the subject of regular-expressions are advised to consult a good book or a decent
online-reference (if you can find one). Do note that the various programming languages sport subtle differences in their
regular-expression implementations, and that JavaScript is no exception here.
// Example 3 - Stipulating Regular-Expression Search Terms
function FindIn (...) { ... }
var MyObj =
{
Fred :
{
Age : 36,
Spouse : "Wilma"
},
Barney :
{
Age : 34,
Spouse : "Betty"
},
Wilma :
{
Age : 32,
Spouse : "Fred"
}
};
// Finds:
alert (FindIn (MyObj, "Barney" )); // All Barneys
alert (FindIn (MyObj, "." )); // Everything
alert (FindIn (MyObj, "Wilma|Age" )); // All Wilmas and Ages
alert (FindIn (MyObj, "e$" )); // All members ending with 'e'
alert (FindIn (MyObj, "^S" )); // All members starting with 'S'
alert (FindIn (MyObj, "^A|a$" )); // All members starting with 'A'
// or ending with 'a'
--------------------------------------
Output:
1
9
4
6
3
4
Using the Call-Back Feature
The use of the call-back parameter allows FindIn to execute a function every time it locates
an object member that conforms to the search parameters supplied (if any). That function is free to do whatever it chooses.
To use this feature, pass a reference to a function as the third parameter in the call to FindIn.
Whenever FindIn locates an object-member that conforms to the search criteria it calls the
function provided, passing it the reference to the object within which the member has been located, along with the name
(as a string) of the member.
It also passes the search-terms that FindIn's caller supplied, along with the depth within the
object structure that the member was found. In addition, it passes any ClientCallPoint argument
that may have been supplied to FindIn (for debugging purposes).
Example Four shows the call-back feature in action. Here the function supplied to FindIn tests
for the type of a given object member, and displays an alert for each numeric object that is encountered.
Note that the call-back should return true if the search should continue, and
false if the search should stop — this allows a search to be aborted if necessary. Do
note, however, that this rule proves somewhat troublesome when using FindIn because
it is easy to forget to return anything. Unfortunately, no good design-alternative exists here, and inverting things (such that
true meant 'stop') would be equally discordant.
// Example 4 - Using a Call-Back Function
function FindIn (...) { ... }
var MyObj =
{
// Contents of MyObj same as in Example 3
};
function OnFound (Obj, Member, SearchTerms, Depth)
{
if (typeof Obj[Member] === "number")
{
alert ("Numeric member " + Member
+ " found at " + Depth
+ " level(s) deep");
}
return true;
}
FindIn (MyObj, null, OnFound);
--------------------------------------
Output:
Numeric member Age found at 1 level(s) deep
Numeric member Age found at 1 level(s) deep
Numeric member Age found at 1 level(s) deep
Avoiding Prototypical-Member Enumeration
Note that, in JavaScript, a for..in loop enumerates the members of an object's
prototype, as well as its personal members. Given that FindIn encapsulates such
a loop, it will also search an object's prototype object, which may or may not be desired behaviour.
If it is unwanted then it is possible to filter out an object's prototypical members using the
hasOwnProperty method supported by all JavaScript objects. To do this you must
use FindIn's call-back feature, and Example Five illustrates this.
Here, two call-back functions are defined. The first simply reports the name and value of a given member, whether
it is prototypical or not, and the second reports the member's name and value only if it is non-prototypical. A
constructor is also defined and then used to create an object, the members of which are then enumerated using
the non-filtering call-back.
Following the addition of the Hometown member to the
Flintstone prototype, FindIn enumerates the object
again using the non-filtering call-back, which causes the new property to appear in the output. A final call to
FindIn, stipulating the filtering call-back, elides the
Hometown member in the output.
Do note that Internet Explorer, up to and including version 5, does not support
hasOwnProperty, nor does Safari up to and including version 1.3.
// Example 5 - Filtering Prototypical Properties
function FindIn (...) { ... }
function OnFound_NoFilterPrototype (Obj, Member, SearchTerms, Depth)
{
alert (Member + " : " + Obj[Member]);
return true;
}
function OnFound_FilterPrototype (Obj, Member, SearchTerms, Depth)
{
if (Obj.hasOwnProperty (Member))
{
alert (Member + " : " + Obj[Member]);
}
return true;
}
function Flintstone (Name, Age, Occupation)
{
this.Name = Name;
this.Age = Age;
this.Occupation = Occupation;
}
var MyObj = new Flintstone ("Fred", 38, "Quarryman");
FindIn (MyObj, null, OnFound_NoFilterPrototype);
Flintstone.prototype.Hometown = "Bedrock";
FindIn (MyObj, null, OnFound_NoFilterPrototype);
FindIn (MyObj, null, OnFound_FilterPrototype);
--------------------------------------
Output:
Name : Fred
Age : 38
Occupation : Quarryman
Name : Fred
Age : 38
Occupation : Quarryman
Hometown : Bedrock
Name : Fred
Age : 38
Occupation : Quarryman
Limiting Search Depth
As well as controlling the search process through use of search terms, and the action of any call-back function,
FindIn also allows the caller to limit the depth to which it searches within
a given object. The Depth parameter operates in the same way that array-indexing
does, which is to say that zero limits the search to the immediate children of the object, one limits it to
their immediate children, and so on.
Note that should callers wish to use the Depth parameter, but not the
SearchTerms, and OnFound arguments then a
value of null will suffice for those. A null
value for SearchTerms equates to 'anything', and a
null value for the call-back reference simply causes an internal 'null-function'
to be invoked.
Example Six illustrates using the Depth argument to cause
FindIn to count the members of MyObj
to only two levels.
// Example 6 - Limiting Search Depth
var MyObj =
{
Fred :
{
Age : 36,
Associates :
{
Spouse : "Wilma",
Friend : "Barney"
}
},
Barney :
{
Age : 34,
Associates :
{
Spouse : "Betty",
Friend : "Fred"
}
}
};
var Total = FindIn (MyObj, null, null, 1);
alert ("Total found: " + Total);
--------------------------------------
Output:
Total found: 6
Depth-limited searches can also be parameterised, and Example Seven illustrates searching two-levels deep
for all objects that have the letter 's' in their name.
// Example 7 - Stipulating Search Terms and Limiting Search Depth
var MyObj =
{
// Contents of MyObj same as in example 5
};
var Total = FindIn (MyObj, "s", null, 1);
alert ("Total found: " + Total);
--------------------------------------
Output:
Total found: 2
Multiply-Parameterised Searches
Note that the call-back feature can be used to locate a given member within a sub-object of a specific name.
The technique is to locate the owner-object by an initial call to FindIn,
having provided a call-back. When FindIn hits an object of the desired
name it invokes that function, which then makes a second call to FindIn
that searches only one-level deep for the desired member. Example Eight illustrates this, where
FindIn is used to locate all Spouse members
of all Associates objects.
This technique can be extended to locate specific members of specific members of specific objects, and so on
(e.g. Barney.Associates.Friend).
// Example 8 - Two-Stage Searching
var MyObj =
{
// Contents of MyObj same as in Example 7
};
var Total = 0;
function OnSpouseFound ()
{
Total++;
return true;
}
function OnAssociatesFound (Obj, Member)
{
FindIn (Obj[Member], "Spouse", OnSpouseFound);
return true;
}
FindIn (MyObj, "Associates", OnAssociatesFound);
alert ("Total found: " + Total);
--------------------------------------
Output:
Total found: 2
FindIn and AspectJS
Using FindIn in conjunction with AspectJS allows affixes to be applied to
a given method or methods without knowing their location precisely. This is accomplished by calling the appropriate
AJS or AJS_HP method from within a call-back function that is passed to FindIn.
In Example Nine (a somewhat contrived demonstration), a prefix is attached to all the methods that have the name
Display.
// Example 9 - Using FindIn with AspectJS
function FindIn (...);
var MyObj =
{
Fred :
{
Age : 36,
Spouse : "Wilma",
GetValues : function () { alert (this.Age + " " + this.Spouse); }
},
Barney :
{
Age : 34,
Spouse : "Betty",
GetValues : function () { alert (this.Age + " " + this.Spouse); }
},
Wilma :
{
Age : 32,
Spouse : "Fred",
GetValues : function () { alert (this.Age + " " + this.Spouse); }
}
};
function Prefix () { alert ("Prefix Executed"); }
function AddPrefix (Obj, Method)
{
AJS.AddPrefix (Obj, Method, Prefix);
return true;
}
FindIn (MyObj, "GetValues", AddPrefix);
MyObj.Barney.GetValues ();
--------------------------------------
Output:
Prefix Executed
34 Betty
Do note that, in JavaScript, functions are 'class-like' objects in their own right, and AspectJS exploits this by
appending additional methods (for internal use) to the proxy function that is invoked whenever an intercepted
method is called. This means that calling FindIn to simply count the number of
members of a given object-hierarchy will return unexpected results if AspectJS-type intercepts have been applied
to one or more methods.
This is shown in Example Ten, where FindIn reports initially that
MyObj has one member (which is a function). After AspectJS has been used to
attach a prefix to that method, FindIn reports that it has six members (although
client code should not attempt to call these).
// Example 10 - The Effect of AspectJS on Object-Member Totals
var MyObj =
{
MyMethod : function () { }
};
function Prefix () { }
alert (FindIn (MyObj));
AJS.AddPrefix (MyObj, "MyMethod", Prefix);
alert (FindIn (MyObj));
--------------------------------------
Output:
1
6
Copyright © Dodeca Technologies Ltd. 2007