How to create a object/function with defined setting when create new? - javascript

I wish to do something like this:
function Student (id, class) {
var id = id
var class = class
this.get = function (subject) {
$.ajax({
url: 'myurl',
data: { id: id, class: class, subject: subject },
success: function (r) { return r }
})
}
this.set = function (subject, mark) {
$.ajax({
url: 'myurl',
method: 'post',
data: { id: id, class: class, subject: subject, mark: mark },
success: function (r) { return r }
})
}
}
my question is how can I modify my function so that I can create new student as below
var s1 = new Student (22, 4) // to set predefined id & class
but, I want the set and get as below (like jquery set & get)
s1("math") // to get
s1("history", 70) // to set
**
so i think the answer is not possible to work as an object to store attribute id & class and call like a function without function name. thanks for your answer guys.
**

You can check how many arguments the caller has provided. Or check for undefined values.
function test(a, b) {
// both ifs check b was not provided
if (typeof b === "undefined") {
}
if (arguments.length == 1) {
}
}
Your current functions probably won't work because you are returning from a callback. AJAX is (in most cases) asynchronous. So in your case, you have to add another argument for providing a callback.
this.get = function (subject, callback) {
$.ajax({
url: 'myurl',
data: { id: id, class: class, subject: subject },
success: function (r) { callback(r); }
})
}
FYI, class is a reserved keyword by the ECMAScript specification.

function sample(x,y){
id=x;
subjectClass =y;
if(arguments.length == 1){
//getter
}
else{
//setter
}
}
call getter
sample("maths")
call setter
sample("history",70);
Note :
class is a reserved keyword, so please remove it and you can use some other variable name

but, I want the set and get as below
That would mean s1 would be a function, not a Student instance. So your constructor would need to return that.
function student(id, klass) {
// no need to declare variables here that are parameters already
return function(subject, mark) {
var data = {id: id, class: klass, subject: subject},
opt = {url: 'myurl', data: data};
if (arguments.length > 1) { // something was given to `mark`
data.mark = mark;
opt.method = "post";
}
return $.ajax(opt);
};
}
Btw, since you cannot return the response from an ajax call, the function will return the jqXHR promise:
var s1 = student(22, 4); // `new` is unnecessary now
s1("math").then(function(r) { console.log("got maths result:", r); });
s1("history", 70).then(function(r) { console.log("successfully set marks"); });

Related

Sensible approach to callbacks on object prototype methods in JavaScript/jQuery?

Is what I've done below a sensible approach to allow callbacks to run on functions defined in an object's prototype, such that the scope is correct?
I've been wrestling with the correct way to set the value of this when an object's prototype method is the one to run in response to a callback which might originate from an AJAX request or from a click binding or whatever.
Here is a simplified annotated version:
// everything is inside an object which provides the namespace for the app
var namespace = {
// a fairly vanilla object creation routing, which uses the prototype
// approach for defining the functions on the object
newObj : function(params) {
var MyObj = function(params) {
this.property = params.property
};
MyObj.prototype = namespace.ObjPrototype;
return new MyObj(params);
},
// the prototype itself, which defines 2 related functions
ObjPrototype : {
// The first is called to do some form of asynchronous operation
// In this case it is an ajax call
doAsync: function (params) {
$.ajax({
type: "get",
url: params.url,
data: params.data,
dataType: params.datatype,
success: namespace.objClosure(this, "asyncSuccess", ["data"]),
});
// the final line above is the key here - it asks a function (below)
// for a closure around "this", which will in turn run the
// function "asyncSuccess" (defined next) with the argument "data"
},
// This is the actual callback that I want to run. But we can't
// pass this.asyncSuccess to the ajax function above, because the
// scope at execution time is all wrong
asyncSuccess : function(params) {
this.property = params.data;
},
},
// This is the bit I sort of invented, to help me around this problem.
// It returns a function which provides a closure around the object
// and when that returned function is run it inspects the requested
// arguments, and maps them to the values in the JS default
// "arguments" variable to build a parameters object which is then
// passed to the function on the object
objClosure : function(obj, fn, args) {
return function() {
if (args) {
var params = {};
for (var i = 0; i < args.length; i++) {
params[args[i]] = arguments[i];
}
obj[fn](params);
} else {
obj[fn]();
}
}
}
}
Now, obviously the actual target callback MyObj.asyncSuccess needs to know that it's going to get a params object, and what structure it will be, and that knowledge has to be shared by the invoking function MyObj.doAsync, but otherwise this seems to work well.
My question is - am I totally mad? Have I missed something obvious that would solve this problem for me in a simpler/less convoluted way? Am I just too far down the rabbit hole by this stage?
I've read around a lot of questions on SO and they have all addressed part of my question, but I don't seem to have got to the bottom of a generally accepted solution for this. I can't be the only person who's ever wanted to do this :)
Edit
I've accepted the answer below, but you need to read all the comments too for it to come together. Thanks folks for your help!
aren't you over complicating things? see if the below code will help you. i did not completely understand your intent but the below code should help you
function newObj(params) {
function asyncSuccess(params) {
this.property = params.data;
}
function objClosure(obj, fn, args) {
return function() {
if (args) {
var params = {};
for (var i = 0; i < args.length; i++) {
params[args[i]] = arguments[i];
}
obj[fn](params);
} else {
obj[fn]();
}
}
}
this.property = params.property
this.doAsync = function (params) {
console.log('reached async');
$.ajax({
type: "get",
url: params.url,
data: params.data,
dataType: params.datatype,
success: objClosure(this, "asyncSuccess", ["data"]),
});
}
}
var k = new newObj({'property':'xyz'});
k.doAsync();
After seeing the comment from "GameAlchemist" i looked into objClosure function i think we can further improvise by using below code: I am still not sure what the value of this.property or data is to give a proper solution hence just assuming few things
function newObj(params) {
function asyncSuccess(params) {
this.property = params ? params.data : null;
}
function objClosure(args) {
return function() {
if (args) {
var params = {};
for (var i = 0; i < args.length; i++) {
params[args[i]] = arguments[i];
}
asyncSuccess(params);
} else {
asyncSuccess();
}
}
}
this.property = params.property
this.doAsync = function (params) {
console.log('reached async');
$.ajax({
type: "get",
url: params.url,
data: params.data,
dataType: params.datatype,
success: objClosure(["data"]),
});
}
}
Few issues here:
if you are already passing params.data to data i.e. data:params.data how can you again assign the value this.property = params.data? Few things are confusing but i hope the above solution works : )

