How can I replace (wrap) methods in new methods programmatically? - javascript

I have several methods that I need to wrap in new methods in basically the same manner. My first solution doesn't work, and I understand why, but I don't know if there is a simple solution to this problem or if it can't be done the way that I want to do it.
Here's an example. I have objects a-c that have an onClick method. I need to execute some code before the onClick methods. I tried the following:
// objects a-c
a = {onClick : function () { console.log('a'); }};
b = {onClick : function () { console.log('b'); }};
c = {onClick : function () { console.log('c'); }};
// first try
var arr = [a, b, c]
for (var i = 0; i < arr.length; i++) {
var oldOnClick = arr[i].onClick;
arr[i].onClick = function () {
// new code here
oldOnClick();
}
}
// outputs 'c'
// what i want is to maintain a reference to the original method
// so in this case, execute my new code and output 'a'
a.onClick();
This doesn't work because when the new method is called, oldOnClick will point to the method from the last iteration and not the to method when it was assigned.
Is there a simple solution that I'm overlooking?

What you need is closure:
for(var i=0, l=arr.length; i<l; i++){
(function(i){
var oldOnclick = arr[i].onClick;
//etc.
})(i);
}

did you tried with some AOP framwork for Javascript?
for example using jQuery AOP plugin:
jQuery.aop.before( {target: String, method: 'replace'},
function(regex, newString) {
alert("About to replace string '" + this + "' with '" + newString +
"' using regEx '" + regex + "'");
}
);
check also here.

Javascript binding rules are pretty odd. Really, javascript is pretty odd.
I don't know that I'd call this the way to fix it, but by introducing a sub-function you can get introduce another bind and thereby fix this particular problem.
Your (modified for quick-y Chrome hacking) code becomes:
a = {onClick : function () { alert('a'); }};
b = {onClick : function () { alert('b'); }};
c = {onClick : function () { alert('c'); }};
var arr = [a, b, c]
for (var i = 0; i < arr.length; i++) {
var oldOnClick = arr[i].onClick;
arr[i].onClick = bind(oldOnClick);
}
a.onClick();
b.onClick();
c.onClick();
function bind(oldFunc)
{
return function () {
//New Code
oldFunc();
}
}
The above code throws up three alerts: a, b, c. Anything replacing '//New Code' will be run at the right time.

var a = {onClick : function () { console.log('a'); }};
var b = {onClick : function () { console.log('b'); }};
var c = {onClick : function () { console.log('c'); }};
var arr = [a, b, c];
for (var i = 0; i < arr.length; i++) {
var oldOnClick = arr[i].onClick;
arr[i].onClick = wrapHandler(oldOnClick);
}
function wrapHandler(handler) {
return function() {
console.log("New stuff");
handler();
}
}
a.onClick(); // outputs "New stuff" then "a"
b.onClick(); // outputs "New stuff" then "b"
b.onClick(); // outputs "New stuff" then "c"

Related

How to loop an object returned by a function in another function in JS?

i've a problem in js. I've a function for example:
function robePersos() {
var persos = {"player" : "data"};
return persos;
}
and then i've another function which call robePersos() like this:
function test() {
var d = robePersos();
for(var k in d) {
console.log(k)
}
}
But nothing happens. Why ?
function robePersos() {
var persos = {
"player": "data"
};
return persos;
}
function test() {
var d = robePersos();
for (var k in d) {
console.log(k)
}
}
test();
EDIT
The first snippet works. So, here is my real function:
function robePersos() {
var persos = {};
$.get({
url : 'url',
success : function(data) {
var text = $(data).find("div[menu='perso'] a"); //.clone().children().remove().end().text();
$(text).each(function(){
perso_name = $(this).text();
perso_link = $(this).attr('href');
persos[perso_name] = perso_link;
});
}
});
for(var k in persos) {
console.log(persos[k]);
}
}
robePersos();
If I replace the loop by only console.log(persos) it works but the loop return nothing. Why ?
If you want to print both Key and Value, use the following small change in your code. Your code is printing just the keys.
function robePersos() {
var persos = {
"player": "data",
"anotherPlayer": "anotherData"
};
return persos;
}
function test() {
var d = robePersos();
for (var k in d) {
console.log(k, "-" ,d[k]); // change made here. It prints both.
}
}
test();
Try whith Object.keys()
function test() {
var d = Object.keys(robePersos());
for (var k in d) {
console.log(k, "-" ,d[k]); // change made here. It prints both.
}
}
Object.keys returns an array whose elements are strings corresponding to the enumerable properties found directly in the object. The order of the properties is the same as that provided when manually iterating over the properties of the object.
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Object/keys

