Javascript: A wrapper class (in ES5) - javascript

Right now I have about 3 seperate javascript "classes". I call them like this;
ajax(parameters).success().error()
getElement('selector').height(400).width(400).remove();
customAlert({object with arguments});
This not only feels like random function calling, but will be likely to give me some naming issues.
Then I thought: How does jQuery do it?
Well, I have no idea. I've tried googling the subject but so far I haven't found any results of how to make this happen. Only results of how to add prototypes and such...
The basic idea of my classes is like this:
var getElement = function(selector){
if(!(this instanceof getElement))
{
return new getElement(selector);
}
//Do element getting
return this;
};
getElement.prototype = {
name: function,
name: function,
//etc.
};
Now, this is kind of working perfectly fine, but I'd like to "prefix" and scope my functions within a wrapper class. I want to call my functions like this:
wrap.getElement(selector);
wrap.ajax(parameters).success().error();
wrap.customAlert({object with arguments});
However, whenever I try it, I bump into at least one kind of error or issue, like;
losing the this scope within my classes
Being unable to add prototype functions to my classes
The entire wrapper class reinstantiating with every function call
being unable to create new object() because the scope isn't right anymore
Also, if at all possible I would like to not re-initialize the wrapper class every time. (seems wildly inefficient, right?)
So I'd want 1 instance of the wrapper class, while the classes within it get their new instances and just do their thing. Is this even possible or am I just dreaming here?
This is the way I've tried it so far;
//It would probably be easier to use an object here, but I want it to
//default to wrap.getElement(selector) when it's just wrap(selector),
//without re-instantiating the old class
var wrap = function(selector){
//Check if docready and such here
}
wrap.prototype.getElement = function(){
// the getElement class here
}
//This is where it starts going wrong. I can't seem to re-add the prototypes
//back to the getElement class this way.
wrap.getElement.prototype = {
name:function,
name:function,
//etc
}
//I can call wrap().getElement(); now, but not the prototypes of
getElement().
//Also, I still have wrap() while I'd want wrap.getElement.
Then I "solved" the issue of having to do wrap() by putting it into a variable first.
var test = new wrap();
test.getElement(selector); // works and only inits once!
//However, I did have to remove the 'new instance' from my getElement()
I have also tried it this way, but this just gave me errors on top of errors of which I didn't really know why;
(function(wrap) {
console.log("init")
this.getElement = function() {
return "test";
};
})(wrap);
// Gives me "wrap is undefined" etc.
And last but not least, I have tried it this way;
var wrap = new function() {
return this;
};
wrap.getElement = function(){};
//This works perfectly fine
wrap.getElement.prototype.css = function(){};
//This will cause "getElement.css is not a function"
So yeah, I'm kind of stuck here. There are many ways to get past this in ES6, I've found. I am however not willing to move to ES6 yet (Because I don't use anything that still needs an interpreter). So it has to be ES5.

The easiest way to wrap your modules in a namespace and keep the classes intact is to use the "Revealing module pattern".
It uses an immediately invoked function expression (IIFE) to set up all the functions with a private scope and then returns just the functions in a new object (which works as a namespace for your module)
var wrap = (function() {
function getElement() {...}
function moreFunctions() {...}
function etcFuncitons() {...}
return {
getElement: getElement,
moreFunctions: moreFunctions,
etcFuncitons: etcFuncitons
};
})();
To answer your second question
I would like to be able to call wrap() by itself as well. I want it to forward automatically to getElement(). Is this hard to do? Is this possible with this construction? Because this looks very easy to maintain and I'd love to keep it like your answer. - Right now it will reply wrap() is not a function
I haven't tested this but you should be able to attach the functions directly to a returned wrapper function. This should avoid the issue of shared this by adding them to the prototype
var wrap = (function() {
function getElement() {...}
function moreFunctions() {...}
function etcFuncitons() {...}
function wrap(selector) {
return new getElement(selector);
}
wrap.getElement = getElement;
wrap.moreFunctions = moreFunctions;
wrap.etcFuncitons = etcFuncitons;
return wrap;
};
})();
This works because everything is an object in javascript, even functions haha

Related

Why would an event listener in a JavaScript class see old context variable?

