Expose function and return object - javascript

I am trying to create an ajaxHelper module that should be able to expose some number of functions, and when they are called, should return a helper object that has either the data retrieved from an AJAX call, or errors associated with that AJAX call.
Here is something along the lines of what I am thinking:
define("helpers-ajaxDataRetriever", ["jquery"], function ($) {
var helper = {};
helper.getIndexData = function() {
fnIndexData();
return helper;
}
var fnIndexData = function () {
$.ajax({
url: nwatchBaseUrl + '/api/HomeApi/NodeSummary'
}).success(function (returnedData) {
helper.success = true;
helper.data = returnedData;
}).fail(function (jqXHR, textStatus) {
helper.success = false;
helper.error.jqXHR = jqXHR;
helper.error.textStatus = textStatus;
});
}
});
I then want other modules that import this ajaxHelper to be able to call the function (such as getIndexData), which would ultimately populate the helper object, and then be able to reference the various properties, such as the boolean success, data, or error objects.
How do I go about doing this?

For it to work the way you are expecting, the module has to return the properties that you like to expose to the outside world ( for it to be used by other modules).
And since ajax is asynchronous, it is better you tackle such scenarios with callbacks instead accessing the variables directly. As you do not know when the ajax call will successfully complete and return you the data.
define("helpers-ajaxDataRetriever", ["jquery"], function($) {
var helper = {};
// you will pass in the options
// which will contains the success and error
// callbacks, along with additional props
// that you wanna pass in and use
helper.getIndexData = function(options) {
fnIndexData(options);
}
var fnIndexData = function(options) {
$.ajax({
url: options.nwatchBaseUrl + '/api/HomeApi/NodeSummary'
}).success(function(returnedData) {
options.success && options.success.apply(null, arguments);
}).fail(function(jqXHR, textStatus) {
options.error && options.error.apply(null, arguments);
});
}
// You return the object, which are the public methods
// or properties which you wanna expose when this module is used
return {
getIndexData: getIndexData
}
});
// This is when you wanna use the above exposed function
// in any module
define("use-ajax", ["helpers-ajaxDataRetriever"], function(customAjax) {
var options = {
success: function(data) {
console.log('success');
// use the data
}, error: function(jqXHR, textStatus) {
console.log('failure');
// you will have access to the
// arguments of the error function here
},
nwatchBaseUrl: 'https://google.com/'
}
customAjax.getIndexData(options);
});
And since we only want to expose getIndexData in the above example, we can completely get rid of the helper namespace and just return the function definition.
You could also achieve the save by using the concept of promise

Related

How can we make a JavaScript property global?

Please refer the below example code
var report = {
chartTypes : null,
init: function () {
this.getChartTypes(function(data){
this.chartTypes = data;
});
},
getChartTypes: function(callback) {
$.ajax({
data:'',
url:'',
success:function(response){
chartTypes = JSON.parse(response);
callback(chartTypes);
}
});
},
getToolbar:function() {
this.chartTypes --------------- NULL
}
}
getChartTypes function load different chart types via AJAX. Therefore i put it as a callback function. Data is received successfully. But when i use this.chartTypes in a different function like getToolbar it says this.chartTypes is null. Even i have initialized the same in the starting. May be scope issue. Please advise.
You are assigning to a variable (probably global) called chartTypes, but that isn't the same as reoprt.chartTypes. You need to assign to this.chartTypes, but this in your anonymous function won't be the same as this outside it, so you need to remember that value using another variable:
getChartTypes: function(callback) {
var self = this;
$.ajax({
data:'',
url:'',
success:function(response){
callback( self.chartTypes = JSON.parse(response) );
}
});
}
With an OOP approach, most developers would use a method and use .bind() to maintain the proper scope when the asynchronous success method is triggered. This way you do not have to worry about closures and using variables to hold the scope of this.
var report = {
chartTypes : null,
init: function () {
this.getChartTypes();
},
getChartTypes : function(callback) {
$.ajax({
data:'',
url:''
}).done(this._setSuccessResponse.bind(this));
},
_setSuccessResponse : function(data){
this.chartTypes = data;
},
getToolbar : function() {
console.log(this.chartTypes);
}
}
You also need to make sure that when you call getToolbar that the Ajax call has also been completed.

Using parent object's properties inside jQuery function and other methods

