Providing a reference with nested functions in js - javascript

I would like to provide a reference to a function of 1st js in 2nd js. I am aware of single functions and passing variables etc. However, how do I give reference to a function within another function?
HTML
...
...
<script src="./js/scripts.js">
</script>
<script src="./js/pure-swipe.js">
</script>
scripts.js
#param {HTMLElement} element The Element
var Application = function(element) {
this.init();
};
Application.prototype = {
onClickNext: function() {
this.goToNext();
}
goToNext: function() {
console.log("NEXT");
},
};
window.addEventListener("load", function() {
var elements = Utility.toArray(document.querySelectorAll(Utility.selector("application")));
for (var i = 0; i < elements.length; i++) {
var instance = new Application(elements[i]);
}
});
pure-swipe.js
document.addEventListener('swiped-left', function(e) {
onClickNext(); //Call function here
// ...
});

Attach the reference to the global object (which, in the browser, is window)
In the code below, I have changed var instance to window.instance. This allows you to access it in pure-swipe.js by using window.instance.onClickNext()
// scripts.js
window.addEventListener("load", function() {
var elements = Utility.toArray(document.querySelectorAll(Utility.selector("application")));
for (var i = 0; i < elements.length; i++) {
window.instance = new Application(elements[i]);
}
});
// pure-swipe.js
document.addEventListener('swiped-left', function(e) {
window.instance.onClickNext(); //Call function here
});

In this particular example, you'll need an object created via new Application (or similar), which will have onClickNext on it as an inherited property. So if you have that object referenced from the variable app, for instance, then:
document.addEventListener('swiped-left', function(e) {
app.onClickNext(); //Call function here
// ...
});
The function is also accessible as Application.prototype.onClickNext but it's clearly meant to be used as a method, not directly. (Technically, with the code you've shown, it would work if you called it directly, but I assume the content of goToNext is more complex than just a console.log. In any case, directly calling Application.prototype.onClickNext would be very poor practice without supplying it a valid this to use.)

Related

How to invoke a function from both an outside and a sibling function in javascript / google app script