so I'm having a problem that seems to defy everything I know about how scope is handled in JavaScript with anonymous functions - but it could be something else I'm not thinking about.
I have a JavaScript object, called Element, with a constructor similar to this:
function Element(boxElement) {
var self = this;
// Set jquery instance variables
self.pageElement = null;
self.boxElement = boxElement;
... blah blah blah
// Implement triggers to empty functions
self.onElementClicked = function () {};
// Bind listeners
self._bind_listeners();
}
The bind_listeners method is defined as such
Element.prototype._bind_listeners = function() {
var self = this;
self.boxElement.on('click', function (e) {
// Don't handle if handled already
if (e.isDefaultPrevented()) return;
console.log("Got past the return");
self.onElementClicked();
});
};
And there's also a method to set the callback method onElementClicked:
Element.prototype.on_element_click = function(callback) {
var self = this;
self.onElementClicked = callback;
};
The problem I am encountering is that if I set my callback using the on_element_click method, my method doesn't see the current instance - it sees what the instance would look like just after construction.
More specifically to my situation, there's an instance variable called boxElement that refers to a JQuery element - and in Chrome's console I can see that the instance (self) still does refer to the correct element on the page, but the onElementClicked instance variable (and others) do not seem to be set from within the listener.
Feel free to revise my explanation or ask for clarification.
From the implementer perspective:
If I do this:
// Set default listener for element click
formElement.on_element_click(function () {
console.log("Hello");
});
The listener never says Hello because onElementClicked doesn't appear to be set.
However, if I instead do this:
formElement.boxElement.click(function () {
console.log("Hello");
});
It successfully says "Hello" and makes me confused.
I found the solution to my specific problem, which is a good example of how an error like this can occur. (offtopic: please feel free to add answers for other ways to produce this error - it is a very non-intuitive problem and will always be caused by an external factor)
It turns out the class I was testing with is a class that extends my Element class - BUT, it does so improperly / VERY VERY badly!
As embarrassing as it is to post this, here's the original constructor of my "subclass" (quotes for reasons soon apparent):
function StrikeoutFormElement (formElement) {
var self = this;
// Set reference to form element
self.fe = formElement;
$.extend(self, self.fe);
// Override methods
self.on_reposition(function () {
self._on_reposition();
});
}
I used JQuery's object extending function and a hacky workaround to override something. I have learned the hard way to NEVER use JQuery's extend for OOP, as it is only intended for data manipulation rather than as a language tool.
The new constructor looks like this:
function StrikeoutFormElement (elem) {
var self = this;
}
// Extend the FormElement prototype
StrikeoutFormElement.prototype = Object.create(Element.prototype);
StrikeoutFormElement.prototype.constructor = Element;
This is a method described in an MDN article somewhere. I'll post the source when I find it if someone doesn't beat me to it.
Shoutout to anyone who looked at this obscure problem and attempted to figure it out!

scope Issue seeing object methods