First of all: I don't know exactly how to call everyting since I am quite new to the more OOP way of writing javascript, so I'll try to explain everything as good as possible.
My problem is that I want to access properties inside an object ( so I can use the this-keyword. This works fine as long as I am in the scope of the object. When I go outside the scope, I would like to access those properties while I can't use the this-keyword anymore.
My code:
var Octa = Octa || function () {
this._initialize();
};
Octa.prototype = {
string: 'Foo',
_initialize: function () {
console.log(this.string); //Output: "Foo"
this.othermethod();
}
}
var Octa = new Octa();
But when I have a method within an Octa method, so outside the scope where I can't use this anymore to get Octa's properties, I can't reach the properties within Octa.
For example:
othermethod: function () {
$.ajax({
url: this.globalUrl + 'content/language/lang.' + l + '.php',
data: {
ajax: true
},
dataType: 'json',
success: function (response) {
Octa.lang = response;
}
});
console.log(JSON.stringify(this.lang)); //Output: null, which means Octa.lang wasn't reachable in the ajax success event (the ajax request was successful).
}
Is there a way to reach the scope of Octa within other objects? Or within jQuery callbacks since the same problem occurs there.
I hope my problem is understandable and if not, I'll try to give more clarification.
Simply refer back to this inside the function scope:
...,
someMethod: function () {
var self = this,
ajaxOptions = this.settings.ajaxOptions;
// note we can still refer to 'this' at this level
$.ajax(ajaxOptions).done(this.ajaxDone).fail(this.ajaxFail);
// the function scope changes for the deffered handlers so you can access by reference of 'this' => self
$.ajax(ajaxOptions).done(function(data, status, xhr){
self.ajaxDone(data, status, xhr)
}).fail(function(xhr, status, error){
self.ajaxFail(xhr, status, error);
});
},
ajaxDone: function(data, status, xhr) {},
ajaxFail: function(xhr, status, error) {},
...
Hope this makes sense.
Now there's also a .bind() function that can be used to bind function scope to a parameter:
$.ajax(ajaxOptions).done(function(){
this.ajaxDone();
}.bind(this));
You'll have to use a polyfill to support older browsers. It's much more easier to use var self imho.

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();
}());

knockoutjs save multiple viewmodels from one function?

