Can I subclass a DOM-class? - javascript

I was wondering if I can create a subclass of HTMLDivElement. Like this.
MyDivElement.prototype.pickColor = function()
{
return this.picked;
}
function MyDivElement()
{
this = new HTMLDivElement();
this.picked = 'unknowd';
}
alert(this.picked); // print: 'unkowd'
Is (something like) this possible?
If not, what is the best way to achieve this?

In browsers where __proto__ is exposed and mutable you can sub class DOM elements. It looks like this:
function CustomEl () {
var el = document.createElement('div')
el.__proto__ = CustomEl.prototype
return el
}
CustomEl.prototype.__proto__ = HTMLDivElement.prototype
I also played with it in more detail on jsFiddle. Unfortunately though IE and Opera don't allow __proto__ access and have no plans to in the future as far as I know.

new HTMLDivElement(); throws a TypError "Illegal constructor" in Chrome - so it's not possible.
Update: I've tested in other current browsers, and they throw various types of errors - but they all throw.
Actually, this would work:
function MyDivElement() {
this.picked = 'unknowd';
}
MyDivElement.prototype = document.createElement('div');
var mydiv = new MyDivElement();
But I'm not sure how you could use this pattern...

In some browsers, you can extend the prototype, in others, no. I'll let you guess the ones where you can't. :-) That's not really the same as extending a DOM element, but it does let you do a certain subset of the things for which you might want that facility. The thing is, DOM elements aren't really JavaScript entities; they're only simulacrums provided by the runtime system. (Maybe someday all the jsdom work will actually come to fruition.)
Well ok I'll tell you about the problematic browsers: IE doesn't like that at all. However others do. If you've ever looked at the Prototype library, you'll come across a manifestation of that fact all the time via nasty irritating IE-only bugs when you forget to Prototype-ize a DOM element reference.
(IE9 may be different, but I sort-of doubt it.)
This is the kind of thing that's dirt simple to test over at jsfiddle.

I'm experimenting with this a little bit. A big difficulty is that you need the context of a document to create an element. You can go with window.document as a default, but that's boring.
Here's the POC I'm working on:
function CustomNode(type, parent) {
if (type instanceof Node) {
// Decorate a preexisting node appropriately if called that way.
if (arguments.length === 2 && type.ownerDocument !== parent) {
// Import the node if it's not owned by the requested document.
type = parent.importNode(type, true);
}
return Object.assign(type, this.__proto__);
}
//Normal flow, e.g., new CustomNode("div");
var d = document;
if (parent) {
// Alt document flow, e.g., new CustomNode("div", xmlDoc);
if (parent.nodeType === 9) {
d = parent;
} else {
// Support for new CustomNode("div", parentElement);
// This doesn't append the element, just makes sure
// the documents match
d = parent.ownerDocument;
}
}
var inst;
// Creation flags
if (type[0] === '#') { //text
inst = d.createTextNode(type.substr(1));
} else if (type[0] === '?') { //Processing instruction
type = type.substr(1).split(' ');
inst = d.createProcessingInstruction(type.shift(), type.join(' '));
} else if (type[0] === '[') { // CDATA
inst = d.createCDATASection(type.substr(1));
} else if (type[0] === '/') { // Comment
inst = d.createComment(type.substr(1));
} else { //Everything else gets an element.
inst = d.createElement(type);
}
// DE-COR-ATE
Object.assign(inst, this.__proto__);
return inst;
}
// Decorator for customized NodeLists; probably inefficient. Decorates
// contents with CustomNode
function CustomNodeList(list) {
var Self = this.constructor,
CNode = this.Node;
return Object.assign([].map.call(list, function (node) {
return new CNode(node);
}), this.__proto__);
}
CustomNodeList.prototype = {
// so we know how to wrap...
Node: CustomNode,
text: function () {
return this[0].textContent;
}
};
CustomNode.prototype = {
// So we know how to decorate NodeLists
NodeList: CustomNodeList,
// So we know how to decorate Nodes
Node: CustomNode,
// Easy make-and-attach
new: function (type, attach) {
var CNode = this.Node;
var ret = new CNode(type, this.ownerDocument);
if (attach) {
this.appendChild(ret);
}
return ret;
},
// NodeLists with ES5 iterators!
find: function () {
var CNodeList = this.NodeList;
return new CNodeList(this.querySelectorAll.apply(this, arguments));
},
kids: function () {
var CNodeList = this.NodeList;
return new CNodeList(this.childNodes);
}
};
Mind, this is likely a bad idea: everything in the same scope of these functions (including the elements themselves) will never get garbage collected, as the GC in most browsers is dead-stupid when it comes to elements referencing objects. I'll never use it for production for that reason alone: it's a straight-up memory leak.