I have tried searching through a lot of S.O. pages but nothing has touched EXACTLY on this top while also NOT USING JQUERY.... I am trying to stick to pure JavaScript as I want to learn it 115% before advancing my current knowledge of JQuery.
I have an object called ScreenResizeTool like this...
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
listen(currImg);
}, true);
}
and a method like this...
ScreenResizeTool.prototype.listen = function(currImg) {
//Random Code For Resizing
};
My trouble is probably obvious to an experienced JavaScript user but I am having trouble not making this into a messy dirty awful OOP set. I have done various tests to show and prove to myself that the this inside the addEventHandler changes when it becomes bound to the window. This much I assumed before testing but I was able to see that once window.resize event happens the listen method is gone and not a part of the global window variable....
I have also tried adding a this capture such as this.me = this inside the object constructor however it also couldn't see the me variable once it ran. Once the window took the function over it no longer knew anything about the me variable or any reference to my class methods....
I am aware that I could separate this differently but my goal here is to learn how to fully encapsulate and use as many clean OOP structures as possible as I just came from the .NET world and I need it in my life.
I am also aware that I could make messy calls and or store this object or access to the methods inside the window variable but that seems outright wrong to me. I should be able to fully encapsulate this object and have its events and methods all implemented in this class structure.
I also know that the currImg variable is not going to be seen either but lets start small here. I assume once I figure out my incorrect train of thought on scope for JavaScript I should be fine to figure out the currImg problem.
I know there's 1000 JavaScript programmers out there waiting to rip me a new one over asking this simple question but I gotta know...
Thoughts anyone?
this inside a function bound to a DOM Object (like window) will always refer to that object.
this inside a constructor function will always refer to the prototype.
A common practice to circumvent the this issue, as you mentioned, is to cache it in a variable, often called self. Now you want the variables and properties of your object available after instantiation, so what you need is the return keyword, more specifically to return the parent object itself. Let's put that together:
function ScreenResizeTool() {
var self = this;
// method to instantiate the code is often stored in init property
this.init = function() {
window.addEventListener('resize', function() {
self.listen(); // self will refer to the prototype, not the window!
}, true);
};
return this;
}
ScreenResizeTool.prototype.listen = function() { // Dummy function
var h = window.innerHeight, w = window.innerWidth;
console.log('Resized to ' + w + ' x ' + h + '!');
};
Pretty easy huh? So we have our prototype now, but prototypes can't do anything if there's not an instance. So we create an instance of ScreenResizeTool and instantiate it with its init method:
var tool = new ScreenResizeTool();
tool.init();
// every time you resize the window now, a result will be logged!
You could also simply store the listen & init methods as private functions inside your constructor, and return them in an anonymous object:
function ScreenResizeTool() {
var listen = function() { ... };
var init = function() { ... };
// in this.init you can now simply call listen() instead of this.listen()
return {
listen: listen,
init: init
}
}
Check out the fiddle and make sure to open your console. Note that in this case I'd rather use the first function than the second (it does exactly the same) because prototypes are only useful if you have multiple instances or subclasses
The whole concept of this in JavaScript is a nightmare for beginners and in my code I usually try to avoid it as it gets confusing fast and makes code unreadable (IMHO). Also, many people new to JavaScript but experienced in object-oriented programming languages try to get into the whole this and prototype stuff directly though the don't actually need to (google JS patterns like IIFE for example as alternatives).
So looking at your original code:
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
listen(currImg); // global function listen?
}, true);
}
ScreenResizeTool.prototype.listen = function(currImg) {
//Random Code For Resizing
};
First off, you probably mean addEventListener instead. In its callback you refer to listen but as a global variable which would look for it as window.listen - which doesn't exit. So you could think to do this:
function ScreenResizeTool(currImg) {
window.addEventHandler('resize', function() {
this.listen(currImg); // what's this?
}, true);
}
As you want to use the prototype.listen function of ScreenResizeTool. But this won't work either as the event listener's callback function is called with a different this and not the this that is your function scope.
This is where something comes in which makes most programmers cringe, you have to cache this, examples from code I've seen:
var _this = this;
var that = this;
var _self = this;
Let's just use the latter to be able to refer to the function within the event callback:
function ScreenResizeTool(currImg) {
var _self = this;
window.addEventListener('resize', function() {
_self.listen();
}, true);
}
Now this will actually work and do what you want to achieve: invoke the prototype.listen function of ScreenResizeTool.
See this JSFiddle for a working example: http://jsfiddle.net/KNw6R/ (check the console for output)
As a last word, this problem did not have anything to do with using jQuery or not. It's a general problem of JS. And especially when having to deal with different browser implementations you should be using jQuery (or another such library) to make your own code clean and neat and not fiddle around with multiple if statements to find out what feature is supported in what way.

Inheritance and problems with the "this" keyword

