JavaScript call object method by its name [duplicate] - javascript

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
javascript object, access variable property name?
I'm trying to call a method of an object by its name when certain event happens - here's my code:
var imageObject = {
sendImage : function(obj) {
"use strict";
var thisId = obj.attr('id');
var thisSubmit = obj.attr('data-submit');
var thisCallback = obj.attr('data-callback');
var thisUrl = $('#' + obj.attr('data-url')).text();
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: imageObject.thisSubmit,
onComplete: imageObject.thisCallback
});
}
}
I would also like to pass the parameters to the submit and callback method.
At the moment nothing happens and I'm sure I'm not calling it right - could anyone explain how to achieve this?
I've also used the following for submit and complete :
onSubmit: function(file, extension) {
imageObject.thisSubmit(file, extension);
},
onComplete: function(file, response) {
imageObject.thisCallback(file, response);
}
But what I get is the error saying :
imageObject.thisSubmit is not a function
The attributes only store the name (string) of the method within the object - so it would be for instance 'uploadImage'.

TJ's answer is nearly there, except that (per the new code in your question) you need to either copy the arguments from onSubmit to thisSubmit, or use .apply() to copy them for you.
The code below uses the latter method, which avoids having to duplicate the function signature over and over:
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: function() {
imageObject[thisSubmit].apply(imageObject, arguments);
},
onComplete: function() {
imageObject[thisCallback].apply(imageObject, arguments);
}
});

If I've understood your question correctly, you need to use the square bracket syntax to access the properties:
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: function () { //Anonymous function so you can pass arguments
imageObject[thisSubmit]("myArg"); //Square brackets here
},
onComplete: imageObject[thisCallback] //Square brackets here (no arguments)
});

I'm assuming the thisSubmit and thisCallback local variables are meant to be the names of the functions that exist on imageObject.
Do both call those methods using names from those strings, and pass in arguments, you use a combination of bracketed syntax and closures:
var imageObject = {
sendImage : function(obj) {
"use strict";
var thisId = obj.attr('id');
var thisSubmit = obj.attr('data-submit');
var thisCallback = obj.attr('data-callback');
var thisUrl = $('#' + obj.attr('data-url')).text();
new AjaxUpload(thisId, {
action: thisUrl,
name: 'thisfile',
onSubmit: function() {
imageObject[thisSubmit](arg, anotherArg, etc);
},
onComplete: function() {
imageObject[thisCallback](arg, anotherArg, etc);
}
});
}
// Presumably there are more methods here, or added later...
}

you have to use call which call a function in js
onSubmit: function(file, extension) {
imageObject.thisSubmit.call(undefined, file,extension);
},
see What is the difference between call and apply?

Related

JS Revealing Pattern event undefined issue

