I have a function like this:
function doSomething()
{
// do something with select element
}
document.getElementById("selectel").onchange = doSomething;
// Call onchange event
document.getElementById("selectel").onchange();
Now, I recognize that I could call the function directly and pass a parameter. But I'd like to know if it's possible to pass a parameter to the onchange() event handler after it's evoked. I tried
document.getElementById("selectel").onchange("hello");
, but this didn't work.
Thank you for your help.
You need to bind a parameter to your function. I'm going to copy paste a function from Ext-JS that lets you do just that. Warning: not for beginners
/**
* Create a new function from the provided <code>fn</code>, change <code>this</code> to the provided scope, optionally
* overrides arguments for the call. (Defaults to the arguments passed by the caller)
*
* #param {Function} fn The function to delegate.
* #param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
* <b>If omitted, defaults to the browser window.</b>
* #param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
* #param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
* if a number the args are inserted at the specified position
* #return {Function} The new function
*/
function bind(fn, scope, args, appendArgs) {
var method = fn,
applyArgs;
return function() {
var callArgs = args || arguments;
if (appendArgs === true) {
callArgs = Array.prototype.slice.call(arguments, 0);
callArgs = callArgs.concat(args);
}
else if (typeof appendArgs == 'number') {
callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
applyArgs = [appendArgs, 0].concat(args); // create method call params
Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
}
return method.apply(scope || window, callArgs);
};
}
You can use it like
function onChange(e, customParameter) {
// Whatever code
}
document.getElementById("selectel").onchange = bind(onChange, null, ["customParameter"], true);
When your handler is called, additional parameters are appended to the arguments passed by the event handler (the event object).
There's a lot of meat in this function, so feel free to ask any additional questions.
Here's a jsfiddle to see it in action http://jsfiddle.net/yBhG6/
Declare an anonymous function:
document.getElementById("selectel").onchange = function() { doSomething("hello"); }
You could use the apply() method which lets you pass arguments.
doSomething.apply(document.getElementById("selectel"), "hello");
I can see two approach to this question.
One is to call Your callback directly by passing select as this:
doSomething.apply(document.getElementById("selectel"), "Hello");
Second is similar to Igor's, but with help of other variables:
var param = "foo"; // Whatever default is
document.getElementById("selectel").onchange = function() {
doSomething(param);
}
param = "hello";
document.getElementById("selectel").onchange();
Related
I'm working on an exercise right now creating a custom event dispatcher.
I am given a pile of test cases, and I'm stuck at creating a method on my event listener object called hasCallbackFor(name, callback). From what I can gather, the hasCallbackFor function is supposed to take a name of a key on the object i'm creating, and callback is a function that is located in an array at that key. It's supposed to check if that function exists. I'm absolutely lost on how to do this and feel like I've tried everything.
This is the test case for the hasCallbackFor function:
var shouldReturnFalseHasCallBackForIfMethodsNotAdded = function () {
testObj = {};
var scope = {
executeSuccess: true
}
var testFunction = function () {
if (this.executeSuccess) {
success1 = true;
}
}
EventDispatcher.mixin(testObj);
testObj.addEventListener('test', testFunction, scope);
testObj.dispatchEvent('test');
assert(testObj.hasCallbackFor("test", testFunction), 'should have callback registered for test event');
assert(!testObj.hasCallbackFor("test", function () {}), 'should have no callback');
console.log('shouldReturnFalseHasCallBackForIfMethodsNotAdded: success')
}
This is my addEventListener function:
/**
* #param {string} name
* #param {function} callback
* #param {Object} opt_scope
*/
addEventListener: function (name, callback, opt_scope) {
if(!EventDispatcher.myObject[name]) {
EventDispatcher.myObject[name] = [];
}
if(opt_scope) {
var bound = callback.bind(opt_scope);
EventDispatcher.myObject[name].push(bound);
} else {
EventDispatcher.myObject[name].push(callback);
}
},
This is my dispatchEvent function:
/**
* #param {string} name
*/
dispatchEvent: function (name) {
EventDispatcher.myObject[name].forEach(function(value) {
value();
});
},
And for my hasCallbackFor function, I'm trying to use
/**
* #param {string} name
* #param {function} callback
* #return {boolean}
*/
hasCallbackFor: function (name, callback) {
return EventDispatcher.myObject[name].includes(callback);
},
This function fails the test case at the
assert(testObj.hasCallbackFor("test", testFunction), 'should have callback registered for test event');
line, and I've officially run out of ideas why. I've been staring at this code for about 3 hours now and would appreciate any insight on this. Thank you!
A function name testFunction in this case is an address of the code.
Let consider a small example:
var arr = [];
var foo = function(e){return true;};
arr.push(foo);
console.log(arr.includes(foo));//true
var bar = foo.bind('something');
arr.push(bar);
arr.includes(bar);//true
arr.includes(foo.bind('something'));//false .bind creates a new function every time
//The worst case:
arr.push(function(e){return true;});//anonymous function is lost forever
console.log(arr.includes(function(e){return true;}));//false
Back to OP. So the issue is here:
var bound = callback.bind(opt_scope);//creates a new function with a new address
EventDispatcher.myObject[name].push(bound);
I offer two solutions:
Return the function from addEventListener
addEventListener: function (name, callback, opt_scope) {
if(!EventDispatcher.observable[name]) {
EventDispatcher.observable[name] = [];
}
if (opt_scope) {
var bound = callback.bind(opt_scope);
EventDispatcher.observable[name].push(bound);
return bound;
} else {
EventDispatcher.observable[name].push(callback);
return callback;
}
}
and in shouldReturnFalseHasCallBackForIfMethodsNotAdded call the function like this:
var foo = testObj.addEventListener('test', testFunction, scope);
// ^^^
testObj.dispatchEvent('test');
assert(testObj.hasCallbackFor("test", foo), 'should have callback registered for test event');
// ^^^
Bind the function in shouldReturnFalseHasCallBackForIfMethodsNotAdded and do not send scope to addEventListener
//addEventListener is the same as in OP
var foo = testFunction.bind(scope);
testObj.addEventListener('test', foo, null);
testObj.dispatchEvent('test');
assert(testObj.hasCallbackFor("test", foo), 'should have callback registered for test event');
Both work.
In my script, I am reading data from a Spreadsheet and creating a time based trigger to make a POST request with some of that data at a specific time.
The problem is, I can't find any way to pass the data to the function that is called by the trigger. All that the Google App Script doc offers is the ability to name the function to call, but no way to pass it parameters.
var triggerDay = new Date(2012, 11, 1);
ScriptApp.newTrigger("makePostRequest")
.timeBased()
.at(triggerDay)
.create();
Does anyone know how I can pass makePostRequest parameters so the function will execute with the needed data?
This is possible but requires multiple steps. The most important thing here are event objects (mentioned by #St3ph).
var RECURRING_KEY = "recurring";
var ARGUMENTS_KEY = "arguments";
/**
* Sets up the arguments for the given trigger.
*
* #param {Trigger} trigger - The trigger for which the arguments are set up
* #param {*} functionArguments - The arguments which should be stored for the function call
* #param {boolean} recurring - Whether the trigger is recurring; if not the
* arguments and the trigger are removed once it called the function
*/
function setupTriggerArguments(trigger, functionArguments, recurring) {
var triggerUid = trigger.getUniqueId();
var triggerData = {};
triggerData[RECURRING_KEY] = recurring;
triggerData[ARGUMENTS_KEY] = functionArguments;
PropertiesService.getScriptProperties().setProperty(triggerUid, JSON.stringify(triggerData));
}
/**
* Function which should be called when a trigger runs a function. Returns the stored arguments
* and deletes the properties entry and trigger if it is not recurring.
*
* #param {string} triggerUid - The trigger id
* #return {*} - The arguments stored for this trigger
*/
function handleTriggered(triggerUid) {
var scriptProperties = PropertiesService.getScriptProperties();
var triggerData = JSON.parse(scriptProperties.getProperty(triggerUid));
if (!triggerData[RECURRING_KEY]) {
deleteTriggerByUid(triggerUid);
}
return triggerData[ARGUMENTS_KEY];
}
/**
* Deletes trigger arguments of the trigger with the given id.
*
* #param {string} triggerUid - The trigger id
*/
function deleteTriggerArguments(triggerUid) {
PropertiesService.getScriptProperties().deleteProperty(triggerUid);
}
/**
* Deletes a trigger with the given id and its arguments.
* When no project trigger with the id was found only an error is
* logged and the function continues trying to delete the arguments.
*
* #param {string} triggerUid - The trigger id
*/
function deleteTriggerByUid(triggerUid) {
if (!ScriptApp.getProjectTriggers().some(function (trigger) {
if (trigger.getUniqueId() === triggerUid) {
ScriptApp.deleteTrigger(trigger);
return true;
}
return false;
})) {
console.error("Could not find trigger with id '%s'", triggerUid);
}
deleteTriggerArguments(triggerUid);
}
/**
* Deletes a trigger and its arguments.
*
* #param {Trigger} trigger - The trigger
*/
function deleteTrigger(trigger) {
ScriptApp.deleteTrigger(trigger);
deleteTriggerArguments(trigger.getUniqueId());
}
function example() {
var trigger = ScriptApp.newTrigger("exampleTriggerFunction").timeBased()
.after(5 * 1000)
.create();
setupTriggerArguments(trigger, ["a", "b", "c"], false);
}
function exampleTriggerFunction(event) {
var functionArguments = handleTriggered(event.triggerUid);
console.info("Function arguments: %s", functionArguments);
}
In case you are using the script properties for other values as well you might have to nest the trigger values.
Additionally you might have to use the script lock to prevent concurrent modification of the script properties.
You can't pass a parameter when a function is launched from a trigger.
You have to store this information somewhere to allow script find it. For example with what you say I understand you have some data in a spreadsheet, you can put this information in the spreadsheet. The function will manage the way to find appropriate information depending time it is fired.
Stéphane
I'm not sure if it would address your specific issue, but the most convenient workaround I've found is to have a function with parameters wrapped in a function with no parameters, and then get the parameters from a static variable that you set in the top level of your script.
You still have to set the values within the script, but at least you can keep the logic separate so that you can use the base function with different values.
function functionAToTrigger(){
functionToTriggerWithParams(myAParams);
}
var myAParams = {
url: 'https://aurl.com',
date: new Date(2012, 11, 1)
};
function functionBToTrigger(){
functionToTriggerWithParams(myBParams);
}
var myBParams = {
url: 'https://burl.com',
date: new Date(2017, 11, 1)
};
function functionToTriggerWithParams(myParams){
// Add some code to run some checks
// Add some code here to log the results
}
ScriptApp.newTrigger(functionAToTrigger).timeBased().everyMinutes(10).create();
ScriptApp.newTrigger(functionBToTrigger).timeBased().everyMinutes(10).create();
triggerDay is a Trigger, that has uniqueid .
first argument of makePostRequest is Time-driven event, that has undocumented property "triggerUid"
,
So as said #St3ph you need to store pair of you "uniqueid" and "parameters" somehow and get it from storage by "triggerUid"
I've inherited an old codebase that no one at my company has worked with. There's a jquery plugin being used, with minimal documentation. Here's the part I need:
/**
* #param {String} message This is the message string to be shown in the popup
* #param {Object} settings This is an object containing all other settings for the errorPopup
* #param {boolean} settings.close Optional callback for the Okay button
* #returns a reference to the popup object created for manual manipulation
*/
Popup.errorPopup = function(message , settings ){
settings = settings || {};
var defaults = {
allowDuplicate: false,
centerText: true,
closeSelector: ".ConfirmDialogClose"
}
settings = $.extend( defaults , settings );
return Popup.popupFactory( message,
settings,
".ConfirmDialogBox",
".PopupContent"
);
}
Our current calls to this function just use the default settings; none of them pass anything in. Example:
Popup.errorPopup('Sorry, your account couldn\'t be found.');
For one use of this, I need to pass in a callback function for when the popup closes. According to the comments, there is a settings.close parameter, but I have no idea how to go about passing it in via the function call.
I tried this:
Popup.errorPopup('Sorry, your account couldn\'t be found.', {close: 'streamlinePassword'});
where streamlinePassword is the name of the callback function.
But got a javascript error: Property 'close' of object # is not a function.
How can I pass this new object param into the function call?
Don't pass a string, pass a function.
Samples:
function streamlinePassword() {
// ...
}
Popup.errorPopup('...', {close: streamlinePassword});
// also possible
Popup.errorPopup('...', {
close: function () {
}
});
// also possible II
Popup.errorPopup('...', {
close: function test() {
}
});
I have found that not using anonymous functions has made my code more readable and self-documenting by flattening the code into more understandable, standalone functions. So I'd like to break out the following construct from:
function Save() {
myVal = 3.14 // some arbitrary value
$('#myID').each(function(index,element) {
if ($(this).val() === myVal) {}
});
}
Into:
function Save() {
myVal = 3.14 // some arbitrary value
$('#myID').each(myFunction);
}
function myFunction(index,element) {
if ($(this).val() === myVal) {}
}
The problem with using .bind here, is that you lose the value of $(this) inside the each method, so (I don't think) I can bind myVal to myFunction.
Maybe I could use element instead of this?
Edit 1: I should have used .myClass instead of #myID for an example selector.
Edit 2: I'm not using bind in the proposed solution because I don't think bind would work.
Edit 3: I appreciate everyone saying that the first example is more readable. I'm just exploring the language and trying out different thoughts.
And what about :
function Save() {
myVal = 3.14 // some arbitrary value
$('#myID').each(myFunction(myVal));
}
function myFunction(myVal) {
return function(index, element) {
if ($(this).val() === myVal) {}
}
}
You're not losing access to this; you're losing access to myVal because myVal is not known inside myFunction, mainly due to that function being defined in a scope that does not have a definition for myVal.
What you can do is something like this:
function myFunction(index, element, myVal) {
if ($(this).val() === myVal) {}
}
and then:
function Save() {
myVal = 3.14 // some arbitrary value
$('#myID').each(function(index, element) {
myFunction.call(this, index, element, myVal);
});
}
This way if you have a lot of logic inside myFunction, you can still separate it out and just call myFunction from .each)'s callback. Not that myFunction is being called with .call because that way you can pass in an explicit value for this (the first argument). Hence this is the same this that is inside the callback to .each.
To be honest though, the first option is much more readable and you really aren't gaining much by splitting your code out like this.
this in this context will be the same. The one thing you lose access to is myVal. You are right that you can't use Function.bind because that does not allow you to specify to keep the original (call time) this
Here's how you can pass myVal and keep the same this, using a modified version of Function.bind, that we're calling myBind
/**
* Binds the given function to the given context and arguments.
*
* #param {function} fun The function to be bound
* #param {object} context What to use as `this`, defaults
* to the call time `this`
* #param {object[]} customArgs Custom args to be inserted into the call
* #param {number} index Where to insert the arguments in relationship
* to the call time arguments, negative numbers count from the end.
That is, -1 to insert at the end. Defaults to a 0 (beginning of list).
*
*/
function myBind(fun, context, customArgs, index) {
return function() {
// Default the index
index = index || 0;
// Create the arguments to be passed, using an old trick
// to make arguments be a real array
var newArgs = Array.prototype.slice.call(arguments, 0);
// Tack the customArgs to the call time args where the user requested
var spliceArgs = [index, 0].concat(customArgs);
newArgs.splice.apply(newArgs, spliceArgs);
// Finally, make that call
return fun.apply(context || this, newArgs);
}
}
function Save() {
myVal = 3.14 // some arbitrary value
$('#myID').each(
// myFunction wil be called with myVal as its last parameter
myBind(myFunction, null, [myVal], -1)
);
}
function myFunction(index, element, myVal) {
if ($(this).val() === myVal) {
// do it here
}
}
To demonstrate the flexibility of this function, let's bind more than one argument, and it should be inserted at the beginning of the call time arguments
function Save() {
var myVal = 3.14, val2 = 6.28; // some arbitrary values
$('#myID').each(
// myFunction wil be called with myVal and val2 as its first parameter
myBind(myFunction, null, [myVal, val2], 0);
);
}
// Since I don't need element, it's already available as this, we don't
// declare the element parameter here
function myFunction(myVal, val2, index) {
if ($(this).val() === myVal || $(this.val() === val2)) {
// do it here
}
}
This is almost the same answer as Samuel Caillerie. The only difference is that we create a different version of Function.bind that doesn't bind this, just the arguments. Another benefit of this version is that you can choose where the insert the bound arguments;
I have a static javascript function that can take 1, 2 or 3 parameters:
function getData(id, parameters, callback) //parameters (associative array) and callback (function) are optional
I know I can always test if a given parameter is undefined, but how would I know if what was passed was the parameter or the callback?
What's the best way of doing this?
Examples of what could be passed in:
1:
getData('offers');
2:
var array = new Array();
array['type']='lalal';
getData('offers',array);
3:
var foo = function (){...}
getData('offers',foo);
4:
getData('offers',array,foo);
You can know how many arguments were passed to your function and you can check if your second argument is a function or not:
function getData (id, parameters, callback) {
if (arguments.length == 2) { // if only two arguments were supplied
if (Object.prototype.toString.call(parameters) == "[object Function]") {
callback = parameters;
}
}
//...
}
You can also use the arguments object in this way:
function getData (/*id, parameters, callback*/) {
var id = arguments[0], parameters, callback;
if (arguments.length == 2) { // only two arguments supplied
if (Object.prototype.toString.call(arguments[1]) == "[object Function]") {
callback = arguments[1]; // if is a function, set as 'callback'
} else {
parameters = arguments[1]; // if not a function, set as 'parameters'
}
} else if (arguments.length == 3) { // three arguments supplied
parameters = arguments[1];
callback = arguments[2];
}
//...
}
If you are interested, give a look to this article by John Resig, about a technique to simulate method overloading on JavaScript.
Er - that would imply that you are invoking your function with arguments which aren't in the proper order... which I would not recommend.
I would recommend instead feeding an object to your function like so:
function getData( props ) {
props = props || {};
props.params = props.params || {};
props.id = props.id || 1;
props.callback = props.callback || function(){};
alert( props.callback )
};
getData( {
id: 3,
callback: function(){ alert('hi'); }
} );
Benefits:
you don't have to account for argument order
you don't have to do type checking
it's easier to define default values because no type checking is necessary
less headaches. imagine if you added a fourth argument, you'd have to update your type checking every single time, and what if the fourth or consecutive are also functions?
Drawbacks:
time to refactor code
If you have no choice, you could use a function to detect whether an object is indeed a function ( see last example ).
Note: This is the proper way to detect a function:
function isFunction(obj) {
return Object.prototype.toString.call(obj) === "[object Function]";
}
isFunction( function(){} )
You should check type of received parameters. Maybe you should use arguments array since second parameter can sometimes be 'parameters' and sometimes 'callback' and naming it parameters might be misleading.
I know this is a pretty old question, but I dealt with this recently. Let me know what you think of this solution.
I created a utility that lets me strongly type arguments and let them be optional. You basically wrap your function in a proxy. If you skip an argument, it's undefined. It may get quirky if you have multiple optional arguments with the same type right next to each other. (There are options to pass functions instead of types to do custom argument checks, as well as specifying default values for each parameter.)
This is what the implementation looks like:
function displayOverlay(/*message, timeout, callback*/) {
return arrangeArgs(arguments, String, Number, Function,
function(message, timeout, callback) {
/* ... your code ... */
});
};
For clarity, here is what is going on:
function displayOverlay(/*message, timeout, callback*/) {
//arrangeArgs is the proxy
return arrangeArgs(
//first pass in the original arguments
arguments,
//then pass in the type for each argument
String, Number, Function,
//lastly, pass in your function and the proxy will do the rest!
function(message, timeout, callback) {
//debug output of each argument to verify it's working
console.log("message", message, "timeout", timeout, "callback", callback);
/* ... your code ... */
}
);
};
You can view the arrangeArgs proxy code in my GitHub repository here:
https://github.com/joelvh/Sysmo.js/blob/master/sysmo.js
Here is the utility function with some comments copied from the repository:
/*
****** Overview ******
*
* Strongly type a function's arguments to allow for any arguments to be optional.
*
* Other resources:
* http://ejohn.org/blog/javascript-method-overloading/
*
****** Example implementation ******
*
* //all args are optional... will display overlay with default settings
* var displayOverlay = function() {
* return Sysmo.optionalArgs(arguments,
* String, [Number, false, 0], Function,
* function(message, timeout, callback) {
* var overlay = new Overlay(message);
* overlay.timeout = timeout;
* overlay.display({onDisplayed: callback});
* });
* }
*
****** Example function call ******
*
* //the window.alert() function is the callback, message and timeout are not defined.
* displayOverlay(alert);
*
* //displays the overlay after 500 miliseconds, then alerts... message is not defined.
* displayOverlay(500, alert);
*
****** Setup ******
*
* arguments = the original arguments to the function defined in your javascript API.
* config = describe the argument type
* - Class - specify the type (e.g. String, Number, Function, Array)
* - [Class/function, boolean, default] - pass an array where the first value is a class or a function...
* The "boolean" indicates if the first value should be treated as a function.
* The "default" is an optional default value to use instead of undefined.
*
*/
arrangeArgs: function (/* arguments, config1 [, config2] , callback */) {
//config format: [String, false, ''], [Number, false, 0], [Function, false, function(){}]
//config doesn't need a default value.
//config can also be classes instead of an array if not required and no default value.
var configs = Sysmo.makeArray(arguments),
values = Sysmo.makeArray(configs.shift()),
callback = configs.pop(),
args = [],
done = function() {
//add the proper number of arguments before adding remaining values
if (!args.length) {
args = Array(configs.length);
}
//fire callback with args and remaining values concatenated
return callback.apply(null, args.concat(values));
};
//if there are not values to process, just fire callback
if (!values.length) {
return done();
}
//loop through configs to create more easily readable objects
for (var i = 0; i < configs.length; i++) {
var config = configs[i];
//make sure there's a value
if (values.length) {
//type or validator function
var fn = config[0] || config,
//if config[1] is true, use fn as validator,
//otherwise create a validator from a closure to preserve fn for later use
validate = (config[1]) ? fn : function(value) {
return value.constructor === fn;
};
//see if arg value matches config
if (validate(values[0])) {
args.push(values.shift());
continue;
}
}
//add a default value if there is no value in the original args
//or if the type didn't match
args.push(config[2]);
}
return done();
}
I recommend you to use ArgueJS.
You can just type your function this way:
function getData(){
arguments = __({id: String, parameters: [Object], callback: [Function]})
// and now access your arguments by arguments.id,
// arguments.parameters and arguments.callback
}
I considered by your examples that you want your id parameter to be a string, right?
Now, getData is requiring a String id and is accepting the optionals Object parameters and Function callback. All the use cases you posted will work as expected.
So use the typeof operator to determine if the second parameter is an Array or function.
This can give some suggestions:
https://planetpdf.com/testing-for-object-types-in-javascript/
I am not certain if this is work or homework, so I don't want to give you the answer at the moment, but the typeof will help you determine it.
Are you saying you can have calls like these:
getData(id, parameters);
getData(id, callback)?
In this case you can't obviously rely on position and you have to rely on analysing the type:
getType() and then if necessary getTypeName()
Check if the parameter in question is an array or a function.
You can use the arguments object property inside the function.
I think you want to use typeof() here:
function f(id, parameters, callback) {
console.log(typeof(parameters)+" "+typeof(callback));
}
f("hi", {"a":"boo"}, f); //prints "object function"
f("hi", f, {"a":"boo"}); //prints "function object"
If your problem is only with function overloading (you need to check if 'parameters' parameter is 'parameters' and not 'callback'), i would recommend you don't bother about argument type and
use this approach. The idea is simple - use literal objects to combine your parameters:
function getData(id, opt){
var data = voodooMagic(id, opt.parameters);
if (opt.callback!=undefined)
opt.callback.call(data);
return data;
}
getData(5, {parameters: "1,2,3", callback:
function(){for (i=0;i<=1;i--)alert("FAIL!");}
});
This I guess may be self explanatory example:
function clickOn(elem /*bubble, cancelable*/) {
var bubble = (arguments.length > 1) ? arguments[1] : true;
var cancelable = (arguments.length == 3) ? arguments[2] : true;
var cle = document.createEvent("MouseEvent");
cle.initEvent("click", bubble, cancelable);
elem.dispatchEvent(cle);
}
Can you override the function? Will this not work:
function doSomething(id){}
function doSomething(id,parameters){}
function doSomething(id,parameters,callback){}