set variable function not working

function eventlisternOnclick(_class, ability, _src, _id){
var cell_onclick = document.querySelectorAll(_class);
for(var c = 0; c < cell_onclick.length; c++){
cell_onclick[c].addEventListener('click', function(){
//problem here , ability(_src) = fun1(mysrc);
//error
ability(_src);
}, false);
}
}
function fun1(mysrc){
console.log();
}
eventlisternOnclick("abc", "fun1", "this.src", null);
the fun1 is a function and i was trying to set as variable at eventlisterOnclick , and run the function there , but when i type ability(_src); it wont work. how do i set the function in my eventlisternOnclick.
if the parameter passed is always class then replace
document.querySelectorAll(_class);
with
document.querySelectorAll("." + _class);
Also, fun1 is a string, so if fun1 is available to this scope then try
eventlisternOnclick("abc", fun1, "this.src", null);
Complete function could be
function eventlisternOnclick(_class, ability, _id){
var cell_onclick = document.querySelectorAll(_class);
for(var c = 0; c < cell_onclick.length; c++){
cell_onclick[c].addEventListener('click', function(){
ability(this.getAttribute("src"));
}, false);
}
}
function fun1(mysrc){
console.log();
}
eventlisternOnclick("abc", fun1, null);

Simplify the code by using cycle function

I have multiply functions which are using the same cycle code and i'm wondering is it possible to simplify the code by having one cycle function so i could execute the code just by calling wanted function names.
Now:
for(var i=0;i<all;i++){ someFunction(i) }
Need:
cycle(someFunction);
function cycle(name){
for(var i=0;i<all;i++){
name(i);
}
}
I tried to do this by using "window" and i get no error but the function is not executed.
var MyLines = new lineGroup();
MyLines.createLines(); // works
MyLines.addSpeed(); // doesn't work
var lineGroup = function(){
this.lAmount = 5,
this.lines = [],
this.createLines = function (){
for(var i=0,all=this.lAmount;i<all;i++){
this.lines[i] = new line();
}
},
this.addSpeed = function (){
// no error, but it's not executing addSpeed function
// if i write here a normal cycle like in createLines function
// it's working ok
this.linesCycle("addSpeed");
},
this.linesCycle = function(callFunction){
for(var i=0,all=this.lAmount;i<all;i++){
window['lineGroup.lines['+i+'].'+callFunction+'()'];
}
}
}
var line = function (){
this.addSpeed = function (){
console.log("works");
}
}
window['lineGroup.lines['+i+'].'+callFunction+'()'];
literally tries to access a property that starts with lineGroups.lines[0]. Such a property would only exist if you explicitly did window['lineGroups.lines[0]'] = ... which I'm sure you didn't.
There is no need to involve window at all. Just access the object's line property:
this.lines[i][callFunction]();
i get no error but the function is not executed.
Accessing a non-existing property doesn't generate errors. Example:
window[';dghfodstf0ap9sdufgpas9df']
This tries to access the property ;dghfodstf0ap9sdufgpas9df, but since it doesn't exist, this will result in undefined. Since nothing is done with the return value, no change can be observed.
Without a name space use:
window["functionName"](arguments);
SO wrap it up and use it thus:
cycle(someFunction);
function cycle(name){
for(var i=0;i<all;i++){
window[name](i);;
}
}
With a namespace, include that:
window["Namespace"]["myfunction"](i);
Note that this is likely a bit of overkill but using a function to make a class object (you can google the makeClass and why it is/could be useful) you can create instances of the object.
// makeClass - By Hubert Kauker (MIT Licensed)
// original by John Resig (MIT Licensed).
function makeClass() {
var isInternal;
return function (args) {
if (this instanceof arguments.callee) {
if (typeof this.init == "function") {
this.init.apply(this, isInternal ? args : arguments);
}
} else {
isInternal = true;
var instance = new arguments.callee(arguments);
isInternal = false;
return instance;
}
};
}
var line = function () {
this.addSpeed = function () {
console.log("works");
};
};
var LineGroup = makeClass();
LineGroup.prototype.init = function (lineNumber) {
this.lAmount = lineNumber?lineNumber:5,
this.lines = [],
this.createLines = function (mything) {
console.log(mything);
var i = 0;
for (; i < this.lAmount; i++) {
this.lines[i] = new line();
}
},
this.addSpeed = function () {
console.log("here");
this.linesCycle("addSpeed");
},
this.linesCycle = function (callFunction) {
console.log("called:" + callFunction);
var i = 0;
for (; i < this.lAmount; i++) {
this.lines[i][callFunction]();
}
};
};
var myLines = LineGroup();
myLines.createLines("createlines");
myLines.addSpeed();
//now add a new instance with 3 "lines"
var newLines = LineGroup(3);
newLines.createLines("createlines2")
console.log("addspeed is a:" + typeof newLines.addSpeed);
console.log("line count"+newLines.lAmount );
newLines.addSpeed();