I am using the modular design pattern for JS and I keep running into issues when using arguments bound functions. I have a particular function that I would like to bind to different events to keep from having to write the function for each bound event. The only difference in the function, or the argument, is the table that will be updated. The problem is that when I build a function with the arguments I need and pass those arguments to bound events, I get an undefined error, in the console, on load. Keep in mind, I want to stick with this design pattern for the security it offers.
Here is my JS:
var Users = (function(){
var $addRoleForm = $('#addUserRole');
var $rolesTableBody = $('#table-roles tbody');
$addRoleForm.submit(ajaxUpdate(event, $rolesTableBody));
function ajaxUpdate(event, tableName) {
event.preventDefault();
event.stopPropagation();
var url = this.action;
var data = $(this).serialize();
var $this = $(this);
$.ajax({
type: 'POST',
url: url,
dataType: 'json',
data: data,
success: function(data) {
if(data.st === 0){
$messageContainer.html('<p class="alert alert-danger">' + data.msg + '</p>');
setTimeout(function(){
$messageContainer.hide();
}, 7000);
} else {
$messageContainer.html('<p class="alert alert-success">' + data.msg + '</p>');
tableName.fadeOut().html('').html(data.build).fadeIn();
$this.find('input').val('');
setTimeout(function(){
$messageContainer.hide();
}, 7000);
}
},
error: function(xhr, status, error){
console.log(xhr.responseText);
}
});
}
})();
Here is the error I get in the console, on load:
Uncaught TypeError: Cannot read property 'preventDefault' of undefined
I have tried to bind the event like this: $addRoleForm.on('submit', ajaxUpdate(event, $rolesTableBody)); and receive the same results.
Any ideas how to fix this?
You're seeing that issue, because the way you have it written now, ajaxUpdateexecutes, returns undefined and THEN passes undefined to the event listener, so you're basically doing this: $addRoleForm.submit(undefined).
2 Choices here:
1) You can wrap it in an anonymous function:
$addRoleForm.submit(function(event) {
//pass the value of "this" along using call
ajaxUpdate.call(this, event, someValue);
});
$someOtherForm.submit(function(event) {
//pass the value of "this" along using call
ajaxUpdate.call(this, event, someOtherValue);
});
2) You can set the first argument in-advance using bind:
$addRoleForm.submit(ajaxUpdate.bind($addRoleForm, someValue));
$someOtherForm.submit(ajaxUpdate.bind($someOtherForm, someOtherValue));
Using this way, you're binding the value of this to be $addRoleForm, setting the first argument to always be someValue, so it's the same as:
ajaxUpdate(someValue, event) {
//value of "this" will be $addRoleForm;
}
To pass the event, and the custom argument, you should be using an anonymous function call
$addRoleForm.submit(function(event) {
ajaxUpdate(event, $rolesTableBody));
});
This is by far the easiest and most readable way to do this.
What you're doing right now equates to this
var $addRoleForm = $('#addUserRole');
var $rolesTableBody = $('#table-roles tbody');
var resultFromCallingFunction = ajaxUpdate(event, $rolesTableBody); // undefined
$addRoleForm.submit(resultFromCallingFunction);
Where you're calling the ajaxUpdate function, as that's what the parentheses do, and pass the returned result back to the submit callback, which in your case is undefined, the default value a function returns when nothing else is specified.
You could reference the function, like this
$addRoleForm.submit(ajaxUpdate);
but then you can't pass the second argument
The question refers to the Revealing Module pattern. Benefit of using this design is readability. Going with the anon function may work, but defeats the overall purpose of the module pattern itself.
A good way to structure your module to help maintain your scope is to setup helper functions first, then call a return at the end.
Example use case with events:
var User = function() {
// local VARS available to User
var addRoleForm = document.querySelector('#addUserRole');
var rolesTableBody = document.querySelector('#table-roles tbody');
// Helper function 1
function ajaxUpdate(tableName) {
...
}
// Helper function 2
function someFunc() {
...
}
function bindEvents() {
addRoleForm.addEventListener('submit', ajaxUpdate, false);
addRoleForm.addEventListener('click', someFunc, false);
}
function init() {
bindEvents();
}
return {
runMe:init
}
}().runMe();
Helps to "modularize" your workflow. You are also writing your revealing pattern as an IIFE. This can cause debugging headaches in the future. Editing the IIFE to instead invoke via the return is easier to maintain and for other devs to work with and learn initially. Also, it allows you to extend outside of your IFFE into another Module, example:
var Clothes = function() {
function anotherFunc() {
...
}
init() {
User.runMe();
anotherFunc();
}
return {
addClothes: init
}
}().addClothes();
I hope this helps to give you a better understanding of how/when/why to use the JS revealing pattern. Quick note: You can make your modules into IIFE, that's not a problem. You just limit the context of the scope you can work with. Another way of doing things would be to wrap the var User and var Clothes into a main module, and then make that an IIFE. This helps in preventing polluting your global namespace.
Example with what I wrote above:
// MAIN APPLICATION
var GettinDressed = (function() {
// MODULE ONE
///////////////////////////
Var User = function() {
// local VARS available to User
var addRoleForm = document.querySelector('#addUserRole');
var rolesTableBody = document.querySelector('#table-roles tbody');
// Helper function 1
function ajaxUpdate(tableName) {
...
}
// Helper function 2
function someFunc() {
...
}
function bindEvents() {
addRoleForm.addEventListener('submit', ajaxUpdate, false);
addRoleForm.addEventListener('click', someFunc, false);
}
function init() {
bindEvents();
}
return {
runMe:init,
style: someFunc
}
}();
// MODULE TWO
//////////////////////////
var Clothes = function() {
function anotherFunc() {
...
}
init() {
User.style();
anotherFunc();
}
return {
dressUp: init
}
}();
// Define order of instantiation
User.runMe();
Clothes.dressUp();
}());