I'm building a fairly complex web app that begins with a main menu where the user makes his initial selections. This is the first time I've tried a true OOP approach using inheritance in JavaScript and I've run into my first problem with the "this" keyword not referring to what I expect it to. I'm guessing that it's the result of a broader problem with my OOP/inheritance approach, so I would appreciate an answer that not only tells me how to solve this individual issue, but also provides deeper feedback and advice on my general approach.
I'm only going to post the JS code because I don't think the HTML is relevant, but I can certainly post that as well if necessary.
The following code defines the main class Select. It then creates a subclass of Select called SelectNum (look towards the end of the code). In SelectNum, I'm trying to override the mouseover method of Select, but not entirely -- I want to first call the super's (Select's) method, and then run some additional code. But when this subclass's mouseover method runs, I immediately get the following error:
"Uncaught TypeError: Cannot call method 'stop' of undefined"
Basically, this.shine is undefined.
To start with, I'm using the following code from O'Reilly's JavaScript: The Definitive Guide:
function inherit(p) {
if (Object.create){ // If Object.create() is defined...
return Object.create(p); // then just use it.
}
function f() {}; // Define a dummy constructor function.
f.prototype = p; // Set its prototype property to p.
return new f(); // Use f() to create an "heir" of p.
}
And my code:
Select = function(el){
return this.init(el);
}
Select.prototype = {
init: function(el){
var that = this;
this.el = el;
this.shine = el.children('.selectShine');
el.hover(function(){
that.mouseover();
},function(){
that.mouseout();
});
return this;
},
mouseover: function(){
this.shine.stop().animate({opacity:.35},200);
},
mouseout: function(){
var that = this;
this.shine.stop().animate({opacity:.25},200);
}
}
//Sub-classes
SelectNum = function(el){
this.init(el);
this.sup = inherit(Select.prototype); //allows access to super's original methods even when overwritten in this subclass
return this;
}
SelectNum.prototype = inherit(Select.prototype);
SelectNum.prototype.mouseover = function(){
this.sup.mouseover(); //call super's method... but this breaks down
//do some other stuff
}
EDIT
The response from Raynos worked. this.sup.mouseover() no longer threw the error, and the correct code was run. However, I actually need to create a SelectNum subclass called SelectLevel. Unlike SelectNum that overrides its superclass' mouseover() method, SelectLevel does NOT need to override SelectNum's mouseover() method:
SelectLevel = function(el){
this.init(el);
this.sup = inherit(SelectNum.prototype); //allows access to super's original methods even when overwritten in this subclass
for(var k in this.sup){
this.sup[k] = this.sup[k].bind(this);
}
}
SelectLevel.prototype = inherit(SelectNum.prototype);
With this code, the mouseover() method simply gets called continuously. I believe that's because this is now bound to the SelectLevel object, so this.sup in the line this.sup.mouseover() in SelectNum always refers to SelectNum, so it just keeps calling itself.
If I remove the this.sup[k] = this.sup[k].bind(this); binding in SelectLevel, then I get the error Uncaught TypeError: Cannot call method 'mouseover' of undefined. It appears that this.sup.mouseover() gets called continuously, calling the mouseover method on each object in the prototype chain. When it gets up to Object, that's when this error gets thrown because, of course, Object doesn't have a sup property.
It seems like I can solve this by removing the this.sup[k] = this.sup[k].bind(this); binding in SelectLevel, and then wrapping the this.sup.mouseover() in an if statement that checks first for the sup property before calling the mouseover() method on it: i.e. if(this.sup !== undefined), but this really just doesn't feel right.
Ultimately, I think I'm missing something fundamental about how to subclass in JavaScript. While solutions to these particular issues do shed some light on how prototypal inheritance works in JS, I really think I need a better understanding on a broader level.
this.sup.mouseover();
calls the .mouseover method on the object this.sup. What you want is
this.sup.mouseover.call(this)
You don't want to call it on this.sup you want to call it on this.
If that's a pain in the ass then you can do the following in your constructor
this.sup = inherit(Select.prototype);
for (var k in this.sup) {
if (typeof this.sup[k] === "function") {
this.sup[k] = this.sup[k].bind(this);
}
}
That basically means override every method with the same function but hard bind the value of this to what you expect/want.

Javascript Prototype function and SVG setAttribute(onclick)

Trying to use an svg onClick to call a prototype function.
Usually to call a prototype function I would just do this.(functionName) but when I put it into the .setAttribute(onclick, "this.(functionName)") it does not recognise the prototype function. Has anyone had any experience in this?
In case the above wasn't clear heres the basic jist of it...
function myobject(svgShape) {
this.svgshape.setAttribute(onclick, 'this.doSomething()');
}
myobject.prototype.doSomething = function() {
alert("works");
}
Three things that may help:
1) First off, I think you're missing this line from the top of your myobject function:
this.svgshape = svgshape;
I'm assuming that was just an error posting the question and have inserted that below.
2) Normally when you're using Prototype (or any modern library), you don't use strings for callbacks, you use functions. Also, you normally assign handlers using the library's wrapper for addEventListener / attachEvent (observe, in Prototype's case) rather than the old DOM0 attribute thing. So:
function myobject(svgShape) {
this.svgshape = svgshape;
$(this.svgshape).observe('click', this.doSomething); // STILL WRONG, see below
}
myobject.prototype.doSomething = function() {
alert("works");
}
3) But JavaScript doesn't have methods (it doesn't really need them), it just has functions, so the above won't ensure that this (the context of the call) is set correctly. With Prototype you'd use bind to set the context:
function myobject(svgShape) {
this.svgshape = svgshape;
$(this.svgshape).observe('click', this.doSomething.bind(this));
}
myobject.prototype.doSomething = function() {
alert("works");
}
(Or you can use your own closure to do it. The advantage of bind is that the closure is in a very well-controlled environment and so doesn't close over things you don't want kept around.)
Now, I've never done any SVG programming with Prototype, so if observe doesn't work for some reason, you might try directly assigning to the onclick reflected property:
function myobject(svgShape) {
this.svgshape = svgshape;
this.svgshape.onclick = this.doSomething.bind(this);
}
myobject.prototype.doSomething = function() {
alert("works");
}
I'm still using bind there so that this has the correct value.
These posts from my anemic little blog offer more discussion of the above:
Mythical methods
You must remember this
Closures are not complicated