Writing a function to set some but not necessarily all parameters in another function

I had a coding interview test that asked the following question which I was not able to fully solve. I'm wondering the best way to do this following my approach -- also sorry this is long.
You are given a function to read in like this (not necessarily 2 parameters):
function add(a, b) {
return a + b;
}
The objective is to create a function to initialize some of those variables and again call the function to perform the calculation like, function setParam(func, params). To use this you would do the following:
_add = setParam(add, {b:9})
_add(10) // should return 19
My solution was to parse the function to see how many parameters there are, then set them using the given parameters but since I barely know javascript I was never able to actually return a function with only some variables set and others still undefined.
(attempt at solution)
function setParam(func, params) {
// varray is an array of the the varriables from the function, func
// ie varray = [a,b] in this test
var varray = /function[^\(]*\(([^\)]*)\)/.exec(func.toString())[1].split(',');
//creates an array, paramset, that has the variables in func defined
//where possible
// ex paramset = [a,9] if only b was set
var paramsset = []
for (i = 0; i < varray.length; i++) {
if (typeof(params[varray[i]]) == "undefined"){
paramsset[i] = varray[i];
} else {
paramsset[i] = params[varray[i]];
}
}
//////
// need to modify existing function and return with added parameters
// where I'm stuck as this doesn't work.
newfunc = (function(){
var _func = func;
return function() {
return _func.apply(this, paramsset);
}
})();
newfunc()
}
I'm sure I'm not doing this the correct way, but any help would be appreciated.
I'm certainly not advocating to go towards that solution, but I still implemented something to follow your initial's API design for fun. The signatures weak map is necessary in order to preserve the initial function's signature so that we can call setParams again on partially applied functions.
var setParams = (function () {
var signatures = new WeakMap();
return function (fn, paramsToApply) {
var signature = signatureOf(fn), newFn;
validateParams(paramsToApply, signature.params);
newFn = function () {
var params = appliedParamsFrom(arguments, paramsToApply, signature.indexes);
return fn.apply(this, params);
};
signatures.set(newFn, signature);
return newFn;
};
function signatureOf(fn) {
return signatures.has(fn)?
signatures.get(fn) :
parseSignatureOf(fn);
}
function parseSignatureOf(fn) {
return String(fn)
.match(/function.*?\((.*?)\)/)[1]
.replace(/\s+/g, '')
.split(',')
.reduce(function (r, param, index) {
r.indexes[param] = index;
r.params.push(param);
return r;
}, { indexes: {}, params: [] });
}
function validateParams(paramsToApply, actualParams) {
Object.keys(paramsToApply).forEach(function (param) {
if (actualParams.indexOf(param) == -1) throw new Error("parameter '" + param + "' could not be found in the function's signature which is: 'function (" + actualParams + ")'");
});
}
function appliedParamsFrom(args, paramsToApply, paramsIndex) {
var appliedParams = [],
usedIndexes = [],
argsIndex = 0,
argsLen = args.length,
argSpotIndex = 0;
Object.keys(paramsToApply).forEach(function (param) {
var index = paramsIndex[param];
appliedParams[index] = paramsToApply[param];
usedIndexes.push(index);
});
while (argsIndex < argsLen) {
if (usedIndexes.indexOf(argSpotIndex) == -1) {
appliedParams[argSpotIndex] = args[argsIndex++];
}
++argSpotIndex;
}
return appliedParams;
}
})();
function add(a, b) { return a + b; }
var addTo9 = setParams(add, { b: 9 });
var add10To9 = setParams(addTo9, { a: 10 });
document.write(addTo9(10) + ', ' + add10To9());
Now, note that JavaScript comes with the Function.prototype.bind function which allows to perform in-order partial function application. The first parameter to bind has nothing to do with arguments, it's to bind the this value.
function add(a, b) { return a + b; }
var addTo9 = add.bind(null, 9);
document.write(addTo9(10));
And finally, an implementation with a placholder if you need one:
var partial = (function (undefined) {
var PLACEHOLDER = {};
function partial(fn, partialArgs) {
return function () {
return fn.apply(this, applyPartialArgs(arguments, partialArgs));
};
}
Object.defineProperty(partial, 'PLACEHOLDER', {
get: function () { return PLACEHOLDER; }
});
return partial;
function applyPartialArgs(args, partialArgs) {
var appliedArgs = partialArgs.map(function (arg) {
return arg === PLACEHOLDER? undefined : arg;
}),
partialArgsLen = partialArgs.length,
argsLen = args.length,
argsIndex = 0,
argSpotIndex = 0;
while (argsIndex < argsLen) {
if (
partialArgs[argSpotIndex] === PLACEHOLDER ||
argSpotIndex >= partialArgsLen
) {
appliedArgs[argSpotIndex] = args[argsIndex++];
}
++argSpotIndex;
}
return appliedArgs;
}
})();
function add(a, b, c, d) {
return a + b + c + d;
}
var _ = partial.PLACEHOLDER;
var addTo9 = partial(add, [_, 5, _, 4]);
document.write(addTo9(5, 5));
I'm guessing that they might have been testing for knowledge of partial application. (not currying)
Edit: Edited based upon your comments. This is Crockford's curry function straight from his book.
function add(a, b) {
return a + b;
}
if (!Function.prototype.partial) {
Function.prototype.partial = function() {
var slice = Array.prototype.slice,
args = new Array(arguments.length),
that = this;
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return function() {
return that.apply(null, args.concat(slice.apply(arguments)));
}
};
}
var example = add.partial(4);
console.log(example(10)); // output 14
console.log(example(20)); // output 24
var example = adder(4) assigns example to be function with a closure with a (in this case 4). When example is called like in the console.log, it will in effect be returning "the value of a when example was assigned, plus this new number."
Walkthrough of the partial() function:
Converts arguments to an array
returns a function gets passed the arguments given, which can be called later. It has a closure with the previously assigned arguments.