JS: Access from a property to another property in the same object [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 7 years ago.
I'll explain my issue using an example:
I have an object which is called 'ExampleObj' which returns 3 property 'init', 'age', 'weight' and I need to access from age to weight but for some reason i can't do that. Could you explain me why and how can I achieve the correct result?
EDIT: this is the current code, self.tabAnimation() is working on dom ready but... is not working on "click", even if I use (); check the **** in the code, is the line which triggers me error.
return {
init: function() {
var self = this;
tabs.init();
self.tabAnimation();
tabToggler.on('click', self.tabAnimation );
},
tabAnimation: function() {
var self = this;
var activeTabBars = function() {
console.log('lol');
tabItem.find(bars).each(function() {
var me = this;
****self.animateBars(me,1000)****
});
}
animateOnVisible.init(tabItem, activeTabBars);
},
animateBars : function(el, duration) {
var percentage = $(el).data('value') + "%";
$(el).animate({
'width': percentage
}, duration);
}
}
}
Thank you very much
Davide
You have to use this:
age: function() {
console.log('some random log');
var me = this;
me.weight;
me.weight(something);
},
You don't have to assign this to another variable, but it doesn't hurt anything if you do.
Note that in your "init" function,
self.age;
by itself will do nothing. To call the function, you have to write it
self.age();

How to set a default function for an object?

So, I've started playing with using an object to help organize my functions. So instead of having to make super long function names, I can just have sub functions in an object with the same prefix.
In my example, I'm using 'get' as a prefix, so I could call subfunctions by doing get.function(); and get.otherfunction();. However, I want to also be able to set a "default" function for the get object, so I can just call get(); by itself and it runs a function (but I don't want that function to run if I'm calling one of the subfunctions).
Here is the code I have thus far:
var get = {
default: function() {
alert('default function');
},
secondary: function() {
alert('secondary function');
}
}
You want to make an ordinary function, then add other functions as properties:
var get = function() { ... };
get.secondary = function() { ... };
If you want to, you could also write
get.default = get;
Or
get.default = function() { return get(); };

Creating reuseable variables within a .on() or any object literal

