JavaScript OOP: use private member jQuery again or reselect - javascript

Let's look on this JavaScript code:
Person: function() {
var element = null;
this.init = function() {
// select an HTML element created+ added to DOM in init()
element = $("#justCreatedElement");
};
this.workingFunction = function() {
// use private member `element`
element;
// create new variable and select again with jquery
var element = $("#justCreatedElement");
}
}
We can assume that the init() method gets called before any other method.
I have the following Question:
1 In the init() I use JQuery to assign a HTML element to the private member element. Because element is visible for all methods I can just use it again in the method workingFunction2(). What is better coding standard:
I Using element private member in all member mehtods and even maybe write it in any member methof.
II Using element private member only to read in all member methods, except once, where it is set for the first time, here in init(). And if private member element must be changed use a mehtod called changeELement() but do not write it in a method where the user cannot know that it get written to.
III General avoid to use a member variable that holds JQuery selections and always select again in every mehtod
Or can somebody give me his own standards regarding this problem.

Related

Can I find an object that owns an HTML element in Javascript?

Let's say I have some code like this:
let myList = new map();
let something = new objectMaker();
object.set(someIndex,new objectMaker());
function objectMaker(){
this.parentThing=new createElement("div");
this.childThing=new createElement("div");
this.parentThing.onclick=clickyFunction();
//See below for details of function call
this.parentThing.id=someIndex+"something";
}
Now, the problem is finding the object which owns the element when I call "clickyFunction()." My current solution looks something like: this.parentThing.onclick=function(){clickElement(event,this);}; If someone clicks childThing e.srcElement will reference childThing and the this parameter will refer to this.parentThing.
Thus, then I have to run a loop
function clickyFunction(e,callingObject) {
for (let key of myList.keys()) {
if (myList.get(key).parentThing.id==callingObject.id){
//This is the parent
...
Is there a way to just pass the instance of objectMaker to clickyFunction() (regardless of which)? It seems like this would be a more elegant solution. Or, should I make some overall change to how the program is organized?
You can use the 'bind' method to create a new function with the desired 'this' value and pass the instance of objectMaker to the clickyFunction.
For example, you can modify the line where you set the onclick handler like this:
this.parentThing.onclick = clickyFunction.bind(this);
By doing this, you're creating a new function that will call clickyFunction with the current instance of objectMaker set as the this value. Then, when you use the this keyword in clickyFunction, you can access the instance of objectMaker directly:
function clickyFunction(e) {
// `this` refers to the instance of objectMaker
...
}
As an alternative, you could pass the instance of objectMaker to the clickyFunction as an argument, like this::
this.parentThing.onclick = function(e) {
clickyFunction(e, this);
}
function clickyFunction(e, objectMakerInstance) {
// `objectMakerInstance` refers to the instance of objectMaker
...
}
Either way, you can skip the loop and access the desired instance of objectMaker directly.
*EDIT:
You can find the object that owns an HTML element in JavaScript using the ownerDocument property of the element. This'll give you the Document object that owns the element. Then, you can use the defaultView property of the document to get the Window object that owns the document, like:
let element = document.getElementById('my-element');
let ownerWindow = element.ownerDocument.defaultView;
Alternatively, you can use the parentNode property to navigate up the DOM tree and find the nearest ancestor element that has an ownerDocument property.
You can do this like this:
let element = document.getElementById('my-element');
let ancestor = element;
while (ancestor && !ancestor.ownerDocument) {
ancestor = ancestor.parentNode;
}
let ownerWindow = ancestor.ownerDocument.defaultView;
This can be handy if the element isn't directly owned by a document, but is instead owned by an element that is itself owned by a document, like a template element or a svg element.

JavaScript Class with same name for property and method

When researching a problem I ran into when trying to remove an event listener that uses bind() similar to the syntax in use by the class constructor below (I am not trying to remove this one, but it would not be successful if I tried), I found two sources that show a solution to that similar to that in use by the onDown method below (I do want this one removed once no longer needed) that creates a separte function that does the binding and the addEventListener attaches the function by its name:
class TestClass {
constructor(el) {
el.addEventListener('mousedown', function () { this.onDown(el.id) }.bind(this));
}
onDown(id) {
// Here a property named onUp is created:
this.onUp = this.onUp.bind(this, id);
document.addEventListener('mouseup', this.onUp);
}
// Here a method named onUp is defined in the class:
onUp(id) {
document.removeEventListener('mouseup', this.onUp);
console.log("Mouse down on " + id + " and then mouse up.");
}
}
const d1 = new TestClass(document.getElementById("div1"));
const d2 = new TestClass(document.getElementById("div2"));
<div id="div1">Div 1: Click me and let go!</div>
<div id="div2">Div 2: Click me and let go!</div>
This code works and achieves my desired results, but what I observed is this creates a both property and a method with the same name, named onUp, in the objects created. This can also be verified by looking at the properties of the objects created (see image below). This is not otherwise possible as confirmed by the error received when calling the method that uses the same name as a property here:
class Test {
sameName = "property";
sameName() { return "method"; }
}
const testObject = new Test();
console.log(testObject.sameName); // Here Console outputs the value of the sameName property
console.log(testObject.sameName()); // This fails calling the sameName() method
Can someone help explain to me how this is working in the first example where an object has a property and method both with the same name? Is this a problem?
Note, I have alternate ways of doing this too. I can otherwise simply give one of them a different name:
onDown(el) {
this._onUp = this.onUp.bind(this, el);
document.addEventListener('mouseup', this._onUp);
}
onUp(el) {
document.removeEventListener('mouseup', this._onUp);
console.log("Mouse down on " + el.id + " and then mouse up.");
}
Or, the { once: true } option works for the example, but not in the actual code I am writing that uses different events (this is just a simplified example):
onDown(el) {
document.addEventListener('mouseup', function () { this.onUp(el) }.bind(this), { once: true });
}
onUp(el) {
console.log("Mouse down on " + el.id + " and then mouse up.");
}
Adding an image that shows the output properties of one of the TestClass objects. It first lists the property values. Since my example only had 1 property, I added another (named anotherProperty) to show the collection of properties, for control purposes. And under Prototype, it lists all the methods in the object. Both sections have an entry named onUp.
And here is another image if the output of the other Test object I built expecting to fail. In here you can see it is built with the same design; it has a property named sameName (which can be referenced) and it has a method under Prototype also names sameName (which fails when calling).
This failure is as expected. But again, in my first example, there is both a property and a method, both with the same name, onUp, and the method does function.
A method is just a property with a function for its value.
A property is a named “thing” attached to an object.
When you create an instance of a class in JavaScript (i.e. call a function with the new keyword) it creates an object with a prototype which is a reference to the class.
When you try to access a property on an object, it first checks the object itself to see if it has such a property. If it can’t find it, it looks at the prototype and checks that object for a property with that name. Then it checks the prototype of that object and so on until it runs out of objects on the prototype chain.
This is how inheritance works in JavaScript.
———
So when you call new TestClass you create an object which has a prototype which has an onUp method.
When the mousedown event fires, you say:
this.onUp = this.onUp.bind(this, id);
Which:
Looks for onUp on the object and doesn’t find it
Looks up the prototype chain
Finds it on the call
Calls bind on it to create a new function
Copies that function to the onUp property of the object itself (creating it)
Now you have an onUp property on the object itself and on the object up the prototype chain.
This generally isn’t a problem but in your specific case, it creates an inefficiency.
The second time you trigger the mousedown event it finds onUp on the object itself, but still calls bind on it to create a new function (which calls the previous function which calls the previous function).
Every time there is another mousedown event you get an additional layer of binding.
You could avoid this by testing with the hasOwnProperty method (which will tell you if the value exists on the object itself and not up the prototype chain).

Weird moment of JS Inheritance

Here is my superclass:
function Element() {
var name;
this.setName(n) = func()...{};
this.getName() = func()..{return name};
}
My another child class:
Select = null;
...
Select =
function (n) {
if (typeof n !== "undefined")
this.setName(n);
...
}
Select.prototype = new Element();
Select.prototype.constructor = Select;
So, what kind of "weird moment" am I talking about? Here it is:
var e1 = new Select("element1");
e1.getName(); // return "element1"
var e2 = new Select(); // WITHOUT NAME
e2.getName(); // return "element1"!!! should be ""!
This is a fairly predictable behavior, but how to get around this?
Of course, i can make something like a this.clear() in Element, that will clear properties and put this method in Select function, but maybe there is a proper solution?
You should add this line Element.call(this, n) into Select. It's quite hard to explain and I feel grumpy because I don't like fake private properties in javascript, but well, I must provide some details in order for you to understand what you are currently doing, otherwise I will not be able to get to sleep.
So, doing new Element() creates a new context where name can live without disturbing anyone. Additionally, two new functions called setName and getName are created, and bound to the prototype object of Select.
From now on, if you create a new instance of Select, you can call these functions since they are available in the prototype of the instance. This actually happens doing new Select("element1"). Indeed, n is defined, so, setName is called from this, which refers to the instance.
But most importantly, calling setName with n set to "element1" will also set name to "element1". Then, if you create a second instance of Select, without defining n, setName is not called, so, name remains set to "element1", for both instances.
Why for both instances? Because both instances share the same setName method, the one bound to the prototype, and this method refers to a unique name variable - remember that Element was called only once.
Finally, why this new line Element.call(this, n) prevents name from being shared? Because each call to Element creates a new context, that is to say, a new name, a new setName and a new getName, and binds these two methods to the newly created instance.
Well, hope this is the morning for you, I would be sorry if you get into sleeping disorders by my fault...
As mentionned by Bergi and HMR, this way of creating the prototype - Child.prototype = new Parent - is out of fashion and will lead you to a dead end. Keep in mind that the prototype is kind of a template for creating instances. Knowing this and considering your code, we can make at least two observations :
As you know, a constructor is intended to initialize instances. In your case, the initialization process - what's inside Element - is unnecessarily executed since the goal is currently to set the template upon which instances will be created.
Let's say that name is required in new Element(name), otherwise your program crashes. What kind of name would you give to the prototype? This question is obviously useless, since you would not want to give a name to a template.
To bypass these problems you need a middleman as shown in the below code (copied from the link shared by Bergi). As you can see, the creation of the prototype is delegated to a "dummy" constructor which is empty. Doing so allows to resolve the two problems raised above.
function Dummy () {}
Dummy.prototype = Element.prototype;
Select.prototype = new Dummy();
Select.prototype.constructor = Select;
Alternatively, you could use the built-in Object.create() :
Select.prototype = Object.create(Element.prototype);
Select.prototype.constructor = Select;
Further reading :
https://stackoverflow.com/a/15461601/1636522
http://aaditmshah.github.io/why-prototypal-inheritance-matters/
You should use the prototype of Element so the variables are not shared between the instances:
function Element() {}
Element.prototype.setName = function(n) { this.name = n; };
Element.prototype.getName = function() { return this.name; };
In this case e2.getName() will return undefined.

Dealing with Scope in Object methods containing 'this' keyword called by Event Listeners

I've generalized my lack of understanding of the situation to this small problem. Here's what I think I know so far:
I have an object myDog (a global variable). Dog has a member variable el that is an html element; because it's an element, I can add event listeners to it. So, when you click on myDog.el, it logs to the console the values of this.name and myDog.name. As expected because of scope, this.name is undefined and myDog.name is 'tye'. this inside of Dog.speak when invoked by the click event listener refers to the element that was clicked, the member variable el, not the object Dog. Since myDog is a global variable, it's able to pick back up regardless of the function's scope and get to myDog.name just fine.
See code below:
function Dog(name,id) {
this.name = name ? name : "spot";
this.id = id ? id : "dog";
this.el = document.getElementById(this.id); // given there is a div with a matching
this.el.addEventListener("click",this.speak); // ignore IE for simplicity (attachEvent has its own 'this' scope issues)
}
Dog.prototype = {
speak: function() {
console.log("this.name: "+this.name+"\nmyDog.name: "+myDog.name);
}
};
var myDog = new Dog("tye","dog1");
So... my questions are
1) What are some strategies for attaching objects to html elements, so that I can go from this.el back to myDog (this.el's owner) without myDog being a global variable?
2) Are global variables in this case a necessary evil? And if so, what are so good strategies in this case to gracefully use them? For example, what if I wanted 100 dogs instantiated? How would I handle all those global variables in Dog.speak?
Here's a jsfiddle version if you want to play with it: http://jsfiddle.net/chadhutchins/Ewgw5/
"1) What are some strategies for attaching objects to html elements..."
Since you're using .addEventListener(), I'd suggest taking advantage of a feature of it that few people seem to know about... making your Dog object implement the EventListener interface.
This establishes a very clean relationship between your Dog data and its associated element.
Only minor changes are required. Code first... explanation below.
DEMO: http://jsfiddle.net/Ewgw5/1/
function Dog(name,id) {
this.name = name ? name : "spot";
this.id = id ? id : "dog";
this.el = document.getElementById(this.id);
// ---------------------------------v----no function!
this.el.addEventListener("click", this);
}
Dog.prototype = {
// Implement the `EventListener` interface
handleEvent: function(event) {
switch (event.type) {
case "click": return this.speak();
}
},
speak: function() {
console.log("this.name: "+this.name+"\nmyDog.name: "+myDog.name);
}
};
var myDog = new Dog("tye","dog1");
So all I did was pass this in the constructor to addEventListener() instead of passing a function, and then I added a handleEvent() method to Dog.prototype.
Now when a "click" event occurs, it will invoke the handleEvent() method. The value of this in that method will be your Dog instance. So from there you can call whatever method(s) you need.
Because you made the element a property of this, you can access the element via this.el. But that's technically not even necessary, since the element is also available via the event object as event.currentTarget.
"2) Are global variables in this case a necessary evil..."
Thankfully no!
This behavior should be part of your shim for .addEventListener().
You can try something like this:
function Dog(name,id) {
var self = this; // <---- NEW BIT saving a reference to this
this.name = name || "spot"; // <-- unrelated tidy-up to use || instead of ?:
this.id = id || "dog";
this.el = document.getElementById(this.id); // given there is a div with a matching
this.el.addEventListener("click",function(){ self.speak(); });
// ignore IE for simplicity (attachEvent has its own 'this' scope issues)
}
Through the magic of closures the anonymous function I've added within addEventListener() has access to the scope of the containing function even after the containing function returns, so it is able to use self which holds a reference to the original object saved from this when Dog() was called as a constructor.
EDIT: Sorry, I didn't directly address the questions you numbered (1) and (2), but as you can see you don't need global variables to fix this this issue. With the technique I described you could instantiate 100 dogs and they'd all work. (Well, they'd all speak anyway: they'd all work if you added Dog.prototype.work = function() { }.)
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
this.el.addEventListener("click", this.speak.bind(this));
This way is preferable because it doesn't require allocating a scope in order to pass the binding. Scope allocation is one of the more costly things in JS.

How do I reference an object method in javascript from outside the object (through event)?

I am creating a fairly complex object and want to be able to manipulate the object from the outside. I want to reference an privileged method from a click event
function myObject(){
//How do I reference this function?
this.doSomething = function (action){
var doing = action;
}
}
I understand that I could reference the method if I create the object inside a variable like:
var obj = new myObject();
obj.doSomething('hit keyboard');
But the links I am creating to trigger this event are being created by the object, but placed outside the object, so they will not know the container variable to reference it.
i.e.
Do Something
doesn't work because of course the doSomething method is part of the object, not a global function. I could rewrite the code to make it global, but would rather avoid doing that.
If this question doesn't make sense I would be happy to make it clearer. Thanks in advance.
Although it will sit in the global scope, and generally not recommended, you could do this (if you just need to get it working):
function myObject(){
//How do I reference this function?
window.doSomething = function (action){ // USE window instead of this !!
var doing = action;
}
}
By using window instead of this, it can be called in your handler.
IF the object instance is already created, then you can say:
Do Something
Otherwise,
Do Something
You need the object to be in the global scope. Try this:
window.myObject = {
doSomething: function(action) {
alert("Done!");
}
};
and then you make a click event like
Do Something
Example
To define the problem, you're in a particular object's click handler and you want to call a method on another object in your code that you can't get to directly from the object you're in. You really only have these options and you can just pick the one that seems best or seems the least objectionable:
You can make the object that you want to call a method on be in the global scope.
You can make the object that you want to call a method on be findable in the global scope (either in a parent hierarchy or with some sort of global find). It can be done this way without necessarily adding any more top level objects to the global scope.
You can set the object that you want to call a method as instance data on all objects that will have the click handler so that you can just reference the global object from the click handler object's instance data.
You can declare the click handler to have a parameter that contains your object by using function closures and avoid all global data.
The problem is that there's a disconnect between the instance of an object and the element on the page whose event should trigger the object's function. There are various ways to handle that without exposing anything globally. One way, for example, would be to tie the two via an object's property:
Give the element an ID:
<a id="myLink">Do Something</a>
Add element property to object:
function myObject(element){
this.element = element;
if (this.element) {
this.element.onclick = this.doSomething;
}
this.doSomething = function () {
// do stuffs.
}
}
Instantiate object:
var obj = new myObject(document.getElementById('myLink'));
Or simplify the object definition a bit more:
function myObject(element){
this.element = element;
if (this.element) {
this.element.onclick = function () {
// do stuffs.
}
}
}

Categories

Resources