How to use $.extend() with variables

I'm trying to make a script to use multiple values in window.location.hash but i'm having a problem with the $.extend() function of jquery
I've tried two ways, but both didn't work out.
var MultiHash = {
params: {},
getHash: function () {
var hashString = document.location.hash.replace('#', '').split('&');
for (var i=0; i < hashString.length; i++) {
var key = hashString[i].split('=')[0];
var value = decodeURIComponent(hashString[i].split('=')[1]);
// First way
var a = {key: value};
// Second way
var a = {};
a[key] = value;
$.extend(params, a);
}
return params;
},
...
}
Is anyone seeing the problem?
first you should write :
$.extend(this.params, a); or you cant access param
there may be other issues.
EDIT
it makes sense you return a instead of this.params in my opinion.
$.extend(a,this.params);
return a
There are two problems wrong with what you're trying to do. The first of which being a reference problem as the params variable for that object should be referenced as this.params. The second problem being that you are not saving the result of the object extension. All of this occurs in the following line:
$.extend(params, a);
It should read something like this instead:
this.params = $.extend(this.params, a);
try this one :
var MultiHash = {
params: {},
getHash: function () {
var hashString = document.location.hash.replace('#', '').split('&');
var a = [];
for (var i=0; i < hashString.length; i++) {
var key = hashString[i].split('=')[0];
var value = decodeURIComponent(hashString[i].split('=')[1]);
a.push(key + ":'" + value + "'");
}
$.extend(this.params,(new Function("return {" + a.Join(",") + "}"))());
return this.params;
},
...
}

Categories

Resources