Supplementary

findIn, a JavaScript Function that Locates Objects within other Objects

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 1, where a reference to the object called MyObj is passed to findIn. Note that the result depends on the browser on which you run the code - in Internet Explorer and Opera, the function reports that the object has four members, although in Firefox and Safari the number returned is five.

This is because, in Firefox and Safari, a function's prototype property is visible in the same way as any other member within a for .. in loop (which lies at the heart of findIn), but is invisible when running the same code on IE and Opera. Given that childMethod is a function, its prototype property is counted when the example is run on Firefox and Safari.

In view of this, note also that there are differences between interpreters over what built-in types are actually enumerable.


 // Example 1

 var MyObj =
    {
    ChildNumeric : 3.14159,
    ChildStr     : "Hello",
    childMethod  : function () { },
    ChildBuiltIn : new Date ()

    };

 var Total = findIn (MyObj);

 alert ("Total found: " + Total);

 --------------------------------------

 Output (on Internet Explorer and Opera):

 Total found: 4


 Output (on Firefox and Safari):

 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 2 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

 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 and Opera):

 Total found: 8


 Output (on Firefox and Safari):

 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 3 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 1 and 2 above.

If you need more information on the subject of regular-expressions then 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

 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 CallPoint argument that may have been supplied to findIn (for debugging purposes).

Example 4 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

 var MyObj = ... // MyObj defined 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
            

Inline Call-Back Definitions

JavaScript allows a function definition (rather than a reference to a function that is defined elsewhere) to act as an argument to another function. This is one of the great strengths of the language, and means that a call-back function can be defined within a call to findIn.

This makes sense if a given call-back is used only once, as defining it inline obviates polluting the name-space with yet another symbol. It is also slightly more efficient, and Example 5 re-implements Example 4, but defines the call-back inline.


 // Example 5

 var MyObj = ... // MyObj defined as in Example 3

 findIn (MyObj,
         null,
         function (Obj, Member, SearchTerms, Depth)
            {
            if (typeof Obj[Member] === "number")
               {
               alert ("Numeric member " + Member
                    + " found at "      + Depth
                    + " level(s) deep");

               }

            return true;

            }
        );

 --------------------------------------

 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 6 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 6

 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 7 illustrates using the Depth argument to cause findIn to count the members of MyObj to only two levels.


 // Example 7

 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 8 illustrates searching two-levels deep for all objects that have the letter 's' in their name.


 // Example 8

 var MyObj = ... // MyObj defined as in Example 3

 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 9 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 9

 var MyObj = ... // MyObj defined 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
            

Inline Call-Backs Revisited

Clearly, implementing the two call-back functions discreetly as shown in Example 9 pollutes the name-space, and inline definitions of these functions can also be used to avoid this, and to provide slightly improved efficiency.

Example 10 extends Example 9 by defining the second call-back inline, within the call to findIn that the first call-back makes. That call-back is also defined inline.

Do note that it is advisable to adopt a rigorous and painstakingly tidy approach to code layout when implementing code such as this, otherwise it is easy to obfuscate matters.


 // Example 10

 var MyObj = ... // MyObj defined as in Example 7

 var Total = 0;

 findIn ( MyObj,
         "Associates",
          function (Obj, Member)
             {
             findIn ( Obj[Member],
                     "Spouse",
                      function ()
                         {
                         Total++;
                         return true;
                         }

                    );

             return true;

             }

        );

 alert  ("Total found: " + Total);

 --------------------------------------

 Output:

 Total found: 2
            

findIn and AspectJS

To understand findIn's value when used in conjunction with AspectJS, consider AspectJ (the AOP extension to the Java programming-language). In AspectJ, 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 the AJS object, and such interceptions are applied using expressions that are coded in AspectJ syntax, and which can contain wildcards.

JavaScript, however, is evaluated dynamically. This means that, in the absence of extensions to the language, AOP must be implemented in a similarly dynamic fashion using JavaScript itself (which is the role of the AJS object). Given this, and to permit the use of wildcards when applying affixes, a function such as findIn is necessary.

To use findIn in this way, you pass it a reference to a call-back function that, when invoked, calls the appropriate AJS method. In Example 11 (a somewhat contrived demonstration of the principle), a prefix is attached to all the methods that have the name Display.


 // Example 11

 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 the AJS object exploits this by appending additional methods to the proxy function that it substitutes for the interceptee, and which is the actual function that executes whenever the interceptee is called.

This means that calling findIn to simply count the number of members of a given object-hierarchy may return unexpected results if AspectJS-type intercepts have been applied to one or more methods.