Related

How to create shorthand for anyElement.querySelector method

One can create shorthand for document.querySelector with
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
so now let a = $('a') and let a = document.querySelector('a') are equivalent.
Is there a way to create shorthand for the querySelector method itself?
I.e. to make let a = element.shortHand(args) and let a = element.querySelector(args) to be equivalent for any (unknown in advance) element.
Edit: Since people are telling that doing the above is a bad idea, there is another question: How to make$ $$ selectors like the one in the Chrome DevTools, which accept the root element as second parameter?
I.e. to make let a = $('a',element) and let a = element.querySelector('a') to be equivalent.
Here are some options:
Add Method to Element.prototype
Element.prototype.shortHand = Element.prototype.querySelector
This "monkey-patches" the Element class in the DOM itself and adds this function on all elements in DOM, which is just a copy of the querySelector function.
This is very discouraged. It's bad for performance and it is bad in case browsers decide to add more functions in the future that conflicts with your function. But if you're just playing around and not shipping this code it should be fine.
Mini jQuery
If you're looking to create your own mini jQuery, you can also do something like this:
class MiniJQuery {
constructor(el) {
this.el = el;
}
shortHand(...query) {
return this.el.querySelector(...query);
}
// ... put any other functions you want to use
}
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
return new MiniJQuery(queryOrElement);
}
// Now you can:
const a = $(element).shortHand(args);
// which is equivalent to
const a = element.querySelector(args);
This is a much safer approach and not problematic. I don't think this adds much value as you can just type the slightly longer method name, but you could add more interesting methods on your class to make it worthwhile.
Proxy
Very similar to the approach above, but you can use a Proxy instead of the MinijQuery class to "forward" unknown methods to the element itself. This means that $(element) will have all the methods that element itself has.
Example:
const handler = {
get: function (target, prop, receiver) {
if (prop === "shortHand") {
return target.querySelector.bind(target);
}
const retVal = Reflect.get(...arguments);
// Bind methods to the element.
return typeof retVal === 'function'
? retVal.bind(target)
: retVal;
},
};
const $ = (queryOrElement) => {
if (typeof queryOrElement === 'string') {
return document.querySelector(queryOrElement);
}
// You can add all sorts of custom function handlers here.
return new Proxy(queryOrElement, handler);
}
$('div') // gets divs
$(element).shortHand(...)
// works the same as element.querySelector
// But the HTMLElement methods still work too:
$(element).querySelector
$(element).querySelectorAll
$(element).className
// ...
Read More Here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Javascript prototype function override when x

In my case, I'm using the Phaser framework.
So in this example I'm extending the Group class of phaser. Every 'actor' class (Sprite, Group, ...) calls upon the update() prototype every few miliseconds.
My idea was to extend this function only when the application runs on a desktop (so not on a phone).
for example:
var MousePointer = function (game, parent, name) {
Phaser.Group.call(this, game, parent, name);
this.init();
};
MousePointer.prototype = Object.create(Phaser.Group.prototype);
MousePointer.prototype.constructor = MousePointer;
MousePointer.prototype.init = function () {
// ... init
};
MousePointer.prototype.update = function () {
// Do something when on desktop
};
I can't possibly use an if clausule in the update() function to check whether the player is on dekstop/tablet/phone. So is there a way to actually override the prototype on initialisation?
for example (pseudocode):
if(onPhone)
MousePointer.prototype.update = parent.prototype.update;
else
MousePointer.prototype.update = this.update;
Well, you've kind of already written the answer for yourself, haven't you? This code (not inside the init method).
if(onPhone) {
MousePointer.prototype.update = function(){//Phone implementation};
} else {
MousePointer.prototype.update = function(){//Other implementation};
}
I advise against starting off with the "regular" function and then potentially overriding it, since you're just declaring it for nothing.
I think a better way to do this would be to write two different classes that shares the same parent, and then write different update() implementations for them. Then you can just do something like:
if(phone) {
var obj = new PhoneMousePointerObject();
} else {
var obj = new DesktopMousePointerObject();
}
// ... later
obj.update()

Testing multiple browsers with protractor backed by page objects

I'm writing a test where two browsers need to interact. The problem with simply forking the browser is that my page objects still reference the old browser. I didn't want to rewrite all of my PO's to take the browser as a parameter so I tried the first solution found in the link below where they overwrite the global variables with the new browser's version :
Multiple browsers and the Page Object pattern
However, changing the global variables doesn't seem to work as all the subsequent page object functions that I call are performed against the original browser instance. I have tried logging the window handler before and after the switch and they are indeed different which only baffles me further. Here's some of the code.
spec:
var MultiBrowserFunctions = require('../common/multiBrowserFunctions.js');
var HomePage = require('../home/home.po.js');
describe('blah', function(){
it('blah', function(){
MultiBrowserFunctions.openNewBrowser(true);
HomePage.initializePage();
});
});
MultiBrowserFunctions:
(function() {
var browserRegistry = [];
module.exports = {
openNewBrowser: function(isSameUrl){
if(typeof browserRegistry[0] == 'undefined'){
browserRegistry[0] = {
browser: browser,
element: element,
$: $,
$$: $$,
}
}
var tmp = browser.forkNewDriverInstance(isSameUrl);
var id = browserRegistry.length;
browserRegistry[id] = {
browser: tmp,
element: tmp.element,
$: tmp.$,
$$: tmp.$$,
}
switchToBrowserContext(id);
return id;
},
resetBrowserInstance : function(){
browserRegistry.splice(1,browserRegistry.length);
switchToBrowserContext(0);
}
}
function switchToBrowserContext(id){
console.log('---------------------------switching to browser: ' + id);
browser=browserRegistry[id].browser;
element=browserRegistry[id].element;
$=browserRegistry[id].$;
$$=browserRegistry[id].$$;
}
}());
My questions are:
(1) why doesn't this work?
(2) Is there some other solution that doesn't involve rewriting all of my po's?
What you can do is, save the browsers in different variables and then switch between them by overriding the globals via a utility or something.
describe('Switching browsers back and forth', function () {
var browserA, browserB;
it('Browser Switch', function () {
var browsers = {
a : browser,
b : browser.forkNewDriverInstance(true)
};
browserA = browsers.a;
browserB = browsers.b;
var browserAndElement = switchBrowser(browserB);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
var browserAndElement = switchBrowser(browserA);
browser = browserAndElement.browser;
element = browserAndElement.element;
//do your stuff
});
});
The switchBrowser() can look like following:
this.switchBrowser = function (currentBrowser) {
browser = currentBrowser;
element = currentBrowser.element;
return {
browser : browser,
element : element
}
}
In this way you don't have to rewrite your POs to take in the new globals.
Hope it helps!
Cheers

implementing namespace function javascript

I'm implementing Stoyan Stefanov's javascript namespace function as I have been reading his very informative JavaScript Patterns book; in my web application but not sure if I'm using it the proper way
here is the funciton implementation i'm using on my web app on this page http://dalydd.com/projects/module_example/
var COOP = COOP || {};
COOP.namespace = function (ns_string) {
var parts = ns_string.split('.'),
parent = COOP,
i;
// strip redundant leading global
if (parts[0] === "COOP") {
parts = parts.slice(1);
}
for (i = 0; i < parts.length; i += 1) {
// create a property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
};
COOP.namespace('sliderContainer')
COOP.sliderContainer = function () {
return slider = ($('#slider').length > 0) ? $('#slider') : $('#element_temp');
} // we need this at the beginning as others are dependent on it and call it initially
my goal is to check every new property of COOP to see if it exists before it's implemented --- so if I create a property of COOP called COOP.sliderContainer - I want to make sure COOP.sliderContainer does not exist already. when I use the namespace function it returns an object but COOP.sliderContainer is a function. I feel like I have to do an extra layer of abstraction in order to name this namespace function work properly like
var sliderContainer = COOP.namespace('sliderContainer');
sliderContainer.sliderContainer = function () {
return slider = ($('#slider').length > 0) ? $('#slider') : $('#element_temp');
}
this seems silly and redundant to me - is there a better way to do this?
any info is appreciated as always - the page has a direct link to the js file on it
namespace function is useful when create sub namespaces inside COOP, it will help to avoid multiple checkings. For example you want to create COOP.module.module1, you have to make 2 checks to see if module and module 1 are not defined or not.
However, in this case, sliderContainer is just a property of COOP. There's no need to use namespace. You just simply check it yourself:
if(COOP.sliderContainer === undefined){
// define it
}
EDIT
You can have a function handle that for you:
COOP.createProperty = function(name, prop){
if(COOP[name] === undefined){
COOP[name] = prop;
}
}
then
COOP.createProperty("sliderContainer", function(){
// do whatever you want
});

Cross-browser Getter and Setter

This works in modern Chrome/Firefox/Opera but fails in IE8. Haven't tried it in IE9. How can I make this cross-browser compatible, including IE7+? (Fiddle here.)
var foo = {
get test(){ return 'Works'; }
};
// foo.test should be 'Works'
I've seen some usage with __defineGetter__ but that threw an 'unrecognized method' error in IE8.
Here is the workaround for IE6/7/8. I performed the test and it works very well!
Update: The link is broken, you can see the code of from my testing here:
// Super amazing, cross browser property function, based on http://thewikies.com/
function addProperty(obj, name, onGet, onSet) {
// wrapper functions
var
oldValue = obj[name],
getFn = function () {
return onGet.apply(obj, [oldValue]);
},
setFn = function (newValue) {
return oldValue = onSet.apply(obj, [newValue]);
};
// Modern browsers, IE9+, and IE8 (must be a DOM object),
if (Object.defineProperty) {
Object.defineProperty(obj, name, {
get: getFn,
set: setFn
});
// Older Mozilla
} else if (obj.__defineGetter__) {
obj.__defineGetter__(name, getFn);
obj.__defineSetter__(name, setFn);
// IE6-7
// must be a real DOM object (to have attachEvent) and must be attached to document (for onpropertychange to fire)
} else {
var onPropertyChange = function (e) {
if (event.propertyName == name) {
// temporarily remove the event so it doesn't fire again and create a loop
obj.detachEvent("onpropertychange", onPropertyChange);
// get the changed value, run it through the set function
var newValue = setFn(obj[name]);
// restore the get function
obj[name] = getFn;
obj[name].toString = getFn;
// restore the event
obj.attachEvent("onpropertychange", onPropertyChange);
}
};
obj[name] = getFn;
obj[name].toString = getFn;
obj.attachEvent("onpropertychange", onPropertyChange);
}
}
I don't believe you can.
In IE8 and lower, property access is mere property access. There's no way to run function code without explicitly invoking the function.
I think in IE8 you may be able to with DOM elements, but I don't believe it works for regular native objects.
There is a "definePropery" method that will essentially allow you to create accessor methods (getters/setters) on Objects without the need to invoke a function call like setProp() / getProp().
The syntax is a little weird but I've been able to get this to work on Firefox, Chrome, Safari and IE9.
Say I have JavaScript Object called "Person".
function Person()
{
// set a default value //
this.__name = 'John';
// add getter & setter methods //
Object.defineProperty(this, "name", {
get: function() {
// additional getter logic
return this.__name;
},
set: function(val) {
this.__name = val;
// additional setter logic
}
});
}
var p = new Person();
console.log(p.name); // 'John'
p.name = 'Stephen';
console.log(p.name); // 'Stephen'
More info on Mozilla's site here.
You cannot, the syntax is not supported in browsers that did not implement it. Its going to be quite a while before you'll be able to use that syntax without having CBC problems. Be grateful IE6 is pretty much dead in North America.

Categories

Resources