Basic question but I can't figure it out :(. A solution to one makes the other one break. Here is the specific case narrowed down, any help is appreciated.
function onOpen() { // first entry point
var helper = new level1Function();
helper.level2FunctionA();
}
function onFormSubmit() { // second entry point
var helper = new level1Function();
helper.level2FunctionC();
}
function level1Function() {
this.level2FunctionA = function() {
console.log('hi');
}
function level2FunctionB() {
// how do I invoke level2FunctionA from here w/o breaking onOpen entry point?
}
this.level2FunctionC = function() {
level2FunctionB();
}
}
onOpen();
onFormSubmit();
// looking for 2 hi's to the console, one through each flow
create a reference to a variable self, assign to this at the top of the function body
function level1Function() {
var self = this;
this.level2FunctionA = function() {
console.log('hi');
}
function level2FunctionB() {
self.level2FunctionA();
}
this.level2FunctionC = function() {
level2FunctionB();
}
}
Another solution, instead of creating a reference to self as that is error-prone in many situations, you could use Function.prototype.bind and create a function boundLevel2FunctionB, which has this bound to the current level1Function instance (I see you're calling it using the new keyword).
Code:
[...] // level2Function body
function level2FunctionB() {
this.level2FunctionA();
}
var boundLevel2FunctionB = level2FunctionB.bind(this);
this.level2FunctionC = function() {
boundLevel2FunctionB();
}
[...]
Cheers!

Javascript Scoping Issue in Backbone Model / Collection

I'm working with a tree structure, so i need to do some pretty wonkey finds whenever I want to work my way from the leaves to the trunk, but I'm basically trying to create a function that I can pass a function to and apply / call / bind / something the original context so that I can see the variables that i had originally. An explaination would be awesome.
layerListView = Backbone.Marionette.CollectionView.extend({
updateSelectedModelsInTree: function () {
var col = myApp.request('someOtherCollection');
this.collection.startFromLeaves(function (m) {
this.updateSelected(m);
// col is undefined in here
}, this);
}
});
layerCollection = Backbone.Collection.extend({
startFromLeaves: function (doToModel, context) {
if (!this.models) return;
for (var i = this.models.length - 1; i >= 0; i--) {
var model = this.models[i],
tag = this.models[i].get('tag');
if (tag == 'branch') this.startFromLeaves(arguments);
doToModel.call(context, model);
}
}
});
so i'm stuck here, and all I want to do is to be able to see the col variable inside of the top function that is passed into startFromLeaves. I have no idea how to use call / bind / apply, but I'm guessing that my context is what is throwing everything off.
Check out the bind function on underscore.js
This allows you to pass a function that has the this context set. So for instance you could do
updateSelectedModelsInTree: function () {
var col = myApp.request('someOtherCollection');
this.collection.startFromLeaves(_.bind(function (m) {
this.updateSelected(m);
// col is undefined in here
}, this));
}
"this" inside your function will now always be the "this" that contains "updateSelectedModelsInTree"

Understanding Scope of 'this' in jQuery on change event

I wrote a quick custom extension for jQuery for a project I am working on. I am having a hard time understanding the scope of 'this' in a custom onChange method I would like implement. If left out the middle of my code where I am calling the webservice but if you checkout the last two methods, you will see where my problem is. I want to call the updateDetails method with the selected value changes. However, when that method is called within the onchange event, I obviously lose the scope of "this" as this.materialListResponse comes back as undefined in this context. Any help on helping me understand this would be greatly appreciated.
$.fn.appendMaterials = function (options) {
this.options = options;
//Set Default Options
this.defaults = {
typeID: '66E1320D-51F9-4900-BE84-6D5B571F9B80'
};
this.options = $.extend({}, this.defaults, options);
//
Code here to call web service and generate response XML
//
this.materialListResponse = $.xml2json(
$.parseXML($(this.materialListWebservice()).find("GetMaterialTreeResponse").text())).Materials.Material;
this.appendOptionString = function () {
var i = 0;
this.optionString = '<option>'
for (i = 0; i < this.materialListResponse.length; i++) {
this.optionString += '<option>' + this.materialListResponse[i].MaterialCode + '</option>';
};
this.append(this.optionString);
return this;
};
this.appendOptionString();
this.updateDetails = function () {
for (i = 0; i < this.materialListResponse.length; i++) {
if (this.materialListResponse[i].MaterialCode === this.val()) {
$('#table1 #MaterialDescription').val(this.materialListResponse[i].Description);
}
}
}
this.change(this.updateDetails)
};
pass the object this as data to the event:
this.change({that: this}, this.updateDetails)
and then you can access that in the scope of the event callback
this.updateDetails = function(event) {
var that = event.data.that;
...
}
RESOURCES
http://api.jquery.com/event.data/
The event handler will be called later, when you have exited the extension. It's called in the scope of the element, so this will be the element that has changed.
Copy the reference to the list to a local variable, and use that in the event handler:
var list = this.materialListResponse;
this.updateDetails = function() {
for (i = 0; i < list.length; i++) {
if (list[i].MaterialCode === this.val()) {
$('#table1 #MaterialDescription').val(list[i].Description);
}
}
}
By using the local variable in the function, the variable will be part of the closure for the function, so it will survive the scope of the extension method where it is declared.
When a method is called in JavaScript as a callback it behaves as a function. In this case "this" refers to the owner of this function, usually the Window object in a Web browser.

prototype this selector

If I'm using the following function :
clusters.prototype.shop_iqns_selected_class = function() {
if(this.viewport_width < 980) {
$(this.iqns_class).each(function() {
$(this.iqn).on('click', function() {
if($(this).hasClass('selected')) {
$(this).removeClass('selected');
} else {
$(this).addClass('selected');
}
});
});
}
}
To add a property to the clusters function, I know that using this.viewport_width I'm referring to the parent function where I have this.viewport_width defined, but when I'm using the jQuery selector $(this), am I referring to the parent of the $.on() function ?
In JavaScript, this is defined entirely by how a function is called. jQuery's each function calls the iterator function you give it in a way that sets this to each element value, so within that iterator function, this no longer refers to what it referred to in the rest of that code.
This is easily fixed with a variable in the closure's context:
clusters.prototype.shop_iqns_selected_class = function() {
var self = this; // <=== The variable
if(this.viewport_width < 980) {
$(this.iqns_class).each(function() {
// Do this *once*, you don't want to call $() repeatedly
var $elm = $(this);
// v---- using `self` to refer to the instance
$(self.iqn).on('click', function() {
// v---- using $elm
if($elm.hasClass('selected')) {
$elm.removeClass('selected');
} else {
$elm.addClass('selected');
}
});
});
}
}
There I've continued to use this to refer to each DOM element, but you could accept the arguments to the iterator function so there's no ambiguity:
clusters.prototype.shop_iqns_selected_class = function() {
var self = this; // <=== The variable
if(this.viewport_width < 980) {
// Accepting the args -----------v -----v
$(this.iqns_class).each(function(index, elm) {
// Do this *once*, you don't want to call $() repeatedly
var $elm = $(elm);
// v---- using `self` to refer to the instance
$(self.iqn).on('click', function() {
// v---- using $elm
if($elm.hasClass('selected')) {
$elm.removeClass('selected');
} else {
$elm.addClass('selected');
}
});
});
}
}
More reading (posts in my blog about this in JavaScript):
Mythical methods
You must remember this
Don't use this all throughout the code. Methods like $.each give you another reference:
$(".foo").each(function(index, element){
/* 'element' is better to use than 'this'
from here on out. Don't overwrite it. */
});
Additionally, $.on provides the same via the event object:
$(".foo").on("click", function(event) {
/* 'event.target' is better to use than
'this' from here on out. */
});
When your nesting runs deep, there's far too much ambiguity to use this. Of course another method you'll find in active use is to create an alias of that, which is equal to this, directly inside a callback:
$(".foo").on("click", function(){
var that = this;
/* You can now refer to `that` as you nest,
but be sure not to write over that var. */
});
I prefer using the values provided by jQuery in the arguments, or the event object.

Create a function like jQuery(document).ready

How can I do that?
It seems that you can have multiple jQuery's ready() functions, and they will all run when the DOM is loaded.
So how can I create my own ready()-like function? :)
function _addEvent(e, evt, handler){
if(evt == "ready")
evt = "DOMContentLoaded";
if(typeof handler !== 'function')return;
if (e.addEventListener)
e.addEventListener(evt, handler, false);
else if (e.attachEvent)
e.attachEvent("on" + evt, handler);
else
{
var oldHandler = e["on" + evt];
function newHandler(event){
handler.call(e, event);
if(typeof oldhandler === 'function')oldhandler.call(e, event);
}
}
}
var _events = ["ready", "click", "mousedown"]; //...
var _myLib = function(item){
function eventWorker(item, event){
this.add = function(handler){
_addEvent(item, event, handler);
};
}
for(var i=0;i<_events.length;i++)
this[_events[i]] = (new eventWorker(item, _events[i])).add;
};
var MyLib = function(item){
return new _myLib(item);
};
MyLib(document).ready(function(){alert("I am ready!");});
Test =>
http://jsfiddle.net/vgraN/
First, you need to identify what it is you need the function for - is it to respond to a particular browser event?
jQuery's $(document).ready(fn) uses an array internally to hold the functions to execute when the DOM has loaded. Adding a new ready(fn) call appends the function fn to the array. When the DOM has loaded (which is detected in various ways according to which browser the code is executing within), each function in turn in the array is executed. Any functions added using ready(fn) after the DOM has loaded are executed immediately.
In summary, you can use an array to store the functions to execute whenever it is that you need to execute them.
Take a look at domready, a standalone port of the ready(fn) function from jQuery to get some ideas about how to go about it.
It sounds like you want to make an array of functions and append new callbacks to it.
It's not easy to do cross browser.
If you assume the DOMContentLoaded event exists then you can just make
var domready = (function () {
var cbs = [];
document.addEventListener("DOMContentLoaded", function () {
cbs.forEach(function (f) {
f();
});
});
return function (cb) {
cbs.push(cb);
};
})();
You can use other fallbacks like window.onload and a hackish scroll check like jQuery does.
I'd recommend either using domready or reading the source.
Do you want to create a function which when passed a function will call that function at a particular time? (Also, it can be called multiple times.) If so this is how I would do it it. (Based on jQuery code.)
var funcQueue = (function () {
var funcList = [];
function runAll() {
var len = funcList.length,
index = 0;
for (; index < len; index++)
funcList[index].call(); // you can pass in a "this" parameter here.
}
function add(inFunc) {
funcList.push(inFunc);
}
})();
To use:
funcQueue.add(function () { alert("one"); });
funcQueue.add(function () { alert("two"); });
funcQueue.runAll(); // should alert twice.

Categories

Resources