Using jQuery, how do I add elements to a jQuery object that's yet to be created?

Perhaps I am doing this wrong and suggestions on how to improve my code are appreciated. My situation is this: I have a toolbar with different elements that are populated by a callback. I do not want to use the show() or hide() commands, I prefer to use detach, but I think there should be a nice way to deal with it. Here's my code:
entryView = function _entryView() {
var menuButton = $('<div/>').addClass('menuButton');
toolBar();
$.getJSON('ajax', function(o) {
var $enum2 = ss.IEnumerator.getEnumerator(dto.TransmittalDates);
while ($enum2.moveNext()) {
var dateTime = $enum2.get_current();
$('.menu').append($('<div/>').addClass('menuitem').text(dateTime.toString()));
}
});
}
toolBar = function _toolBar() {
var flyoutMenu = $('<div/>').addClass('menu');
$('.menuButton').click(function(o) {
$('.menubutton').append(flyoutMenu);
});
I did a quick cut and paste and renamed the variables to make them make sense. As you can see on the entry I build the toolbar and the very last thing I do is the ajax call. The menu, however, is not created until the "click" event, so appending is not possible.
I realize that having global variables is bad, so I'm trying to avoid that, but I think the best situation would have the ajax call populate a Menu variable and when the DOM is created, to pull from that same Menu item. How do I pull this off? Is there a better way to do it?
Edit: Fubbed a bit on the toolbar function, I think I have it should be correct now.
I'm confused by some parts of your code:
What's the entryView function and when is it called?
Why does the toolBar function exist as opposed to being inline? From where else is it called?
Why are you creating functions like that? Creating a variable without var is bad practice, always makes global variables, and will be forbidden in ES5 strict mode. You should create functions like this:
var someFunction = function(arg1, arg2) { … };
or like this:
function someFunction(arg1, arg2) { … }
Why are you giving each function a second name (e.g. _toolBar)? The "private" name will only be in scope inside the function.
The menu doesn't have to be in global scope, just in a scope that's common to both functions.
I would refactor it like this (knowing very little about the design of your application), inlining toolBar:
function entryView() {
var menuButton = $('<div/>' {'class': 'menuButton'}), menu = $('<div/>', {'class': 'menu'});
$.getJSON('ajax', function(o) {
var $enum2 = ss.IEnumerator.getEnumerator(dto.TransmittalDates);
while ($enum2.moveNext()) {
var dateTime = $enum2.get_current();
$('.menu').append($('<div/>' {'class': 'menuItem'}).text(dateTime.toString()));
}
});
menuButton.click(function(o) {
menuButton.append(menu);
});
}

Categories

Resources