Sometimes I run into situations where I'm having to create the same variables, and retrieve the exact same type of information over & over again while inside of a object literal, such as an .on() Out of sheer curiosity, and the fact that there has to be a better way, here I am.
NOTE I am not talking about jQuery .data() or any sort of normal window. global variable. I am talking one that is maintained within the closure of the object literal.
Some of these variables change in real-time of course, hence why I always had them inside of each method within .on()
Case in point:
$(document).on({
focusin: function () {
var placeHolder = $(this).attr('data-title'),
status = $(this).attr('data-status');
// etc etc
},
focusout: function () {
var placeHolder = $(this).attr('data-title'),
status = $(this).attr('data-status');
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, '.selector');
Is there a way to just have the variables stored somewhere, and retrieve on each event? They need to be dynamic
$(document).on({
// Basially:
// var placeHolder; etc
// each event gets these values
focusin: function () {
// now here I can simply use them
if (placeHolder === 'blahblah') {}
// etc
}
}, '.selector');
You can use event data to pass some static variables to the event, as well as to make a method-wise trick to pass the "dynamic" ones:
$(document).on({
focusin: function(e) {
var attrs = e.data.getAttributes($(this)),
var1 = e.data.var1;
var placeHolder = attrs.title,
status = attrs.status;
// ...
},
focusout: function(e) {
var attrs = e.data.getAttributes($(this)),
var1 = e.data.var1;
var placeHolder = attrs.title,
status = attrs.status;
// ...
},
// ...
}, ".selector", {
// static variables
var1: "some static value",
// dynamic "variables"
getAttributes: function($this) {
return {
title: $this.data("title"),
status: $this.data("status")
};
}
});
DEMO: http://jsfiddle.net/LHPLJ/
There are a few ways:
Write your own function that will return the JSON above; you can loop through properties to keep from duplicating work.
Write a function that returns those variables (eg: as JSON) so you need only call one function each time.
Write a function to set those variables as global properties and refer to them as needed.
Why not simply add a helper function to extract it?
var getData = function(elm) {
return {
placeHolder : $(elm).attr('data-title'),
status : $(elm).attr('data-status');
};
};
$(document).on({
focusin: function () {
var data = getData (this);
// do stuff with data.status etc.
},
//repeat...
If you're always targeting the same element (i.e. if there's a single element with class selector), and if the values of those variables won't change between the different times the events are triggered, you can store them on the same scope where the handlers are defined:
var placeHolder = $('.selector').attr('data-title'),
status = $('.selector').attr('data-status');
$(document).on({
focusin: function () {
// etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, '.selector');
Functions declared on the same scope as those variables will have access to them.
I would recommend wrapping everything in a function scope so that the variables are not global. If these attributes never change, you could do something like:
(function(sel){
var placeHolder = $(sel).attr('data-title'),
status = $(sel).attr('data-status');
$(document).on({
focusin: function () {
// etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, sel);
})('.selector');
Otherwise, you could do this (in modern browsers, IE9+)
(function(sel){
var obj = {};
Object.defineProperty(obj, 'placeHolder', {
get:function(){return $(sel).attr('data-title');}
});
Object.defineProperty(obj, 'status', {
get:function(){return $(sel).attr('data-status');}
});
$(document).on({
focusin: function () {
console.log(obj.placeHolder, obj.status);
//etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, sel);
})('.selector');
You're doing it close to correct, but the best way to do it is this:
$(document).on({
focusin: function () {
var el = $(this), //cache the jQ object
placeHolder = el.data('title'),
status = el.data('status');
// etc etc
}
}, '.selector');
Data was created for this purpose, don't worry about trying to create re-useable items. If you're delegating the event using object, then it's probably because the elements aren't always on the page in which case you need to get the variables within each individual event.
Finally, don't try to optimize when you don't need to.
You can save them in the window object and make them global.
window.yourVariable = "whatever";
This does what you want but is for sure not the most clean way. If you can, you can consider saving the desired data to the object itself via $(element).data("key", "value")

How to call a member function from another member function in Javascript

Say I have some code like this
function Chart(start, end, controller, method, chart)
{
console.log('Chart constructor called');
this.start = start;
this.end = end;
this.controller = controller;
this.method = method;
this.chart = chart;
this.options = {};
}
Chart.prototype.update = function()
{
console.log('update ' + new Date().getTime());
$.getJSON('index.php', {
controller: this.controller,
method: this.method,
START: this.start,
END: this.end },
function(json) { this.draw(json); }); //<-- Problem right here!
}
Chart.prototype.draw = function(json)
{
//lots of code here
}
I'm getting the error Uncaught TypeError: Object #<an Object> has no method 'draw'. Now, I'm the first to admit that I'm pretty new to Javascript. Am I supposed to call member functions in another way? Or am I supposed to do something different altogether?
edit: Here is how I'm creating my object:
chartObj = new Chart(start, end, 'OBF.RootCauses', 'ajaxRootCauses', chart);
The problem here is that this is changed because you are defining a new function - so this refers to the function you are in.
There are other ways how to get around this, but the simplest way would be to save this to a variable and call the function on that variable, something like this:
Chart.prototype.update = function()
{
console.log('update ' + new Date().getTime());
var self = this;
$.getJSON('index.php', {
controller: this.controller,
method: this.method,
START: this.start,
END: this.end },
function(json) { self.draw(json); });
}
See Chris's answer for a different approach for solving the same problem.
Since you're already using jQuery, you can change this line
function( json ) { this.draw( json ); });
to this:
$.proxy( this.draw, this ) );
That will preserve the context where the function was called (i.e., the this variable).

Categories

Resources