SitePoint Sponsor

User Tag List

Results 1 to 13 of 13
  1. #1
    om nom nom nom Stomme poes's Avatar
    Join Date
    Aug 2007
    Location
    Netherlands
    Posts
    10,271
    Mentioned
    50 Post(s)
    Tagged
    2 Thread(s)

    getting functions to see vars

    Hello all,

    I'm struggling a bit with trying to make sure when I set an event handler (or listener) on some element which calls a function I've got elsewhere, that the function can't see necessary vars because they've already been garbage collected.

    I'm following the type of setup as in Simply Javascript or the Javascript Live course.

    So I have stuff like this

    Code:
    var SomeObj = {
        init:  function() {
            set up vars and event handlers/listeners
        },
    
        somefunc: function() {
            does stuff;
        },
    
        anotherfunc: function() {
            does stuff;
        },
    
        yetanotherfunc: function() {
            does stuff;
        },
    
        lastfunc: function() {
            does stuff;
        }
    };
    This can keep my functions separate and can be used by multiple elements.

    But, often the element who is calling the function is deep inside some for loop or bunch of for loops, looping through a nodeList or whatever.

    I don't know how to get those passed to the functions.


    Here's an example I've got right now:

    Code:
    var SomeObj = {
        init: function() {
            var inputs = document.forms['someForm'].elements;
    
            other unrelated vars...
    
            //loop through inputs, give them a tabIndex
            for (var i=0, j=inputs.length; i<j; i++) {
                inputs[i].autoTabIndex = i;
    
                var someRegex = /%$#%$#*/;
    
               //test if the input's id matches someRegex; 
              //if so, set the function to that input
                if (someRegex.test(inputs[i].id)) {
                    inputs[i].onkeyup = autoTab;
                }
            }
        }, //init
    
        autoTab: function() {
            if (this.value.length === this.maxLength && this.autoTabIndex < j) {
                inputs[this.autoTabIndex + 1].focus();   
            }
        }
    };
    Here, I loop through inputs and set a tab index on them. I state a pattern I'm looking for in the id, and if it matches, run the autoTab function.

    autoTab needs to check if the input has been "filled" to its maxlength (set in the HTML) and also then make sure it's not the last input (though I'm sure no disaster would happen if it was... the function would just have to exit). It then has to see who the next input in line is and move the focus over.

    But autoTab can only see "this", and cannot see "j" (who is declared in the for loop above) or "inputs" (who is declared outside the for loop in the init function). I keep getting this idea I should be able to get inputs from the inputs[i] or this but not sure how (it's not like inputs is actually a parentNode or anything).

    I don't know if I should try returning these vars out into the open or if there's some other way I can pass these to the function. I've tried clumsily adding these vars as arguments to the function, but obviously I'm doing it wrong. I also figured since the function is called from inside the for loop, it should be able to "see" than environment, not just the particular element calling it.

    I would like to "get" this concept much better so I can just make and call functions when I want without being unsure if I'm passing everything I need to pass. What ends up happening is I end up moving the separate functions back into the main one until I have all these functions floating around everywhere together. It becomes a mess and I can't get the benefits of external event listeners.

    Thanks
    poes

  2. #2
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well sketchy at the moment, so maybe wrong on this, but could you benefit from using closures? That way all the methods would have access to the required variables.

    Something like..

    Code JavaScript:
    var SomeObj = (function(){
        var _j = 0; // define variable to be used in closure.
    	var _inputs;
     
    	return {
        init: function(){    
    		set up vars and event handlers/listeners
        },
     
        somefunc: function() {
            does stuff;
        },
     
        anotherfunc: function() {
            does stuff;
        },
     
        yetanotherfunc: function() {
            does stuff;
        },
     
        lastfunc: function() {
            does stuff;
        }
    }());

    I'm also thinking about the use of call or apply and maybe a bind function like this

    Code:
    function bind(fn, context){
       return function(){
         return fn.apply(context, arguments);
      };
    }
    Just ideas.

    RLM

  3. #3
    om nom nom nom Stomme poes's Avatar
    Join Date
    Aug 2007
    Location
    Netherlands
    Posts
    10,271
    Mentioned
    50 Post(s)
    Tagged
    2 Thread(s)
    Thanks for the feedback. Yes, this is (hopefully) going to be very functional and modular and it's going to be happy closure hell. : )

    Yeah I'm questioning whether I should be setting most of my vars out in the open of my object (so global to everyone in the object but not global as far as other scripts are concerned) instead of defining them in the init().

    However I don't want J floating around out there-- it's an inner var used for telling the loop when it's finished. That my function needs to see that too is confuzling me. I'm trying to think of how to rewrite it so that the function doesn't need to know it.

    I did end up moving some vars outside for other things and that's working.

    The call and apply things I haven't touched because while I've read and re-read them, I haven't used them more than once (which was code from someone else) so I don't really know how to use them properly (other than trial-and-error, where basically I'm "hey it seems to work this must be good enough" and that's not necessarily telling me whether it was a Good Idea).

    I mean, I read stuff like this and I'm like, whoa, no, I really don't have any idea what one should be doing with args/params, other than using them to shuttle this from a listener to somefunc(somethingtoreplaceTHIS).

    If anyone knows of a tut that kinda shows using call and apply (without requiring some particular library) that would be nice. I'm using the code from JavascriptLive as a reference, except I'm using Core (parts of it) instead of jQuery.

  4. #4
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,139
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Inside your autoTab method this references the SomeObj not the target of the event, like JQuery. Therefore, maxlength and all those other properties don't exist. Instead you need to extract the target from the event. The other thing you could do is bind the target to event. That way you don't need to actually get the target from the event object. Oh… and yes by assigned the method autoTab directly as an event listener the binding is lost, so this actually refers to the window object (correction). However, that was only part of the problem. The other part being the reference to inputs in autoTab when it doesn't exist and assuming this resolves to the target of event, which it doesn't.

    Here is an example with binding: (untested)

    Code JAVASCRIPT:
    var SomeObj = {
        init: function() {
            var inputs = document.forms['someForm'].elements;
     
            other unrelated vars...
     
            //loop through inputs, give them a tabIndex
            for (var i=0, j=inputs.length; i<j; i++) {
                inputs[i].autoTabIndex = i;
     
                var someRegex = /%$#%$#*/;
     
               //test if the input's id matches someRegex; 
              //if so, set the function to that input
                if (someRegex.test(inputs[i].id)) {
                    inputs[i].onkeyup = (function(target,obj) { return function() { obj.autoTab(target,inputs); }; })(inputs[i],this);
                }
            }
        }, //init
     
        autoTab: function(target,inputs) {
            if (target.value.length === target.maxLength && target.autoTabIndex < j) {
                inputs[target.autoTabIndex + 1].focus();   
            }
        }
    };

  5. #5
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This is good as it gives me chance to run through it(again) and get a better understanding myself Stommie.

    I've done a couple of breakdowns here, which I hope help.

    Code JavaScript:
    function bind(fn, context){
    	return function(){
    		fn.apply(context,arguments);
    	};
    }
     
    // could be written a bit like this
    function bind(fn, context){
      // define variables to be used in closure for our returned nested function
      var handlerFunction = fn;
      var that = context;
     
      return function(){
        // call out handlerFunction but in the context of 'that'
    	// A triggered event will automatically pass in an events object to the associated function.
     
    	// Usually the issue with events is that 'this' is set to the object triggering the event or the global object.
    	// The advantage here is that with apply we get to set our own 'this' or context.
    	// As the events object is passed to our handler as arguments we also get to target the sources info as well.
    	handlerFunction.apply(that, arguments);
      }
    }
     
    // calling bind gives us this
    function(){ handlerFunction.apply(that, arguments); }

    In use in a pretty pointless script
    Code JavaScript:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>Bind Example</title>
    <style type="text/css">
    form label { display: block; cursor: pointer;
    }
    </style>
    </head>
    <body>
    <form id="formNames" name="formNames">
      <fieldset>
        <label for="firstName">First Name: </label>
    	<input name="firstName" id="firstName" type="text" />
    	<label for="lastName">Last Name: </label>
    	<input name="lastName" id="lastName" type="text" />
      </fieldset>
    </form>
     
    <script type="text/javascript">
    // A lazy bit of script written for mozilla and chrome.
     
    // Our bind method
    function bind(fn, context){
    	return function(){
    		fn.apply(context,arguments);
    	};
    }
     
    // We'll use these objects for our context(this)
    var focusObj = {
    	firstNameColour : 'red',
    	lastNameColour : 'green'
    };
     
    var blurObj = {
    	defaultColour : 'yellow'
    };
     
    // This will be our handler method
    function formHandler(e){
      // e is our event object
      // it was passed in as arguments from fn.apply
      var target = e.target;
      var eType = e.type; 
     
      if (target.name == 'firstName' && eType == 'focus') {
    	// note 'this' is our focusObj, so we have access to it's properties
    	target.style.backgroundColor = this.firstNameColour;
      } else if (target.name == 'lastName' && eType == 'focus') {
        target.style.backgroundColor = this.lastNameColour;
      } else if (eType == 'blur') {
        // this time we use the blurObj's context
    	target.style.backgroundColor = this.defaultColour;
      }
    }
     
    var form1 = document.formNames;
    // form1.addEventListener('focus', function(){fn.apply(context,arguments}, true);
    // context and fn are available to the above function thanks to closure.
    form1.addEventListener('focus', bind(formHandler, focusObj), true);
     
    form1.addEventListener('blur', bind(formHandler, blurObj), true);
    </script>
    </body>
    </html>

    RLM

  6. #6
    om nom nom nom Stomme poes's Avatar
    Join Date
    Aug 2007
    Location
    Netherlands
    Posts
    10,271
    Mentioned
    50 Post(s)
    Tagged
    2 Thread(s)
    Cool, I'll look more closely at that today. Most of my other functions I've so far been able to rewrite so that I can just throw a bunch of listeners on them and everything is ok, but this one is so far the only one who needs to see the loop and everything.

  7. #7
    om nom nom nom Stomme poes's Avatar
    Join Date
    Aug 2007
    Location
    Netherlands
    Posts
    10,271
    Mentioned
    50 Post(s)
    Tagged
    2 Thread(s)
    oddz:
    was relooking at your code, and I'm missing something:

    Code:
    if (someRegex.test(inputs[i].id)) {
        inputs[i].onkeyup = (function(target,obj) { 
            return function() { 
                obj.autoTab(target,inputs); 
            }; 
        })(inputs[i],this);
    }
    What's "this" here? I see 2 args, "target" and "obj". But, I thought inputs[i] was my target and was also "this". "this" is the actual form input receiving the event. It's also referred to as inputs[i] (one input in the inputs nodeList).

    So if I fill everything in (to see it working correctly) I would have this, right?

    Code:
    if (someRegex.test(thisinput.id)) {
        thisinput.onkeyup = (function(target,thisinput) { 
            return function() { 
                thisinput.autoTab(target,inputs); 
            }; //?? or is something else actually being given .autoTab?
        })(thisinput,this);
    }
    
    ...
        autoTab: function(thisinput,inputs) {
            if (thisinput.value.length === thisinput.maxLength &&
            thisinput.autoTabIndex < j) {
                inputs[thisinput.autoTabIndex + 1].focus();   
            }
        }
    I'm having trouble following the vars as they go through this.

    Do I understand this correctly: that the result of a function is being assigned to the onkeyup event of the form input, and that result is a function who calls autoTab and is also able to pass on both the form input itself (inputs[i]) and the "inputs" nodeList (so that autoTab() knows it)? Does this mean it also knows what "j" is? because this isn't really part of inputs, but is part of the for loop. If it didn't know what "j" was I could still do inputs.length since it's getting told who "inputs" is.

    But I'm not following the variables correctly. Could you write your previous example down but using the actual vars (just so I can see where they are getting passed on)?

    I've come across a similar issue with different code and I need to understand this better, because it's often my main roadblock in JS.


    RLM2008: yeah I've seen people do a lot of
    var that = this;
    to make their own "this". But I'm still not educated enough yet in stuff like .call and .apply to use it correctly. I think I need to grok oddz' version first, before I'll understand all the pages i find trying to show me how to use .apply.

  8. #8
    om nom nom nom Stomme poes's Avatar
    Join Date
    Aug 2007
    Location
    Netherlands
    Posts
    10,271
    Mentioned
    50 Post(s)
    Tagged
    2 Thread(s)
    Ok, I did manage to get a version of the above working in Firefox, so I stepped through working code to try to see who all the vars were.

    This was the version I got:
    Code:
    var SomeObj = {
        init: function() {
            
            var form;
            var inputs = []; //other vars will need this later
    
          /*choose the correct form, if it exists*/
          /*make an inputs array that only exists of actual inputs, not fieldsets etc*/
            if((form = document.forms.formBereken) || 
              (form = document.forms.formAfsluiten)) {
                var allInputs = form.elements;
                for (var i=0, l=allInputs.length; i<l; i++) {
                    var typePattern = /^(text|select-one)$/i;
                    if (typePattern.test(allInputs[i].type)) {
                        inputs.push(allInputs[i]);
                    }
                }
            }
    
            /*for simplicity, find the input(s) who have a certain ID*/
            for(var i=0, l=inputs.length; i<l; i++) { 
                inputs[i].autoTabIndex = i;
                if(inputs[i].id == 'Postcodenum') {
                    inputs[i].onkeyup = (function(target, obj) { 
                        return function() { 
                            SomeObj.autoTab(target,inputs); 
                        }; 
                    })(inputs[i],this);
                }
            }
        }, //init
           
    
        autoTab: function(target,inputs) {
            if (target.value.length === target.maxLength &&
            target.autoTabIndex < inputs.length) {
                inputs[target.autoTabIndex + 1].focus();   
            }
        }
    };
    Where onload,
    this
    and
    obj
    = the Window object.
    target = the form input (inputs[i])

    After page is loaded and the event occurs, inside autoTab,
    this = the SomeObj
    and target still = the form input

    so was it
    Code:
    if (someRegex.test(thisinput.id)) {
        thisinput.onkeyup = (function(thisinput,Window obj) { 
            return function() { 
                thisinput.autoTab(thisinput,inputs); 
            }; 
        })(thisinput,Window obj); //?? or is "this" at this point actually my obj SomeObj?
    }
    
    ...
        autoTab: function(thisinput,inputs) {
            if (thisinput.value.length === thisinput.maxLength &&
            thisinput.autoTabIndex < inputs.length) {
                inputs[thisinput.autoTabIndex + 1].focus();   
            }
        }
    ?

    I have trouble seeing where the script picks up obj as meaning the Window obj and when this switched from global/Window obj to my SomeObj...

    So for instance if I also wanted to pass on the already-defined "l" (for array.length) so that the autoTab function isn't figuring that out each time either, would I set that in my outer function or can it just be sitting in the inner function (SomeObj = autoTab(targets, inputs, l);) ?

  9. #9
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I was just looking at your orignal script post#1, so sorry if we've moved on.

    The following might not be the best solution. You can add properties to functions just like any other object.

    Just a thought.

    Code JavaScript:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>Pass variable to handler</title>
    <style type="text/css">
    #test { width: 100px; height: 100px; background: red;
    }
    </style>
    </head>
    <body>
    <div id = 'test'>Test</div>
     
    <script type="text/javascript">
    var someObj = {
     
      init: function() { 
     
    	var testElem = document.getElementById('test'),
    	    j = 10;
     
        // add the j property to autoTab
    	this.autoTab.len = j;		
        testElem.onclick = this.autoTab;
     
      },
     
      autoTab: function() {
     
    	// 'this' is now in the context of the 'test' div element (in mozilla anyway)
     
    	var fn = arguments.callee; //autoTab
    	alert(fn.len);
     
    	// or
     
    	var that = someObj;
    	alert(that.autoTab.len);
     
      }
    }
     
    someObj.init();
    </script>
    </body>
    </html>

  10. #10
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Code:
                if(inputs[i].id == 'Postcodenum') {
                    inputs[i].onkeyup = (function(target, obj) { 
                        return function() { 
                            SomeObj.autoTab(target,inputs); 
                        }; 
                    })(inputs[i],this);
                }
    (inputs[i],this) this is someObj.

    in fact you should be able to change

    SomeObj.autoTab(target,inputs);

    to

    obj.autoTab(target,inputs);

    I would have thought.

    edit: I could be wrong on that. It has been known

    A silly question maybe, but why are you passing inputs to the handler?

    edit2: I can't be sure but I'm wondering whether you're creating a circular reference there
    inputs[i].onkeyup = (function(target, obj) {
    return function() {
    SomeObj.autoTab(target,inputs);
    };
    })(inputs[i],this);

  11. #11
    om nom nom nom Stomme poes's Avatar
    Join Date
    Aug 2007
    Location
    Netherlands
    Posts
    10,271
    Mentioned
    50 Post(s)
    Tagged
    2 Thread(s)
    obj.autoTab(target,inputs);

    I would have thought.

    edit: I could be wrong on that. It has been known
    I had tried that at first just because I didn't know who was what and that was what oddz started with. But, if something inside one method (my init()) is referencing another method (autoTab()), I need to preface it with the name of the Object that method belongs to... for some reason, in the outer function "obj" doesn't equal my object, but the Window object instead.

    A silly question maybe, but why are you passing inputs to the handler?
    It's an auto-tab function: once the user has filled up the targeted input(s) with the maxlength of characters, the focus needs to be automatically brought to the next available input (I think it's sucky usability but you see it commonly in fields like credit-card numbers etc). There may be a better way to do it, but so far the only way I know (and I got that original idea from Raffles) is to make an array (or nodeList as I originally had) of all the inputs in the form, assign each of them a tabIndex, so that I can say inputs[i].tabIndex + 1 to get to whoever is the next input.
    So the autoTab function needs to be able to see that whole array/nodeList. Maybe since the inputs do already have their own tabIndex there's another way to get to that, without needing "inputs"?

    (also, yes right now there is only one input doing this, but it was built for more... originally there was a regex catching the id's)

    Also, Raff had added the idea of checking to make sure the targetted input isn't also the last one... because then there's nobody to move the focus to (not likely and actually submit is the last input in all my forms anyway...).

    edit2: I can't be sure but I'm wondering whether you're creating a circular reference there
    inputs[i].onkeyup = (function(target, obj) {
    return function() {
    SomeObj.autoTab(target,inputs);
    };
    })(inputs[i],this);
    Inputs is the array of all form controls I care about... inputs[i] is the one in particular I need. I want to pass both of them on to autoTab.

    ...

    I wrote another closure and left the obj part out. That seemed fine?? Maybe because I'm not using it again as obj.autoTab, but that hadn't worked the first time (browser complained it wasn't a function, meaning it didn't know who the function belonged to).

    Code:
    var SomeObj = {
        init: function() {
        //finds all my fieldsets with class of 'toggle'
            var hiddenFields = Basis.getElementsByClass('toggle');
    
            for (var j=0, h=hiddenFields.length; j<h; j++) {
                var field = hiddenFields[j];
               SomeObj.checkValue(field);
            }
        },
    
    //a list of fieldset id's and values of related select inputs
        config: [
            {'fieldId': 'JongField', 'option': 'Jong'},
            {'fieldId': 'Cascodekking', 'option': 'Casco'}
        ],
                
        checkValue: function(field) {
            var choice;
            for(var k=0, l=SomeObj.config.length; k<l; k++) {
                if(field.id === SomeObj.config[k].fieldId) {
                    var temp=SomeObj.config[k].option;
                    choice=document.getElementById(temp);
                    SomeObj.toggle(choice, field);
                    choice.onchange = (function(target) { 
                        return function() {
                            SomeObj.toggle(target, field); 
                        };
                    })(choice);
                }
            }
        },
    
        toggle: function(target, field) {
            var val = target.value.toLowerCase();
            if(val === 'ja') {
                Basis.removeClass(field, 'hidden');
            }
            else {
                Basis.addClass(field, 'hidden');
            }        
        }
    };
    I still see repetition there (the exact same thing needs to run both on page load AND onchange of the select element) but I'm not pushing my luck just yet, because I'm still trial-and-erroring too much here. I almost grok it.

    Oh and this thing:
    Code:
    var temp=SomeObj.config[k].option;
    choice=document.getElementById(temp);
    is only because last time I tried, IE didn't like
    var choice = document.getElementById(SomeObj.config[k].option);
    which worked fine on everyone else.

    I wonder if "obj" would have worked and lets me not have to explicitly mention my SomeObj but that I did something wrong? In which case, because I'm mentioning SomeObj.autoTab maybe I don't need the obj arg at all.

    *edit Yup, I could safely remove the obj arg entirely:
    Code:
            for(var i=0, l=inputs.length; i<l; i++) { 
                inputs[i].autoTabIndex = i;
                if(inputs[i].id == 'Postcodenum') {
                    inputs[i].onkeyup = (function(target) { 
                        return function() { 
                            SomeObj.autoTab(target, inputs, l); 
                        }; 
                    })(inputs[i]);
                }
            }
    I'm about to run it through Lint to clean it up a bit.

  12. #12
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It's because of 'window.onload = someObj.init', I'm guessing. Just tested it and 'this' came up as 'window' inside init.

    Try putting the script at the end of the body and manually firing someObj.init() and 'this' will be your someObj.

    I've not really used window.onload that much, so you need to change the binding somehow.

    How about simply

    Code:
    window.onload = function(){ someObj.init() };
    In a quick test it seems to work.

    RLM

  13. #13
    om nom nom nom Stomme poes's Avatar
    Join Date
    Aug 2007
    Location
    Netherlands
    Posts
    10,271
    Mentioned
    50 Post(s)
    Tagged
    2 Thread(s)
    Oh I'm using Core to load my objs : ) Listens for 'load' and then calls whateverIneed.init().

    Still, trying to remove the name of SomeObj and replace with a generic 'obj' doesn't seem to catch my SomeObj, just uselessly grabs Window.

    Meh, I'll get it some day : )


Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •