I would like to create an utility object/function size, which given a dom element myDom and calling another function as big or small change the inline style of myDom.
Currently I am interesting in a solution for actually passing myDom to function big or small so there the inline style modification can happen.
I would like to know which JS pattern could help me to achieve this result and a brief example.
window.size(myDom).big();
window.size(myDom).small();
You can just return object with big and small methods and use closure to access myDom:
function size(myDom) {
return {
big: () => { myDom.style.... }
small: () => { .... }
};
}
Or you can create class with myDom as constructor parameter (which stores it in the field) and appropriate methods.
If you want to use ES6 classes, you could return an instance of a class that wraps around your element:
class Sizeable {
constructor(el) {
this.el = el;
}
big() {
// do something with this.el
}
small() {
// do something with this.el
}
}
window.size = function(el) {
return new Sizeable(el);
};
Which is, of course, roughly equivalent to:
function Sizeable(el) {
this.el = el;
}
Sizeable.prototype.big = function() {
// do something with this.el
}
Sizeable.prototype.small = function() {
// do something with this.el
}
window.size = function(el) {
return new Sizeable(el);
};
The size method must have an object as its return value.
That object must have properties named big and small.
It should also have any additional data (stored in other properties) that you want to make available to
The values of each of those properties must be functions.
Those functions can either read variables from the function which created them (as closures) or use the this keyword to read the data from the object (you would need to store the data on that object in other properties).
Related
It seems like I don't understand prototypes correctly once again.
If you call a method on an object, and it doesn't exist - does it not check the prototype for the method can run it?
like
Array.prototype.myArrayMagic = function () { ... }
Then on any array you can call arr.myArrayMagic() ?
That was my understanding, but I am now running into issue where calling a prototype method wont work unless I explicitly call it through .prototype
Also: I am restricted to ES5 code
The base module setup
function Wall() {}
Wall.prototype = {
shouldShowWall: function() {
return true;
},
show: function () {
if (!this.shouldShowWall()) {
return;
}
// Do other stuff here
}
}
module.exports = Wall;
The child object
function OtherWall() {
this.item = 'my_tracking_id',
this.minutes = 30
// ...
}
OtherWall.prototype = {
constructor: Object.create(Wall.prototype),
/**
* Override wall.prototype.shouldShowWall method for deciding if the wall should be shown at this time
*/
shouldShowWall: function() {
// return true or flase here
}.bind(this)
}
module.exports = OtherWall;
So following other answers and trying to understand as best as possible, this should be set up in a way where I can call
new OtherWall().show();
but it doesn't work, it only works if I call
new OtherWall().prototype.show();
Is this normal, or have I set it up wrong?
What do I need to change to get it to work with OtherWall().show();
If you call a method on an object, and it doesn't exist - does it not check the prototype for the method can run it?
Yes, it does (like with any property), but this code is incorrect:
OtherWall.prototype = {
constructor: Object.create(Wall.prototype)
// ...
That won't make Wall.prototype the prototype of OtherWall.prototype. To do that, do this:
OtherWall.prototype = Object.create(Wall.prototype);
Then add properties on OtherWall.prototype, starting with constructor:
// But keep reading, I wouldn't do it quite like this
OtherWall.prototype.constructor = OtherWall;
...then shouldShowWall, etc.
My answer here may also be useful, it shows creating a "subclass" using ES5 syntax (contrasting it with ES2015's class syntax).
Another couple of notes:
1. This looks like it's probably not what you intend:
/**
* Override wall.prototype.shouldShowWall method for deciding if the wall should be shown at this time
*/
shouldShowWall: function() {
// return true or flase here
}.bind(this)
The this in scope there isn't an instance of OtherWall, it's whatever this is outside that object literal. If you want to bind shouldShowWall to the instance, you'll need to do that in the constructor.
2. I'd suggest making methods non-enumerable, like ES2015's class syntax does, and the same for the constructor property like JavaScript always has. When you create a property via simple assignment, the property is enumerable.
3. I think you have a typo in OtherWall, you've used a , at the end of the first statement instead of a ;. Technically, it works, because of the comma operator, but I don't think you were using the comma operator on purpose. :-)
Here's an example applying all of the above; Object.defineProperty is an ES5 method (I'm out of practice writing ES5 code, but I think I've avoided all ES2015+ stuff here):
function assignNonEnumerable(target, source) {
Object.keys(source).forEach(function (key) {
Object.defineProperty(target, key, {
value: source[key],
configurable: true,
writable: true,
enumerable: false, // This is the default, but showing it here for emphasis
});
});
}
function Wall() {
}
assignNonEnumerable(Wall.prototype, {
shouldShowWall: function () {
return true;
},
show: function () {
console.log("show called");
if (!this.shouldShowWall()) {
return;
}
// Do other stuff here
},
});
function OtherWall() {
this.item = "my_tracking_id";
this.minutes = 30;
// If you want to bind it to the instance:
this.shouldShowWall = this.shouldShowWall.bind(this);
}
OtherWall.prototype = Object.create(Wall.prototype);
assignNonEnumerable(OtherWall.prototype, {
constructor: OtherWall,
shouldShowWall: function () {
// You can't bind the instance here, see the constructor above
console.log("shouldShowWall called");
return Math.random() < 0.5;
},
});
new OtherWall().show();
Is there a way for a function to get the object value before the function name? Below is an example of what I am kinda trying to achieve. I want to be able to read the myElement variable within the function but not pass it as a parameter but pass it before the function with the dot.
Any simple examples or explanations would be most helpful.
var myElement = document.getElementById('myID');
myElement.myFunction();
function myFunction() {
alert(myElement);
}
The only way you could do this is to add myFunction to HTMLElements prototype (which is what gets returned by document.getElementById(). That's usually frowned upon, but if it's your own project and you know what you do, you could do that.
var myElement = document.getElementById('myID');
HTMLElement.prototype.myFunction = function() {
console.log(this);
}
myElement.myFunction();
<div id="myID"></div>
With this prototype in place, you can call myFunction on every HTMLElement in your code.
In regards to your last comment, the function could be
HTMLElement.prototype.myFunction = function() {
alert(this.id);
}
I don't see why you should do it, as it's much easier to just do
alert(myElement.id);
In regards to the comments, here's what I'd do. Instead of extending anything, create your own class (or function), that takes a HTMLElement. Now on this class, you can add whatever method you want, manipulate your element and then return the plain HTMLElement from a getter. You can obviously change that to whatever return you want.
class MyHtmlElement {
constructor(htmlElement) {
this._htmlElement = htmlElement;
}
alertId() {
alert(this._htmlElement.id);
// optional
return this;
}
logId() {
console.log(this._htmlElement.id);
// optional
return this;
}
setId(newId) {
this.htmlElement.id = newId;
// optional
return this;
}
setStyle(prop, val) {
this._htmlElement.style[prop] = val;
// optional
return this;
}
get htmlElement() {
return this._htmlElement;
}
set htmlElement(value) {
this._htmlElement = value;
}
}
const el = new MyHtmlElement(document.getElementById('foo'));
el
.setId('bar')
.logId()
.alertId()
.setStyle('background-color', 'red')
.setStyle('width', '100vw')
.setStyle('height', '100vh');
// If you need the plain element, return it
const plainHTMLElement = el.htmlElement;
console.log(plainHTMLElement);
<div id="foo"></div>
When a function is stored in an object and then called with theObject.theFunction(), the value of this within the function will be theObject.
function sayHello() {
alert('Hello, my name is ' + this.name);
}
let myObject = { name: 'Bob', speak: sayHello };
myObject.speak(); // shows the message 'Hello, my name is Bob'
Now if you want to be able to create your own function and let it be used by an Element, you either need to store the function in the Element instance first or to add it to the Element prototype, both of which I highly discourage. If you feel like you have to do this, there's a flaw in your design.
Still, if you have a good reason to add a custom method to an existing object, I recommend you look up lessons about prototype inheritance in JavaScript, or read my old answer about it here if you're not sure how it works. You could say, make a function which adds methods to an object when it is called, like this:
function addMethods(elem) {
elem.speak = sayHello;
}
let myElement = document.getElementById('myID');
addMethods(myElement);
myElement.speak(); // Hello, my name is <value of the element's name attribute>
Or you could add the method to the prototype of all elements:
Element.prototype.speak = sayHello;
let myElement = document.getElementById('myID');
myElement.speak();
While browsers have let people do this since forever ago, there is technically no guarantee that Element is publicly available, or that its prototype is modifiable, or that you can add methods to Element instances. The Prototype framework (an inconveniently named third party library) has been using these techniques for a long time, but it did cause them a couple issues. jQuery prefers using a different approach, wrapping elements in another object on which custom methods are put.
In fluid APIs like the one provided in Chalk's they give you the ability to chain methods together.
chalk.red.bgYellow('string')
These methods can also be used on their own:
chalk.red('string')
How can we string these together without parentheses. I realise that each method will return the object with all these properties & methods on it. I just don't understand how they can be a method as well as an object with methods on it.
I have looked at the source code of Chalk but it's slightly out of my reach currently.
I think this is probably along the lines of how it's achieved.
Essentially, you have getters on an internal member object. These change the state of that object, while also returning the constructor function itself.
We then add methods to the prototype object of the constructor (or via es6 class syntax sugar) which set the state of the internal object, while also calling a method on that object (log() for example). These methods also set the state of the internal object.
This could definitely be cleaner but I think this is how this type of functionality is achieved in practice.
If anyone has any thoughts let me know.
One thing to note: Initially I was returning the chk object from within the constructor. Of course, this doesn't work because then we don't have a prototype object on our constructor function, meaning I cannot add methods to it. It's a case of continuously returning the constructor function, and accessing the internal object's state from the methods on the prototype.
const Chalk = function () {
const _this = this
this.chk = {
get red () {
this.color = 'red'
return _this
},
get blue () {
this.color = 'blue'
return _this
},
get bgYellow () {
this.bg = 'yellow'
return _this
},
get bgBlue () {
this.bg = 'blue'
return _this
},
log(msg) {
this.msg = msg
console.log(`color: ${this.color} \nbg: ${this.bg} \nmsg: ${this.msg}
`)
}
}
}
Chalk.prototype.red = function (msg) {
this.chk.color = 'red'
this.chk.log(msg)
}
Chalk.prototype.blue = function (msg) {
this.chk.color = 'blue'
this.chk.log(msg)
}
const chalk = new Chalk().chk
chalk.bgYellow.blue('test')
I was reading up on some discussions about extending native objects in javascript. Extending native objects in current browsers seem to have much less disadvantages than they used to. Certainly considering we have more control about how objects are extended through methods such as:
Object.defineProperty(<some parameters...>)
A big risk that however remains is that of collision between different code/libraries, resulting in unexpected behavior.
Risks of naming collision (and global clobbering) can be reduced by namespacing your own functions. So I thought why do we not do this if we extend the native object? Of course execution context is a problem but we can fix that with bind(), making native functions of that object available in our extended functionality. So i created the following to extend the Element object:
// Define the extend function
Object.defineProperty(Element.prototype, 'extend', { value: function(namespace, object) {
var cache = {};
// Create the namespace and make sure on invocation the Element is bound
Object.defineProperty(Element.prototype, namespace, { value: function() {
var objectCheck = typeof cache[Element.prototype.constructor.name] !== 'undefined';
if(objectCheck && typeof cache[Element.prototype.constructor.name][namespace] !== 'undefined'){
console.log('cache used');
return cache[Element.prototype.constructor.name][namespace];
} else {
var extended = Element.prototype[namespace].extended;
for (var e in extended) {
extended[e] = extended[e].bind(this);
}
if(!objectCheck)
cache[Element.prototype.constructor.name] = {};
cache[Element.prototype.constructor.name][namespace] = extended;
}
return extended;
}});
this[namespace].extended = object;
return this;
}});
// Extend the Element prototype with an attr function in
// the namespace 'namespace' or ofcourse whatever function
Element.prototype.extend('namespace',{
attr: function(name, value) {
if(arguments.length === 1) {
return this.getAttribute(name);
} else {
this.setAttribute(name, value);
return this;
}
}
});
It all looks good when check it out on an actual element, the namespace is there and inside it we find our 'attr' function. We can invoke it like this:
document.querySelector('.a-css-class').namespace().attr('class')
// returns 'a-css-class'
The code could be refactored further to dynamically extend all sorts of objects. However I'm curious, what could this mean for performance, does this experiment make sense? The main question, is this any better than extending directly?
EDIT (based on Bergi's comments regarding performance):
It would be possible to cache the created function in the outer function. Let's see if I can come up with an implementation.
EDIT 2:
Added a simple caching function to make sure not all namespaced methods are create each en every invocation.
EDIT 3:
The updated code. In an attempt to make extending native object safer, the following code was produced. It might prevent naming collision. Thanks #Bergi:
/**
* Extend a (NATIVE) object with a namespace. See below for an example
*/
Object.defineProperty(Object.prototype, 'extend', { value: function(namespace, object) {
function Wrapper(that) {
this.that = that;
}
Wrapper.prototype = object;
Object.defineProperty(Object.prototype, namespace, { value: function() {
return new Wrapper(this);
}});
}});
// This example uses the Element object, but it could be any object
Element.prototype.extend('namespace',{
attr: function(name, value) {
if(arguments.length === 1) {
return this.that.getAttribute(name);
} else {
this.that.setAttribute(name, value);
return this;
}
}
});
However I'm curious, what could this mean for performance
Your current code means that whenever you call namespace(), you create a new object and lots of bound functions, which will impact performance quite heavily for large namespaces.
I would recommend to let your namespace method return a wrapper object with an element: this property, which inherits all the methods that can be called on it. You'd use this.element.getAttribute then instead of this.getAttribute:
Element.prototype.extend = function(namespace, object) {
function Wrapper(el) {
this.element = el;
}
Wrapper.prototype = object;
Element.prototype[namespace] = function() {
return new Wrapper(this);
};
};
The main question, is this any better than extending directly?
Not much. You still are defining extend and namespace properties on the Element.prototype, and for these two properties all the arguments against extending the DOM are still valid. You might lower the risk of collisions with more common names (attr), but it's not better than just defining a namespace_attr method.
we currently learn some Javascript stuff in a course at the university.
For that we implement a library for common tasks like show(), hide(), write and such things.
Currently im running with an implementation like:
var myLib_maker = function () {
/*
private scope
*/
var debuggingMode=true;
var currentElement=null;
/*
end private scope
*/
return {
getElement: function (id) {
var o;
if (typeof id === 'string') { o = document.getElementById(id); }
if (!!(o && o.nodeType !== 1)) {
throw {
name: 'Type Error',
message: 'Wrong node type at id: '+id
}
}
currentElement=o;
return this;
},
getCurrentElement: function() {
console.log(currentElement)
return currentElement;
},
isVisible: function () {
return this.getCurrentElement().style.display.toLowerCase() === "block";
},
show: function () {
this.debug("show "+this.getCurrentElement())
this.getCurrentElement().style.display = "block";
return this;
},
hide: function () {
this.debug("hide "+this.getCurrentElement())
this.getCurrentElement().style.display = "none";
return this;
},
toggle: function() {
this.debug("toggle "+this.getCurrentElement())
this.isVisible() ? this.hide(): this.show();
return this;
},
write: function (content){
this.debug("write to"+this.getCurrentElement().id);
var tg = this.getCurrentElement().tagName.toLowerCase();
if (tg === 'input' || tg === 'textarea') {
currentElement.value = content;
} else {
currentElement.innerHTML = content;
}
return this
},
debug: function (what) {
if (debuggingMode===true){
console.log("[DEBUG] "+what);
}
return this;
}
};
}
var myLib=myLib_maker();
Than I have an external function (for testing) to switch 2 textareas contents.
function switchEditors(id1, id2){
c1=myLib.getElement(id1).getCurrentElement().value;
c2=myLib.getElement(id2).getCurrentElement().value;
myLib.getElement(id1).write(c2)
myLib.getElement(id2).write(c1)
}
I first tried with the following code, which obviously does not work, cause I overwrite my private currentElement and so I write always to id2
function switchEditors(id1, id2){
tmp=myLib.getElement(id1).getCurrentElement().value
myLib.getElement(id1).write(myLib.getElement(id2).getCurrentElement().value)
myLib.getElement(id2).write(tmp)
}
But what I really wanted initially was not using a private currentElement variable.
The first implementation of the write method extended the Element Object
Element.prototype.write= function (content){
var tg = this.tagName.toLowerCase();
if (tg === 'input' || tg === 'textarea') {
this.value = content;
} else {
this.innerHTML = content;
}
return this;
}
and such the getElement function returned
document.getElementById(id)
I want cascading (I hope this is the right word -> I mean the myLib.getElement("myid").show().hide() concatenation thing) and getting direct access to
all Element attributes but we must not use global scope for our library, so I have to encapsulate my library in any way.
So is there an elegant way to use the cascading thing and be able to get a direct access to all attributes on an element object without implementing each method within the global element scope?
Or is my lib desing completely wrong and has to be done totally different.
If so, just tell me, I appreciate any help.
(I tried to figure out how jQuery actually implement these things, but didn't get a real clue how it is done ... too much code ... :) )
I hope I described my wishes and requirements. If not please ask for more specific details.
As you've figured out, the currentElement is shared between calls to getElement. Instead you could create a new instance of myLib-object with Object.create and bind currentElement to that.
getElement: function (id) {
var o, self = Object.create(this);
/* ... */
self.currentElement = o;
return self;
}
And use this.currentElement throughout so that each call uses its own current element.
While Magnar's solution will work with this (singleton) pattern, it is a better idea to avoid creating a whole new object each time you call getElement. There is a reason for creating "classes" instead of singletons.
You can do it like this:
var MyLib_Maker = (function () { // I capitalized the class as a helpful
// convention recommended by Douglas Crockford
// Private static vars
var debuggingMode = true;
var currentElement = null;
// Private static methods
function _myMethod (x, y) { // call below by _myMethod(3,4);
return x * y;
}
// Private instance methods (but only if called properly:
// invoke below by _instMethod.call(this, 3, 4); )
function _instMethod (x, y) {
return this.anInstanceNumber * x * y;
}
// Private instance properties (quite cumbersome but doable if you
// absolutely must--e.g., for classes whose objects need to be clean when iterated)
// See http://brettz9.blogspot.com/2009/02/true-private-instance-variables-in.html
// and http://brettz9.blogspot.com/2009/02/further-relator-enhancements.html
// (put the Relator inside the closure if you don't want it reusable (and public),
// but then your extending classes must be inside the closure too)
function MyLib_Maker (arg1, arg2) {
// I recommend the following check to allow your class to be
// instantiated without the 'new' keyword (as in jQuery/$):
if (!(this instanceof MyLib_Maker)) {
return new MyLib_Maker(arg1, arg2);
}
// Other constructor code here
// ...
}
// Methods added on the prototype benefit from merely
// providing a low-memory reference across all instances;
// this will avoid adding a whole new object unnecessarily
// into memory
MyLib_Maker.prototype.getElement = function () {
// ....
return this; // Keep the chain going (if not public
// properties, you could add a method which also avoids
// extending the chain, like $(el).get() in jQuery
};
return MyLib_Maker;
}()); // We can invoke immediately to wrap up the closure
// Usage example:
var mlm = MyLib_Maker(2, 3).getElement().doSomething();
By the way, what you describe is called chaining; cascading is used in the likes of CSS to indicate that like different waves out of a waterfall, one may write over the other, as you can do by writing rules which override prior ones in CSS.
And it is good you moved away from overriding the Element object because, whatever the browser incompatibilities, this is the worst kind of global namespace pollution because it affects all elements, increasing the chance that another library which depends on that method (or which is careless in overriding the built-in prototypes itself) may get you unexpected results.