I have a class I am using for creating CRUD Objects for my site.
It stores the form and table paths for adding, listing, editing and deleting the data, as well as reloading your view with ajax after each edit.
Here is my class definitions:
class CRUDObj{
constructor(containerSel, links) {
this.links = links;
this.containerSel = containerSel;
this.formCallBack = function(){};
}
setActive(obj_id){
$.post(this.links.editURL+'?'+obj_id, {status:"active"}, this.reload);
}
reload(returnData){
this.formCallBack(returnData);
this.formCallBack = function(){};
if($(this.containerSel).length > 0){
ajaxLoad(this.links.listURL, $(this.containerSel));
}
}
}
A basic instance of initializing it:
var contactObj = new CRUDObj('#contacts', {
editURL: '/contact.edit.php',
listURL: '/contacts.list.php',
});
contactObj.formCallBack = function(){
console.log('Custom Reload Callback');
};
The problem appeared when I tried to add the callback, so that I could run a custom function during the refresh.
Running contactObj.setActive(); works properly, and my refresh function is called after the form submits, but when it hits my callback I get:
Uncaught TypeError: this.formCallBack is not a function
Calling it manually contactObj.refresh(); works smoothly.
How can I pass this callback function through better?
The problem is that you're passing method as function, so you loose this context. this will be window object or undefined (if using strict mode):
You need this:
var self = this;
lightboxForm(this.links.editURL+'?'+obj_id, function(x) { self.reload(x) });
or using ES6
lightboxForm(this.links.editURL+'?'+obj_id, x => this.reload(x));
or using bind to return function with given context:
lightboxForm(this.links.editURL+'?'+obj_id, this.reload.bind(this));
Related
I try to change some way to call methods into namespace.
Calling parent methods (I dont think its possible)
Creating and call inheritance function
Calling inside another method (mostly jquery onReady event function) (this.MyFunction() not working)
I split every namespace in files (want to keep it that way)
I try How to call function A from function B within the same namespace? but I didn't succed to split namespaces.
my fiddle sample got only 1 sub-namespace but could be more.
https://jsfiddle.net/forX/kv1w2rvc/
/**************************************************************************
// FILE Master.js
***************************************************************************/
if (!Master) var Master = {};
Master.Print= function(text){
console.log("master.Print :" + text);
$("body").append("<div>master.Print : " + text + "</div>");
}
/**************************************************************************
// FILE Master.Test1.js
***************************************************************************/
if (!Master) var Master = {};
if (!Master.Test1) Master.Test1 = {};
/**************************************************************************
* Descrition :
* Function for managing event load/documentReady
**************************************************************************/
Master.Test1.onReady = function () {
$(function () {
Master.Test1.Function1(); //try to replace because need all namespace.
try {
this.Function2(); //not working
}
catch(err) {
console.log("this.Function2 not working");
$("body").append("<div>this.Function2 not working</div>");
}
try {
this.Print("onReady"); //not working
}
catch(err) {
console.log("this.Print not working");
$("body").append("<div>this.Print not working</div>");
}
try {
Print("onReady"); //not working
}
catch(err) {
console.log("Print not working");
$("body").append("<div>Print not working</div>");
}
});
}
Master.Test1.Function1 = function () {
console.log("Function1");
$("body").append("<div>Function1</div>");
this.Function3(); //working because not inside another function
}
Master.Test1.Function2 = function () {
$("body").append("<div>Function2</div>");
console.log("Function2");
}
Master.Test1.Function3 = function () {
$("body").append("<div>Function3</div>");
console.log("Function3");
Master.Print("Function3"); //try to replace because need all namespace.
}
Master.Test1.onReady();
I use Master.Test1.Function1(); and I want to change that because Function1 is inside the same namespace.
I use Master.Print("Function3"); I dont think I can change that. the way I try to use it, it's more an inheritance function. but I dont know if theres a way to do that?
Maybe I should change the my namespace methode? maybe prototype will do what I want?
You can capture the this in a variable because this inside $(function() {}) will point to document object. The below will work provided you never change the calling context of onReady -- i.e. it is always called on the Test1 object and not called on other context:
Master.Test1.onReady = function () {
var self = this;
$(function () {
self.Function1();
// ..
});
}
To access Print you have to reference using the Master object like: Master.Print() as it won't be available in the Test1 object
this is document within .ready() or jQuery() alias for .ready() where function(){} is parameter $(function() {}). this at this.Function2() will reference document.
"Objects" in javascript are not built the same way as in most object-oriented languages. Essentially, what you are building is a hierarchy of static methods that have no real internal state in-and-of themselves. Therefore, when one of the defined methods is invoked, the context (or state) of that method depends on what object invoked the method.
If you want to have any internal context, you will need to create an "instance" of an "object prototype". At that point, you can use "this.otherFunction" within your other functions. Here is a small example:
var MyObject = function() {};
MyObject.functionOne = function() {
console.log("Function 1");
this.functionTwo();
};
MyObject.functionTwo = function() {
console.log("Function 2");
};
var instanceOne = new MyObject();
instanceOne.functionOne();
You might get some more information about object definition here
I have a module with four functions that call one after the other. I am trying to follow the Revealing Module Pattern. One of the functions is public, the remaining are private. It goes like this:
publicMethod is called from another module
queryNames is called from publicMethod
execute(parameters, callback?, errback?) is called from queryNames
addNamesList is called as the callback? argument of execute
Several dijit/form/CheckBox's are created and the method querySegments is triggered onChange
querySegments needs to call a method of an object created in publicMethod.
The problem is in step 6, I can't reach the object created in step 1.
I have tried to use dojo hitch to define the callback? argument in step 3, but I can't get it to work. I tried putting this in its first argument, but even then I can't reach the required scope to call addNamesList.
Here is some code to demonstrate this issue.
define([
'dojo/dom',
'dijit/form/CheckBox',
'esri/layers/ArcGISDynamicMapServiceLayer',
'esri/tasks/query',
'esri/tasks/QueryTask',
'dojo/_base/lang'
],
function (
dom,
CheckBox,
ArcGISDynamicMapServiceLayer,
Query, QueryTask,
lang
) {
// ***************
// private methods
// ***************
// fetch names and call addNamesList to put the list in place
var queryNames = function (map, mapLayer) {
// new QueryTask(url, options?)
var queryTask = new QueryTask("url")
var query = new Query()
// execute(parameters, callback?, errback?)
// this callback passes an argument called featureSet
queryTask.execute(query, lang.hitch(map, "addNamesList", mapLayer), function(error) {console.log(error)})
}
// callback function of queryNames
var addNamesList = function (mapLayer, featureSet) {
console.log('addOplist')
var namesCount = featureSet.features.length
for (var i = 0; i <namesCount; i++) {
// work
var cbox = new CheckBox({
id: "cbox_" + i,
value: featureSet.features[i].attributes["someID"],
checked: false,
onChange: function (evt) {
querySegments(this.value, mapLayer)
}
})
cbox.placeAt("someDiv" + i, "first")
}
}
// triggered by the checkbox event
var querySegments = function (name, mapLayer) {
// build the query
var queryStatement = "someID = " + name
var layerDefinitions = [queryStatement]
// call a method of mapLayer
mapLayer.setLayerDefinitions(layerDefinitions)
}
// **************
// public methods
// **************
var publicMethod = function (map) {
var mapLayer = new ArcGISDynamicMapServiceLayer('restURL')
map.addLayer(mapServiceLayer)
queryNames(map, mapLayer)
return mapLayer
}
return {
publicMethod: publicMethod
}
}
)
You can see a more detailed explanation and a working example on this other (and more broad) question that I have put on Code Review.
I am new to JavaScript and I guess I still have a lot of issues with scoping, closures and callbacks.
I will deeply appreciate any input, including how to improve this question.
Edit
With this current implementation (with dojo hitch), no error is thrown. The method addNamesList is not called (nor errback, which I also don't understand why). I think this is because addNamesList is not on map's (hitch first argument) namespace. I tried to put this instead, but it makes no difference.
Before I decided to use hitch, the code looked like this:
var queryNames = function (map, mapLayer) {
...
queryTask.execute(query, addNamesList)
}
var addNamesList = function (featureSet) {
...
...
...
querySegments(this.value, mapLayer)
}
but then I couldn't reach mapLayer inside the method triggered by the check box event. It would throw Uncaught ReferenceError: mapLayer is not defined. That is why I tried to use hitch.
Javascript is asynchronous, so pretty much data coming from db, http requests or whatever is returned via callbacks. Here's what happens in your code:
public method calls queryNames
queryNames call addNamesList of map asynchronously and returns nothing
public method takes back control, meanwhile some stuff is going on with the addNamesList
mapLayer is returned untouched while some stuff is still going on in the background
So, to avoid this, you should return data from public method via callback, so you pass callback as the second parameter to the public method, then to the querySegments. Then, in the success callback of query, when you finally get the result ready, you do:
callback(mapLayer);
So, everything you should do is to pass this callback as deep as needed to the place where you have your mapLayer ready (so you've done with it everything you wanted), and then do a callback(mapLayer);.
This and this would probably explain better.
Best regards, Alexander
I am trying to add a functionality to a web page that uses a jquery library which doesn't seem to have any documentation. (unknown origin) my problem is mainly due to the lack of understanding on jquery plugin model and/or inner workings of javascript.
1. the plugin is initiated as follows
jQuery('div.carousel').scrollGallery({
mask: 'div.mask',
slider: 'div.slideset',
slides: 'div.slide', ............ });
2. the plugin is defined in jquery as follows
;(function($){
function ScrollGallery(options) {
this.options = $.extend({
mask: 'div.mask', ...... }, options);
this.init();
3. in the Object.prototype declaration i see the following function numSlide defined.
ScrollGallery.prototype = {
....................
numSlide: function(c) {
if(this.currentStep != c) {
this.currentStep = c;
this.switchSlide();
}
},
.......... };
Question.
How do i reference numSlide(int) function externally?.
I tried the following methods and it did not work.
myx = jQuery('div.carousel').scrollGallery({ // var myx was added in the global scope
myx.numSlide(1); //error undefined is not a function
i tried adding return this; at the end of myx = jQuery('div.carousel').scrollGallery({ but it still returns the jQuery object.
i also tried
jQuery.scrollGallery().numSlide(2); //error undefined is not a function
jQuery.scrollGallery.numSlide(2); //same error
Do i need to add LIGHT BULB
// jquery plugin
$.fn.scrollGallery = function(opt){
return this.each(function(){
$(this).data('ScrollGallery', new ScrollGallery($.extend(opt,{holder:this})));
});
};
}(jQuery));
ANSWER (I think)
it looks like the ScrollGalary object is stored in a data for the selector. So i believe i can do the following jQuery('selector').data('ScrollGallery').numSlide(2);
I decided to post this anyway in-case if anyone in the future had a similar gullible situation.
One way of doing this will be to initiate ScrollGallery object first and then use it.
var test = new ScrollGallery();
test.numSlide();
if you want to extend jQuery and use the function you can assign it as follows
$.fn.scrollGallery = new ScrollGallery();
and use it
$("window").scrollGallery.numSlide();
I am building a mvc aplication using as template HotTowel (you know, durandal, knockout, breeze,etc). The application is not ready yet we are doing good progress :).
At the middle of one funcionality I need to build a dynamic call to javascript function.
The call using hard code values is something like:
<a href="#" id="openreport"
onclick="showReport('#Url.Action("Index","Report", new { Id= 9, languageId = 2})');">Show
report</a>
The call abover works fine. My troubles start when i try using knockoutjs to bind onclick event to string property. something like this:
<a href="#" id="openReport" data-bind="onclick: $root.reportUrl()" >
Show report
where report url it's a obervable variable. here the typescript code:
export var reportUrl =<any> ko.observable();
export var expandRow = function (myObjectComeFromATable) {
var urlAction = '#Url.Action("Index", "Report", new { Id= ID_TO_REPLACE, languageId = LANG_TO_REPLACE }) ';
var url = "showReport('"+urlAction+"');";
reportUrl(url);
};
UPDATE
Use of quotes are fine. the value of the knockout variable is same has hard code value showed before. Maybe it's a aproblem with my sintaxis in the layout?
You are trying to eval code in an observable? I don't think it will work. You could do the eval manually in a function. But I don't think that's a good solution.
If you always call "showReport" on click, you don't need an eval: just have a property in your model that contains the url to call, and run showReport(yourUrl) in a callback function.
If the function you are calling on click changes, you could keep a reference to the function to call in your model. Something like that (but cleaner):
var model = function() {
var _this = this;
this.function1 = function() {
alert('hello');
}
this.function2 = function() {
alert('world');
}
this.run = function() {
_this.current();
}
this.change = function() {
_this.current = _this.function2;
}
this.current = this.function1;
}
ko.applyBindings(new model());
Click
Switch
See here: http://jsfiddle.net/pp2QA/2/
I created a Modal class like this:
export class Modal {
link: Link;
constructor (link: Link) {
this.link = link;
}
create() {
this.link.Modal.$Modal = $.modal({ });
}
addSubmit() {
this.link.Modal.$Form.submit(function () {
var a = this.link;
submitHandler(this.link);
return false;
});
}
}
Here is the code that I am using:
var modal = new Modal(link);
modal.create();
modal.addSubmit();
There is a problem here. I want to pass the link as a parameter to the submitHandler function. However it seems it's not there. Also when I check everthing on the line "var a = this.link" it seems that the variable "a", the "this" and the "link" all show as "any" when I hover over them.
You have discovered JavaScript scope - which can be confusing at first.
JavaScript is functionally scoped. This means that the variables belong to the function they are declared in.
When the variable is declared outside the function, JavaScript attempts to walk up the chain to find the variable.
This is confusing when the context you type the code in is not the context your code is called in. In your case, this in the context of the submit function is the element that is being submitted (a form element for example). It isn't the class where you write the submit function.
Here is an update that should work:
export class Modal {
link: Link;
constructor (link: Link) {
this.link = link;
this.link.Modal.$Modal = $.modal({ });
}
addSubmit() {
var _this = this;
this.link.Modal.$Form.submit(function () {
var a = _this.link;
submitHandler(_this.link);
return false;
});
}
}
In this example we put this into a variable with a name that won't be overridden so the JavaScript runtime won't stop looking until it finds it. (It will stop looking for this straight away as it will have it defined locally as the form element). As there is no local _this JavaScript will walk up the chain until it discovers our variable, which represents the class.
There is also a short-hand in TypeScript to do this, which results in the same JavaScript as we have hand-written above:
export class Modal {
constructor (public link: Link) {
this.link.Modal.$Modal = $.modal({ });
}
addSubmit() {
this.link.Modal.$Form.submit(() => {
var a = this.link;
submitHandler(this.link);
return false;
});
}
}
Lastly, you don't want calling code to have to rely on calling functions in a specific order for things to work. This is an example of exposing the internal workings of your class, so I have updated the usage of the class to be:
var modal = new Modal(link);
modal.addSubmit();
I am assuming that addSubmit is optional - otherwise the Modal class should do that too without the calling code having to know about it.