This is shown in Example 12, where (on IE and Safari) findIn reports initially that MyObj contains 1 member, which is myMethod. However, after the AJS object has been used to attach a prefix to that method, findIn reports that it has ten members, which are myMethod, as before, plus the nine function-members that the AJS object adds to myMethod and their prototype-members. (Note that Firefox and Safari both report two and twenty members because function-objects' prototype members are visible to a for .. in loop.)


 // Example 12

 var MyObj =
    {
    myMethod : function () { }
    };

 function prefix () { }


 alert (findIn (MyObj));

 AJS.addPrefix (MyObj, "myMethod", prefix);

 alert (findIn (MyObj));

 --------------------------------------

 Output (on Internet Explorer and Opera):

 1
 10

 Output (on Firefox and Safari):

 2
 20
            

Using findIn_DbC

findIn_DbC ('findIn Design by Contract') is an optional adjunct to findIn that uses the AJS object to apply a prefix to that function. The prefix tests the parameters provided to findIn, and throws an exception whenever bad arguments are detected.

To use findIn_DbC your code should import the function's definition, along with the AJS object definition, and should then make a single call to findIn_DbC as soon as possible in the application's run. Example 13 illustrates this.

Examining the parameters that findIn_DbC accepts:

  1. The first parameter must be a reference to the object that owns findIn, this may be the Global object, or it may be some other user-defined object should you choose to make findIn a method of that object.
  2. The second parameter must be a string containing the name of the findIn function. This is necessary because of the way that the AJS object works, and, from there, because you may have renamed findIn. If you have not re-named the function then the value for this argument must be "findIn".
  3. The third parameter must be a reference to version 1.1 of the AJS object. It is necessary to pass this explicitly because you may have chosen to make the AJS object a member of another object, thus taking it out of global scope. Additionally, you may have chosen (for reasons known only to you) to change the name of the AJS object, in which case you should supply that new name as the third parameter.
  4. The fourth parameter (the CallPoint argument) is optional and fulfills the same role as the CallPoint does in AspectJS method signatures. See the relevant section in the AspectJS tutorial for more information.

Failure to supply the first three parameters, or to provide arguments of an incorrect type, will cause findIn_DbC to throw an Error object, whose message member will contain a detailed description of the problem (including the contents of CallPoint, if the caller has provided a value for that argument). Additionally, and given that it is necessary to call findIn_DbC only once, calling it again will also raise an exception.


 <!-- Example 13 -->

 <html>
    <head>
       <script type = "text/javascript" src = "AJS.js"       ></script>
       <script type = "text/javascript" src = "findIn.js"    ></script>
       <script type = "text/javascript" src = "findIn_DbC.js"></script>

       <script type = "text/javascript">

       findIn_DbC (this, "findIn", AJS, "Example 13 - Point A");

       ...


       </script>
    </head>

    <body> ... </body>

 </html>
            

Effects of findIn_DbC

Once called, subsequent calls to findIn will continue to operate normally, but with the benefit of rigorous checks on any arguments that client code supplies it. To disable this error checking, and thereby lose the overhead it entails, simply comment out the call to findIn_DbC, and disable any statement that loads findIn_DbC's code.

The second effect of findIn_DbC is that it appends a CallPoint argument to findIn's method signature, which fulfills the same role as the argument accepted by findIn_DbC. This is the same effect that aJS_DbC has on AJS object-methods.

The 'before and after' effects of using findIn_DbC are illustrated in Example 14.

First, a number of erroneous calls are made to findIn, in an attempt to search the object called MyObj. These, however, are all incorrect in that they omit essential arguments, or provide arguments of the wrong type; yet none of them raise an exception.

Following this, the code enables error checking, and then repeats the erroneous calls. This time the prefix applied by findIn_DbC detects the bad parameters and throws an Error object accordingly. When an Error object is caught the contents of its message member are displayed.

Note that, once enabled, there is no way to turn error checking off during a given run of the application.


 // Example 14

 var MyObj = { SomeMember : 0 };

 function OnFound () { return true; }

 findIn     ();                                             // The following six calls are incorrect
 findIn     (null,    null,          null,     null);       // yet raise no exceptions
 findIn     (MyObj,   2,             null,     null);
 findIn     (MyObj,  "SomeMember",   "",       null);
 findIn     (MyObj,  "SomeMember",   OnFound, "1");
 findIn     (MyObj,  "SomeMember",   OnFound, -1);

 findIn_DbC (this, "findIn", AJS);

 try { findIn ();                                           } catch (E) { alert (E.message); }
 try { findIn (null,   null,          null,     null, "A"); } catch (E) { alert (E.message); }
 try { findIn (MyObj,  2,             null,     null, "B"); } catch (E) { alert (E.message); }
 try { findIn (MyObj, "SomeMember",   "",       null, "C"); } catch (E) { alert (E.message); }
 try { findIn (MyObj, "SomeMember",   OnFound, "1",   "D"); } catch (E) { alert (E.message); }
 try { findIn (MyObj, "SomeMember",   OnFound, -1,    "E"); } catch (E) { alert (E.message); }

 --------------------------------------

 Output:

 Error in call to findIn - Obj argument is undefined. Client-code call point: undefined
 Error in call to findIn - Obj argument is null. Client-code call point: A
 Error in call to findIn - SearchTerms argument is not a string. Client-code call point: B
 Error in call to findIn - OnFound argument is not a reference to a function. Client-code call point: C
 Error in call to findIn - DepthMax argument is non-numeric. Client-code call point: D
 Error in call to findIn - DepthMax argument is less than zero. Client-code call point: E