This is a kind of weird problem. Given the javascript below, I'm expecting the newFunctions object to contain functions that wrap an original object, however it's only running the last action that occurs in the loop. Shouldn't var actionToCall copy a reference to what currentAction is currently looking at, and not change when the loops iterate? I'm stumped here.
var typeArray = {
type1: {
function1: function() {
console.log("this is function 1");
},
function2: function() {
console.log("this is function 2");
}
},
type2: {
somefunction: function() {
console.log("this is some function")
}
},
type3: {
blah: function() {
return;
},
moreBlah: function(something) {
console.log(something);
},
evenMore: function() {
console.log("I'm last!");
}
}
},
index,
typeIndex,
currentType,
actionIndex,
currentAction,
newFunctions = {};
for(typeIndex in typeArray) {
currentType = typeArray[typeIndex];
for(actionIndex in currentType) {
currentAction = currentType[actionIndex];
console.log(currentAction.toString());
newFunctions[actionIndex] = function() {
var actionToCall = currentAction;
console.log("about to run action");
actionToCall.call(this);
console.log("action has been called");
}
}
}
console.log(newFunctions);
for(index in newFunctions) {
(newFunctions[index])();
}
It's because actionToCall is being assigned to currentAction.
Since currentAction is global it's value keeps changing as the loop iterates.
When the loop ends, currentAction is assigned to evenMore.
Here's the fix using a self execution function to induce scope.
var typeArray = {
type1: {
function1: function() {
console.log("this is function 1");
},
function2: function() {
console.log("this is function 2");
}
},
type2: {
somefunction: function() {
console.log("this is some function")
}
},
type3: {
blah: function() {
return;
},
moreBlah: function(something) {
console.log(something);
},
evenMore: function() {
console.log("I'm last!");
}
}
},
index,
typeIndex,
currentType,
actionIndex,
currentAction,
newFunctions = {};
for(typeIndex in typeArray) {
currentType = typeArray[typeIndex];
for(actionIndex in currentType) {
currentAction = currentType[actionIndex];
console.log(currentAction.toString());
//induce scope here so actionToCall keeps the current value of currentAction.
(function(){
var actionToCall = currentAction;
newFunctions[actionIndex] = function() {
console.log("about to run action");
actionToCall.call(this);
console.log("action has been called");
}
})();
}
}
console.log(newFunctions);
for(index in newFunctions) {
(newFunctions[index])();
}
Related
I use Angular 1.5 and I made a factory function which is return a literal object like this:
return {
item: null,
get: function() {
return item;
},
create: function() {
if (this.get()){
this.remove();
}
this.item = {};
},
remove: function() {
var item = this.get();
if (item) {
this.item = null;
}
},
add: function() {
if (!this.get()) {
this.create();
}
this.item.newprop = 'value';
}
}
please do not ask me to change to function declaration. I want a object with his own actions(functions) and properties that is working on.
This pattern (like get inside create so on..) I didn't copied from anywhere. so I'm wonder if has a name? It is best way to deal with function-black boxes?
What is the best way to put Promise inside? so every function should return a promise
every then function I need to use bind???
todo like this:
create: function () {
this.get()
.then(remove)
.then(function () {
this.item = {}; // BUT this === undefined!!
});
}
You have to use bind in every then callback function:
var myModule = {
item: null,
get: function() {
return Promise.resolve(this.item);
},
create: function() {
return this.remove().then(function() {
this.item = {};
}.bind(this));
},
remove: function() {
return this.get().then(function(item) {
if (item) {
this.item = null;
}
}.bind(this));
},
add: function() {
return this.get().then(function(item) {
return item || this.create();
}.bind(this)).then(function() {
this.item.newprop = 'value';
}.bind(this));
}
}
// Let see it working:
myModule.create().then(function() {
return myModule.get();
}).then(function(item) {
console.log("After create: ", item);
return myModule.remove();
}).then(function() {
return myModule.get();
}).then(function(item) {
console.log("After remove: ", item);
return myModule.add();
}).then(function() {
return myModule.get();
}).then(function(item) {
console.log("After add: ", item);
});
I am having a scoping issue. I have a simply object literal function, with a number of methods. I am trying to call method4(), from method1()
var myObjectFunc = function() {
return {
method1: function() {
// call method4 here and pass value
},
method2: function() {
},
method3: function() {
},
method4: function() {
}
}
}
I have tried numerous ways with 'this':
Attempt one:
var myObjectFunc = function() {
var that = this;
return {
method1: function() {
// call method4 here and pass value
var myName = 'Peter';
that.method4(myName);
},
method2: function() {
},
method3: function() {
},
method4: function(val) {
console.log(val);
}
}
}
Attempt two:
var myObjectFunc = function() {
return {
method1: function() {
// call method4 here and pass value
var myName = 'Peter';
this.method4(myName);
},
method2: function() {
},
method3: function() {
},
method4: function(val) {
console.log(val);
}
}
}
Both of which return the error:
TypeError: that.method4 is not a function
try this:
var myObjectFunc = function() {
var ret = {
method1: function() {
// call method4 here and pass value
var myName = 'Peter';
ret.method4(myName);
},
method2: function() {
},
method3: function() {
},
method4: function(val) {
console.log(val);
}
}
return ret;
}
Try this:
const myObjectFunc = () => ({
method1() {
// call method4 here and pass value
const myName = 'Peter'
this.method4(myName)
},
method2() {
},
method3() {
},
method4(val) {
console.log(val)
}
})
Then:
myObjectFunc().method1()
logs 'Peter'.
My issue is I have 2 inner objects in my js class and I'm trying to use the methods from one of those objects in my other object (examples of what I'm trying to do below). I understand why this doesn't work because of a the scope. I'm just wondering if there is a way to get it to work.
var Class1 = {
self : this,
Obj1 : {
Obj1Method : function () {
alert("Do something");
},
Obj1Method2 : function () {
alert("Do something else");
},
InnerObj1 : {
InnerNestObj1Method : function (val) {
alert(val + 2);
}
}
},
Class1Method2 : function () {
this.Obj1.Obj1Method2();
},
Obj2 : {
Obj2Method : function (val2) {
self.Obj1.InnerObj1.InnerNestObj1Method(val2);
},
Obj2Method2 : function () {
self.Class1Method2();
}
}
};
Class1.Obj1.InnerObj1.InnerNestObj1Method(3); //works
Class1.Class1Method2(); //works
Class1.Obj2.Obj2Method2(); //No bueno
Class1.Obj2.Obj2Method(5); //No bueno
You can fix your example by replacing self with Class1.
The line self : this, is setting Class1.self to point to the global object (this when that line is evaluated).
var Class1 = {
self : this,
Obj1 : {
Obj1Method : function () {
alert("Do something");
},
Obj1Method2 : function () {
alert("Do something else");
},
InnerObj1 : {
InnerNestObj1Method : function (val) {
alert(val + 2);
}
}
},
Class1Method2 : function () {
this.Obj1.Obj1Method2();
},
Obj2 : {
Obj2Method : function (val2) {
Class1.Obj1.InnerObj1.InnerNestObj1Method(val2);
},
Obj2Method2 : function () {
Class1.Class1Method2();
}
}
};
Class1.Obj1.InnerObj1.InnerNestObj1Method(3); //works
Class1.Class1Method2(); //works
Class1.Obj2.Obj2Method2(); //bueno
Class1.Obj2.Obj2Method(5); //bueno
What happens when you do self: this
// If this is running in non strict mode, from the global scope, `this` points
// To the global object because there was no function call setting `this`
var Class1 = {
self : this,
};
What you need to understand is that this is set by whoever called the function using this. In the example above, there is no caller, so the runtime sets this to point to the global object.
Here's how you could you could make your object a bit more reusable and give yourself a reference to the outer object:
function createClass() {
var self = {
Obj1: {
Obj1Method: function() {
alert("Do something");
},
Obj1Method2: function() {
alert("Do something else");
},
InnerObj1: {
InnerNestObj1Method: function(val) {
alert(val + 2);
}
}
},
Class1Method2: function() {
self.Obj1.Obj1Method2();
},
Obj2: {
Obj2Method: function(val2) {
self.Obj1.InnerObj1.InnerNestObj1Method(val2);
},
Obj2Method2: function() {
self.Class1Method2();
}
}
};
return self;
}
var Class1 = createClass();
Class1.Obj1.InnerObj1.InnerNestObj1Method(3); //works
Class1.Class1Method2(); //works
Class1.Obj2.Obj2Method2(); //works
Class1.Obj2.Obj2Method(5); //works
You can do it with Classes:
"use strict"
class Class1 {
constructor() {
this.Obj1 = {
Obj1Method: function() {
alert("Do something");
},
Obj1Method2: function() {
alert("Do something else");
},
InnerObj1: {
InnerNestObj1Method: function(val) {
alert(val + 2);
}
}
};
var self = this;
this.Obj2 = {
Obj2Method: function(val2) {
self.Obj1.InnerObj1.InnerNestObj1Method(val2);
},
Obj2Method2: function() {
self.Class1Method2();
}
};
}
Class1Method2() {
this.Obj1.Obj1Method2();
}
};
var c1 = new Class1();
c1.Obj1.InnerObj1.InnerNestObj1Method(3); //works
c1.Class1Method2(); //works
c1.Obj2.Obj2Method(3); //works
c1.Obj2.Obj2Method2(); //works
I have this code (also shown below) that is giving me an error in IE8 but is fine in Chrome and PhantomJS.
The error is "Object doesn't support this property or method knockout-2.2.1.debug.js, line 2319 character 35", which is called from currentPage(pages[pages.indexOf(current) + steps]);
I have no clue why it's not working, so any help would be greatly appreciated!
var Page = (function () {
function Page(index, name, canNavigateToPage, navigatedToThisPage) {
this.index = index;
this.name = name;
this.canNavigateToPage = canNavigateToPage;
this.navigatedToThisPage = navigatedToThisPage;
}
Page.prototype.navigateToPage = function () {
if (this.canNavigateToPage()) {
this.navigatedToThisPage(this);
}
};
return Page;
})();
var AccountSearchParameters = (function () {
function AccountSearchParameters() {
this.reference = ko.observable();
this.scheme = ko.observable();
this.lastName = ko.observable();
this.sufficientInputToSearchForAccount = ko.computed(function () {
return this.reference() && this.scheme() && this.lastName();
}, this);
}
return AccountSearchParameters;
})();
function viewModel() {
var self = this,
currentPage = ko.observable(),
accountSearchParameters = new AccountSearchParameters(),
forwardPageProgressionGuards = {
'1': function canMoveToPage2() {
return accountSearchParameters.sufficientInputToSearchForAccount();
},
'2': function canMoveToPage3() {
return true;
},
'3': function canMoveToPage4() {
return true;
}
},
canMoveToNextPage = function (currentlyOnPage) {
function disallowPageMovementNotExplicitlyDefined() {
return false;
}
return (forwardPageProgressionGuards[currentlyOnPage] || disallowPageMovementNotExplicitlyDefined)();
},
canMoveToPreviousPage = function (currentlyOnPage) {
return currentlyOnPage > 1;
},
pages = [
new Page(1, 'Customer details', function () {
return true;
}, function (page) {
currentPage(page);
}),
new Page(2, 'Bank details', forwardPageProgressionGuards['1'], currentPage),
new Page(3, 'Payment details', forwardPageProgressionGuards['2'], currentPage),
new Page(4, 'Confirmation', function () {
return true;
}, currentPage)],
pageNavigator = function (canNavigate, steps) {
current = currentPage();
console.log(canNavigate(current.index));
if (canNavigate(current.index)) {
currentPage(pages[pages.indexOf(current) + steps]);
}
};
currentPage(pages[0]);
self.page = ko.computed(function () {
return currentPage();
});
self.accountSearchParameters = accountSearchParameters;
self.nextPage = function () {
pageNavigator(canMoveToNextPage, 1);
};
self.previousPage = function () {
pageNavigator(canMoveToPreviousPage, -1);
};
self.canMoveToNext = ko.computed(function () {
return canMoveToNextPage(currentPage().index);
});
return self;
}
$(function () {
ko.applyBindings(viewModel());
});
indexOf in IE8 does not supported, use $.inArray
Is it possible for me to call selectCompanyJump(this) internally without calling it from App.site.profile?
Instead of doing App.site.profile.selectStateJump(this); can I do like parent.selectStateJump(this); without reassigning this outside of the .change() call?
$(document).ready(function () {
App.site = function () {
return {
init: function () {
this.profile.init();
},
profile: function () {
var profile;
return {
init: function () {
profile = $('div#profile');
$('select[name="company_id"]', profile).change(function () {
App.site.profile.selectCompanyJump(this);
});
$('select[name="state_id"]', profile).change(function () {
App.site.profile.selectStateJump(this);
});
},
selectCompanyJump: function (select) {
$(select.parent()).submit();
},
selectStateJump: function (select) {
$(select.parent()).submit();
}
}
}()
}
}();
App.site.init();
});
You can reference the "this" scope you want as another variable outside change() function definitions:
profile: function () {
var profile;
return {
init: function () {
profile = $('div#profile');
var self = this;
$('select[name="company_id"]', profile).change(function () {
self.selectCompanyJump(this);
});
$('select[name="state_id"]', profile).change(function () {
self.selectStateJump(this);
});
},
selectCompanyJump: function (select) {
$(select.parent()).submit();
},
selectStateJump: function (select) {
$(select.parent()).submit();
}
}
}()
Assuming that you are just using the select argument of your functions to reference the element that triggered the event you could just pass a pointer to the event binder and then use the this keyword.
profile: function () {
var profile;
return {
init: function () {
profile = $('div#profile');
$('name="state_id"', profile).change(this.selectStateJump);
},
selectStateJump: function () {
$(this).parent().submit();
}
}
you can do the following
$(document).ready(function () {
App.site = function () {
var me = this;
me.selectStateJump = function selectStateJump (select) {
$(select.parent()).submit();
}
return {
....
selectStateJump: selectStateJump
}
and you'll be able to call just me.selectStateJump()
EDIT:
actually below would be enough
$(document).ready(function () {
App.site = function () {
function selectStateJump (select) {
$(select.parent()).submit();
}
return {
method : function(select) {
selectStateJump(select);
}
selectStateJump: selectStateJump
}