I have spent the last few days researching the following KnockoutJS issue.
I have a page with 3 viewmodels on. I am using div's with id's to specify the bindings i.e:
ko.applyBindings(new viewModel(datasource), $("#sectionQualifications")[0]);
I am also using RequireJS which has really helped with making my app modular and works well with KnockoutJS of course.
My question relates to having (as mentioned) 3 viewmodels on my page.. none overlap, but each viewmodel has a SAVE function. So a quick look at one of the viewmodel snippets:
function viewModel(data) {
self = this;
self.quals = ko.observableArray(data),
self.addQual = function () {
self.quals.push(new qualification());
},
self.remove = function (item) {
// Remove from the database IF we have an actual record in our viewmodel
if (item.Id !== 0) {
dataservice_qualifications.deleteEntity(item.Id, ko.toJSON(item),
{
success: function (ret) {
common.notifyOK('Qualification removed');
},
error: function (err) {
common.notifyError('Cannot remove that qualification');
console.log('Qualification Remove Error', err);
console.log('Remove error object', this.Id);
}
}
);
}
// Remove from the actual view model
self.quals.remove(item);
}
// Save and move on.. we need to iterate through the qualifications, update any existing rows (ID NOT 0) or
// add new entries (ID IS 0)
self.save = function () {
var saveData = ko.toJS(this.quals);
for (var i in saveData) {
// New qualification entry
if (saveData[i].Id === 0) { // New qualification entry
dataservice_qualifications.postEntity(ko.toJSON(saveData[i]),
{
success: function (ret) {
},
error: function (error) {
common.notifyError('Cannot add qualification ' + saveData[i].qualificationName);
console.log('Qualification add error', error);
}
}
);
} // eof NEW qualification
if (saveData[i].Id > 0) {
dataservice_qualifications.putEntity(saveData[i].Id, ko.toJSON(saveData[i]),
{
success: function (ret) {
},
error: function (error) {
common.notifyError('Cannot update qualification ' + saveData[i].qualificationName);
console.log('UPDATED: ERROR:', error);
}
}
);
} // eof UPDATED qualification
} // eof saveData loop
common.notifyOK('Qualifications updated');
} // eof savenext function
return;
};
So from that above sample, I would have 2 other viewmodels that are similar, which have the SAVE function as above. So of course I want to use say jQuery to click a button and save all 3 viewmodels (i.e. via that SAVE function on each).
Because I am using RequireJS, I have tried exposing a "public" function that in turn tries to internally call the viewModel.save() function as follows in this snippet:
function saveModel() {
viewModel.save();
}
// PUBLIC INTERFACE
return {
saveModel: saveModel,
loadViewModel: koMapData
}
So the theory being I can call the "saveModel" function from wherever which should trigger the save of the viewmodels?
Any help really appreciated. By the way I have gone down the path of trying to create the viewmodel like:
var viewModel = {
save: function() {
blah blah...
}
}
however no real luck in that either? I am sure I am missing something simple, because I would think you could/should be able to trigger a function from a viewmodel externally somehow?
EDIT
Just for reference, the models DO NOT overlap..
Thanks in advance,
David.
You can merge view models in an object like this:
var mainVModel = {
vModel1: { // has save method},
vModel2: { // has save method},
saveAll : function(){
mainVModel.vModel1.save();
mainVModel.vModel2.save();
}
}
ko.applyBindings(new mainVModel());
Actually thanks #XGreen for your suggestion, and I can see that working well, however I am using requirejs, and so my app structure isn't quite a match.
However, I have been successful in the following solution:
First, I created the viewmodel in a slightly different way..
var viewModel = {
quals: ko.observableArray([]),
addQual: function () {
viewModel.quals.push(new qualification());
},
remove: function (item) {
// Do the remove bit..
},
save: function () {
var saveData = ko.toJS(viewModel.quals);
// Do the save stuff..
} // eof savenext function
}; // eof viewModel def
So the viewmodel is defined, and then I had a helper function to access just the SAVE function of the viewmodel:
// Private: Allows external access to save the viewmodel
var viewModelFunctions = {
saveModel: function () {
viewModel.save();
}
}
.. and then finally, because I am using the revealing module pattern within the requirejs structure, I created a public interface function as follows:
function koMapData(incomingData) {
datasource = (incomingData === null) ? [new qualification()] : incomingData;
viewModel.quals = ko.observableArray(ko.toJS(datasource));
ko.applyBindings(viewModel, $("#sectionQualifications")[0]);
}
// PUBLIC INTERFACE
return {
viewModelFunctions: viewModelFunctions,
loadViewModel: koMapData
}
So you can see the last part with the viewModelFunctions, which means in another module (or wherever) I can reference/trigger the SAVE function remotely by:
mymodule.viewModelFunctions.saveModel()
Which also means (in my case because I have 3 viewmodels I need to trigger save for from one event) I can have the flexibility of saving when/how I want to. Obviously it would be good to return any errors too etc up to the calling module, but that is in principle how it works for me.

Creating custom JavaScript object from data returned by jQuery AJAX request

I want to create a custom javascript object which contains data returned from a jQuery AJAX request, but I don't know which is the right way to do it. I was thinking maybe one way could be to include the AJAX request inside the constructor function so the object is created like this:
// Constructor function
function CustomObject(dataUrl) {
var that = this;
$.ajax(dataUrl, {
success: function (json) {
that.data = $.parseJSON(json);
}
});
}
// Creating new custom object
var myObject = new CustomObject('http://.....');
Another way may be to use a function which does the AJAX and then returns the new object based on the data from the AJAX response.
function customObject(dataUrl) {
// Constructor function
function CustomObject(data) {
this.data = data;
}
$.ajax(dataUrl, {
success: function (json) {
var data = $.parseJSON(json);
return new CustomObject(data);
}
});
}
// Creating new custom object
var myObject = customObject('http://.....')
I would like to know what is the best practice when doing something like this, as well as advantages/disadvatages of different methods. Maybe you can point me to some article or example on something similiar to what I am trying to do.
Thanks in advance.
I think this would be a better approach, it makes your CustomObject only knowledgeable about the data it contains. Here you delegate the work of creating objects to a factory, and pass in a callback to get a reference to the created object, since ajax is asynchronous. If you don't mind making it synchronous, then the createCustomObject function can just return the instance, and the callback can be removed.
function CustomObject(data) {
this.data = data;
}
var factory = (function(){
function create(dataUrl, objectClass, callback){
$.ajax({
url: dataUrl,
success: function (data) {
callback(new objectClass(data));
}
});
}
return{
createCustomObject: function(dataUrl, callback){
create(dataUrl, CustomObject, callback);
}
};
})();
// Creating new custom object
var myObject = null;
factory.createCustomObject('http://..', function(customObject){
myObject = customObject;
});
I'd argue that the second method is better because then you only create a new CustomObject once the script is actually fully prepared to do so (i.e. it has the data it needs from the AJAX request).

Categories

Resources