Scoping of 'this' in TypeScript

I have a very simple class, but already run into pain with the definition of ‘this’ in Typescript:
Typescript
/// <reference path='jquery.d.ts' />
/// <reference path='bootstrap.d.ts' />
module Problem {
export class Index {
detailsUrl: string;
constructor() {
$('.problem-detail-button').click((e) => {
e.preventDefault();
var $row = $(this).closest('tr'); //this must be that of the callback
var problemId: number = $row.data('problem-id');
$.ajax({
url: this.detailsUrl, //this must be the instance of the class
data: { id: problemId },
type: 'POST',
success: (result) => {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
},
})
});
}
}
}
Javascript
var Problem;
(function (Problem) {
var Index = (function () {
function Index() {
var _this = this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var $row = $(_this).closest('tr');
var problemId = $row.data('problem-id');
$.ajax({
url: _this.detailsUrl,
data: {
id: problemId
},
type: 'POST',
success: function (result) {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
}
});
});
}
return Index;
})();
Problem.Index = Index;
})(Problem || (Problem = {}));
Now the problem is that the line
var $row = $(this).closest('tr'); //this must be that of the callback
and this line
this.detailsUrl, //this must be the instance of the class
conflict in the meaning of 'this'
How do you handle the mixture of the 'this'?
module Problem {
export class Index {
detailsUrl: string;
constructor() {
var that = this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var $row = $(this).closest('tr'); //this must be that of the callback
var problemId: number = $row.data('problem-id');
$.ajax({
url: that.detailsUrl, //this must be the instance of the class
data: { id: problemId },
type: 'POST',
success: (result) => {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
},
})
});
}
}
}
Explicitly declare that = this so you have a reference for that.detailsUrl, then
don't use a fat arrow for the click handler, so you get the correct this scope for the callback.
You need to fallback to the standard way of javascript. i.e store the variable as :
var self = this;
Then you can use function instead of ()=> and use this to access variable in callback and self to access the instance of the class.
Here is the complete code sample:
module Problem {
export class Index {
detailsUrl: string;
constructor() {
var self = this;
$('.problem-detail-button').click(function(e){
e.preventDefault();
var $row = $(this).closest('tr'); //this must be that of the callback
var problemId: number = $row.data('problem-id');
$.ajax({
url: self.detailsUrl, //this must be the instance of the class
data: { id: problemId },
type: 'POST',
success: (result) => {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
},
})
});
}
}
}
// Creating
var foo:any = {};
foo.x = 3;
foo.y='123';
var jsonString = JSON.stringify(foo);
alert(jsonString);
// Reading
interface Bar{
x:number;
y?:string;
}
var baz:Bar = JSON.parse(jsonString);
alert(baz.y);
And your generated javascript:
var Problem;
(function (Problem) {
var Index = (function () {
function Index() {
var self = this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var $row = $(this).closest('tr');
var problemId = $row.data('problem-id');
$.ajax({
url: self.detailsUrl,
data: {
id: problemId
},
type: 'POST',
success: function (result) {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
}
});
});
}
return Index;
})();
Problem.Index = Index;
})(Problem || (Problem = {}));
var foo = {
};
foo.x = 3;
foo.y = '123';
var jsonString = JSON.stringify(foo);
alert(jsonString);
var baz = JSON.parse(jsonString);
alert(baz.y);
If you're only supporting browsers that have .addEventListener, I'd suggest using that to associate your data with your elements.
Instead of implementing your code, I'll just give a simple example.
function MyClass(el) {
this.el = el;
this.foo = "bar";
el.addEventListener("click", this, false);
}
MyClass.prototype.handleEvent = function(event) {
this[event.type] && this[event.type](event);
};
MyClass.prototype.click = function(event) {
// Here you have access to the data object
console.log(this.foo); // "bar"
// ...and therefore the element that you stored
console.log(this.el.nodeName); // "DIV"
// ...or you could use `event.currentElement` to get the bound element
};
So this technique gives you an organized coupling between elements and data.
Even if you need to support old IE, you can shim it using .attachEvent().
So then to use it, you just pass the element to the constructor when setting up the data.
new MyClass(document.body);
If all the logic is in your handler(s), you don't even need to keep a reference to the object you created, since the handlers automatically get it via this.
I normally bind this to a variable as soon as I have it in the scope I want.
However the this you are after could be found like this:
constructor() {
var class_this=this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var callback_this=e.target;
Late to the thread, but I have something different to suggestion.
Instead of:
var $row = $(this).closest('tr'); //this must be that of the callback
Consider using:
var $row = $(e.currentTarget).closest('tr');
As in this example, anywhere you might want to use this in a jQuery callback, you have access to a function parameter you can use instead. I would suggest that using these parameters instead of this is cleaner (where "cleaner" is defined as more expressive and less likely to be turned into a bug during future maintenance).
module Problem {
export class Index {
constructor() {
$('.classname').on('click',$.proxy(this.yourfunction,this));
}
private yourfunction(event){
console.log(this);//now this is not dom element but Index
}
}
}
check about jquery.proxy().
just remind you there is another way.

Understanding closure and scope

For some reason (probably because i don't understand closures) function inResult always returns false and the loop is never executed. Of course i'm sure that result contains has the right properties.
function hasId() {return $(this).prop('id');}
function inResult(res) { return res.hasOwnProperty($(this).prop('id'));}
$.ajax({
url : opt.url,
data : $.extend(true, opt.data, {ids: ids}),
context : this, // A collection of elements
type : 'POST',
dataType : 'json',
success : function(result) {
// Filter elements with id and with a property in result named "id"
this.filter(hasId).filter(inResult(result)).each(function() {
console.log($(this).prop('id'));
});
}
});
EDIT: working code solution (thanks to Šime Vidas for poiting me in the right direction):
// Use closures to change the context later
var hasId = function() { return $(this).prop('id'); };
var inResult = function(res) { return res.hasOwnProperty($(this).prop('id')); };
$.ajax({
url : opt.url,
data : $.extend(true, opt.data, {ids: ids}),
context : this, // A collection of elements
type : 'POST',
dataType : 'json',
success : function(result) {
// Filter elements with id and with a property in result named "id"
var filtered = this.filter(function() {
// Note the context switch and result parameter passing
return hasId.call(this) && isBinded.call(this, result);
});
filtered.each(function() { console.log($(this).prop('id')); });
}
});
Try this:
this.filter( hasId ).filter( function () {
return inResult( result );
}).each( function () {
console.log( this.id );
});
In your code you have .filter(inResult(result)) which won't work because you're invoking inResult immediately and passing the result of that invocation (which is a Boolean value) to filter(), which doesn't work with Boolean values.
You could also do it like so:
var keys = Object.keys( result );
var filtered = this.filter( function () {
return this.id && keys.indexOf( this.id ) > -1;
});
Object.keys( result ) returns an array of all own property names from result.

Why has the author of this code used the .call method? Surely he could have just accessed the prototype?

Im looking through some code (unfortunatly the author isnt around anymore) and im wondering why he has used the .call method.
hmlPlaylist.prototype.loadVideos = function () {
var scope = this;
this.config.scriptUrl = '_HMLPlaylistAjax.aspx?' + Math.random();
jQuery.ajax({
type: 'GET',
url: this.config.scriptUrl,
success: function (d, t, x) {
scope.loadVideos_callback.call(scope, d);
},
error: function () {
}
});
};
hmlPlaylist.prototype.loadVideos_callback = function (data) {
var jsonData = '';
var jsonError = false;
try {
jsonData = eval("(" + data + ")");
} catch (jError) {
jsonError = true;
}
if (!jsonError) {
if (jsonData.playlists.length > 0) {
this.buildPlaylistList(jsonData.playlists);
}
if (jsonData.videos.length > 0) {
this.buildVideoList(jsonData.videos);
this.bindVideoNavs();
}
}
else {
// no json returned, don't do anything
}
};
Obviously he seems to have used it to pass a 'this' reference to the loadVideos_callback method but why? The 'loadVideos_callback' method is attached to the prototype of 'hmlplaylist' which is the 'class'. So if you access this inside the 'loadVideos_callback' method you get to the same thing dont you?
yes, I think you are right (I can't see the code in action). You still need the closure around scope, but in this case the use of call is not necessary.
To pull some of the comments into this answer, this is always the context on which the method was invoked. So if a new instance of htmlPlayList was created, and the method invoked on that instance, this would be a reference to that instance.

Call a javascript function from outside its object

I have some simple javascript that as far as i can tell should work but doesn't.
The code is below
var presenter = new Practicum.Web.TEI.StudentPlacement2009.CreateLetter_class(); //this is a class generated by Ajax.Net
function GetLetters() {
var GetLettersParams = new Object();
GetLettersParams.TemplateType = $('#LetterTypes').val();
var letters = ajaxCall(presenter.GetLetters, GetLettersParams);
createOptions('Templates', letters, 'Id', 'Name', true);
}
function ajaxCall(ajaxMethod, parameters) {
var response = ajaxMethod.call(parameters); //fails here with the message in
if (response.error != null) {
alert('An error has occured\r\n' + response.error.Message);
return;
}
return response.value;
}
this is part of the class Ajax.Net produces.
Practicum.Web.TEI.StudentPlacement2009.CreateLetter_class = function() {};
Object.extend(Practicum.Web.TEI.StudentPlacement2009.CreateLetter_class.prototype, Object.extend(new AjaxPro.AjaxClass(), {
GetLetterTypes: function() {
return this.invoke("GetLetterTypes", {}, this.GetLetterTypes.getArguments().slice(0));
},
GetDegrees: function() {
return this.invoke("GetDegrees", {}, this.GetDegrees.getArguments().slice(0));
},
GetLetters: function(getLettersParams) {
return this.invoke("GetLetters", {"getLettersParams":getLettersParams}, this.GetLetters.getArguments().slice(1));
} ...
Any help would be much appriciated;
Colin G
The first parameter that needs to be passed to Function.call() is the object on which the function is called. Then follow the function parameters as separate values:
func.call(someobj, param1, param2, ...);
To call a function with an array of arguments you should use apply(). apply() also takes the object for which the method should be called as first parameter:
func.apply(someobj, params);
So in your case it would look something like this:
function ajaxCall(ajaxMethod, obj, parameters) {
var response = ajaxMethod.call(obj, parameters);
// ...
}
var letters = ajaxCall(presenter.GetLetters, presenter, GetLettersParams);
You need to pass an object to the first argument of the call method e.g.:
ajaxMethod.call(presenter, parameters);
See http://www.webreference.com/js/column26/call.html
Supertux is right. You could try this to make sure the context is set for "call":
function GetLetters() {
var GetLettersParams = new Object();
GetLettersParams.TemplateType = $('#LetterTypes').val();
var letters = ajaxCall(presenter.GetLetters, presenter, GetLettersParams);
createOptions('Templates', letters, 'Id', 'Name', true);
}
function ajaxCall(ajaxMethod, context, parameters) {
var response = ajaxMethod.call(context, parameters); //Call requires a context
if (response.error != null) {
alert('An error has occured\r\n' + response.error.Message);
return;
}
return response.value;
}
Or you could simplify things quite a bit by not using ajaxCall:
function GetLetters() {
var GetLettersParams = {
TemplateType: $('#LetterTypes').val()
},
response = presenter.GetLetters(GetLettersParams);
if (response.error != null) {
alert('An error has occured\r\n' + response.error.Message);
return;
}
createOptions('Templates', response.value, 'Id', 'Name', true);
}

Categories