"Enhance" existing JS class in ECMAScript 2017 (Firefox v55)? - javascript

Basically I'm wondering whether it is possible to redefine a method at class level, not instance level, as sought here.
Just to make clear, also, I want to be able to call the original method (on this) from within the enhanced/replacement method.
I tried implementing the chosen answer, "decorating the constructor", but it doesn't appear to work in FF55's "SpiderMonkey" version of ES2017: I get a message saying the class "requires new".
Doing a bit of googling I found this article. This man talks about a whole bunch of concepts which I wasn't even aware existed in Javascript! My knowledge of JS hasn't gone much beyond the Javascript Definitive Guide 6th Edition, although I do use Promises and async/await a lot (I'm hoping a new edition of the JS Def Guide will come out one day...).
So are there any experts out there who know of a way to, essentially, "enhance" (i.e. re-engineer, not extend) existing JS classes in ES2017?

Let say you have a class named Original, which has a render method, which you want to update.
class Original {
render() {
return 'Original';
}
}
You can update it by changing the render method of the prototype of the Original function like so:
Original.prototype.render = function() {
return 'Changed';
}
If you want to add a new method to your class this is how you do it:
Original.prototype.print = function() {
return 'Printing...';
}
Then you can use these methods as usual.
const changed = new Original().render();
const printed = new Original().print();

Whoops... thanks to Dovydas for this. I was obviously having a mental block!
const superObserve = MutationObserver.prototype.observe;
MutationObserver.prototype.observe = function( target, config ){
console.log( 'blip');
superObserve.call( this, target, config );
}

Related

Handling assertions in a custom helper

I've started playing around with CodeceptJs and I've got it up working quite easily. I'm currently using it with NightmareJs and all seems fine.
The specific area I'm testing is a gallery that fetches data from an interface via JSONP creating a list of images wrapped in <div>s.
A portion of the tests I'm implementing is like the following:
Feature('gallery')
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeElement('#gallery .col-md-3')
I.click('#gallery .col-md-3')
I.seeElement('#gallery .selected')
})
Now since the elements can be any number, it's currently silently using the first element, but in order to give it a bit more entropy I wanted to pick an element at random, something like the following
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeMoreThanElements('#gallery .col-md-3', 1)
I.clickOnRandomElement('#gallery .col-md-3')
I.seeElement('#gallery .selected')
})
Or even better, if I could grab the list of elements so I can decide which one to click on, like:
Scenario('clicking on an element adds "selected" class', (I) => {
I.amOnPage('/')
I.seeMoreThanElements('#gallery .col-md-3', 1)
const elements = I.grabRandomElement('#gallery .col-md-3')
const random = getRandomInt(1, elements.length)
I.click(`#gallery .col-md-3:nth-child(${random})`)
I.seeElement(`#gallery .col-md-3.selected:nth-child(${random})`)
})
The current helpers available don't allow me to perform some particular actions, so I started implementing a custom handler as described in the guide at http://codecept.io/helpers/
In my configuration I have the following:
"helpers": {
"Nightmare": {
"url": "http://localhost:3000"
},
"DOMElements": {
"require": "./__tests__/helpers/domelements_helper.js"
}
}
and domelements_helper.js currently looks like the following:
'use strict'
let assert = require('assert')
class DOMElements extends Helper {
seeMoreThanElements (locator, count) {
this.helpers['Nightmare']._locate(locator).then(function (els) {
return assert(els.length >= count, `Found more than ${count} elements`)
})
}
}
module.exports = DOMElements
This doesn't - clearly - work. This is where I'm getting a bit confused.
First of all, I'm using the default Node.js assertion library, and if there's any need I'm happy to move over to something more robust like Protractor or Chai-as-promised, but the slimmer the better.
Secondly, the documentation clearly states the following:
any helper method should return a value in order to be added to promise chain
Which doesn't really make sense... should I return a promise or should I handle the whole thing within the then() statement? As returning a basic value doesn't really do much. Even then, how do I handle failed assertions?
I've also seen a Nightmare clientscript in the code base but I have no idea if it's of any use for my case, as I've just started digging through the code base in order to understand a little bit better how to customise and extend CodeceptJs.
Any pointers are really appreciated
since nobody seems to have got this far, I'm going to add an answer as I seem to have found how this thing works by going through the codebase and understand a bit more how it works.
tl;dr: the quick solution is the following:
/* __tests__/helpers/domelements_helper.js */
const assert = require('assert')
class DOMElements extends Helper {
seeMoreThanElements (locator, count) {
return this.helpers['Nightmare']._locate(locator)
.then((elementsArray) => {
if (elementsArray.length < count) {
return assert.fail(elementsArray.length, count, `Found more than ${count} elements`)
}
})
}
}
module.exports = DOMElements
The way the whole thing works is by promises and you have to handle failure appropriately so that the whole system can fail gracefully (sorta).
In particular _locate() returns a promise and everything has to be handled asynchronously, although by design this seems to be quite awkward and it's making things particularly hard to implement, at least in the current state.

Deleting a property of an object inside its definition; Why?

Looking at the recent Google Maps API loader source, I'm wondering what is the purpose of the following:
google.maps.Load = function(apiLoad) {
delete google.maps.Load;
...
Why would you delete a property of an object, inside its definition? I suspect it could have some performance increase, but can't figure out how a property can delete itself inside its definition.
Obviously we can only make assumptions since it's only that code author can say for sure.
If the reason was to ensure that the Load procedure is performed just once then the decision chosen is really poor.
The problem is that deletion of properties makes impossible V8 (and may be other engines) to use so called "hidden classes" (which is an optimisation method for faster object's properties lookup).
The better alternative would be
google.maps.Load = function() {};
or
google.maps.Load = function() { throw new Error("Already loaded") };
as suggested by #Sam in the comments.
References:
Understanding hidden classes in v8
Fast Property Access
I'd say to only allow to be loaded once.
This is to ensure that the API is only loaded once. However, this will not throw a useful error when the function is called a second time, but it may cause an exception.
Here is an alternative solution which throws a more useful error.
google.maps.Load = function() { throw new Error("Already loaded") };

Adding Models to Backbone.js Collection Silently Fails

I am implementing a Blog Engine as a learning exercise for a new job. I have a Backbone.js Collection class called BlogList that is composed of BlogModel objects (a BlogModel is a single post to a blog). I have a masterBlogList that keeps all blog posts in memory for the lifetime of the application (I realize this is not a realistic design, but it is part of the spec).
I have chosen to use masterBlogList to hold the canonical state of the application. All new posts, edits, etc. are persisted to the database (MongoDB) as well as masterBlogList. When I want to display a subset of the posts in masterBlogList, I copy them into a new BlogList instance and then narrow this new instance down based on search criteria. Again, I realize this might not be the best design (cloning BlogModels and BlogLists), but it is what I've got and I'd prefer not to rework it.
The problem is that copying one BlogList to another is not working. Even when the source list is non-empty, the destination list always ends up being empty. I have tried to debug this every which way with no luck. Here is the relevant portion of the BlogList source code:
// BlogList
$ (function () {
App.BlogList = Backbone.Collection.extend ({
model : App.BlogModel,
url : '/blog-entries',
comparator : function (a) {
return -(new Date (a.get ('date')));
},
populateFromMemory : function (sourceList) {
// this.reset ();
var self = this;
sourceList.each (function (postModel) {
self.add(postModel);
});
var foo = new App.BlogModel();
this.add(foo);
},
(continued...)
Even the last bit regarding foo is not working. I've also tried adding a clone() of postModel and also new App.BlogModel(postModel.toJSON()).
Any help would be extremely appreciated!
Sorry to have bothered anyone :<, but I got it working. The code actually does work as written above. The problem is that my search criteria were filtering out all of the posts, so I wasn't seeing anything. End of a long day! Thanks to those who tried to help me...

How to create a well formed global javascript object containing special functions

I am creating a small project that heavily relies on JavaScript. I come from php/mysql and now stepping into node.js/javascript/mongodb, and I hve to say it's quite a mindswitch.
I want to create a simple object that has some special function that I can use in the page. I have been looking at some tutorial, and looking at the libraries such as jquery and backbone, but I need some final advice on my decision.
I only need some small functions, and no cross-browser support, that's why I don't choose something like backbone. Maybe ill change to that later when I have a better crasp on JavaScript programming.
What is confusing me is whether to use the new, or maybe wrapping the code into a self-invoking function.
I see jquery creates an object inside the window and than exposes that, but I have no idea how that works.
Enough intro, now to the point. I have created something like this:
var $s = Object.create({
page: Object.create({
title: 'pagetitle',
html: '',
data: {},
render: function(){
// Basic render function
}
}),
socket: Object.create({
// My websocket connection
}),
store: function(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
retrieve: function(key) {
var value = localStorage.getItem(key);
return value && JSON.parse(value);
},
slugify: function(slug){
return slug.replace(/[^a-zA-Z 0-9-]+/g,'').toLowerCase().replace(/ /g,'-');
}
});
This are just a few random functions I put in.
I haven't tested this yet, it is a draft, I want to know if this is any good.
Now I was thinking i can do some stuff like this:
$s.page.html = 'somehtml';
$s.page.render();
// Maybe
$s.store( $s.page.title, $s.page.html );
I do use jQuery and jQuery templating, so something like this could be possible:
$.tmpl( $s.page.html, $s.page.data ).appendTo( "#content" );
Nothing fancy is needed here. You can create a global javascript object with a method like this:
var myGlobalObject = {};
myGlobalObject.testFunction = function() {
// put your code here
};
You can then call that like this:
myGlobalObject.testFunction();
One slightly more flexible design pattern you will often seen used is this:
var myGlobalObject = myGlobalObject || {};
myGlobalObject.testFunction = function() {
// put your code here
};
This is used when there might be lots of different pieces of code contributing to myGlobalObject and they all want to make sure that it's properly declared before adding properties to it. This way of doing it, creates it if it doesn't already exist and if it does already exist, leaves the methods and properties on it that might already be there. This allows multiple modules to each contribute initialization to myGlobalObject without regards for the order they load.

How to integrate backbone.sync with jstorage?

I'm a newbie javascript developer (first post here!) and been recently trying to play with backbone.sync. I've been reading through the todo example and notice that it uses backbone-localstorage. My feeling is that backbone-localstorage was just a quick implementation that the author used for demo purposes. I also noticed that the keys are randomly generated, whereas I would like something that would allow me to name my own key values.
I've been looking at jstorage (www.jstorage.info), and would really appreciate some pointers (preferably with code samples) on how to integrate it with backbone.js. I imagine backbone.sync would need to be overriden somehow as is done in backbone-localstorage.
Alternatively, what localStorage plugin/wrapper would you recommend, and how would it integrate with backbone?
Thanks for help in advance.
My feeling is that backbone-localstorage was just a quick implementation that the author used for demo purposes.
Exactly right. I'd say that the implementation is fine for most things, but what support you can get for it is probably minimal.
I also noticed that the keys are randomly generated, whereas I would like something that would allow me to name my own key values.
This isn't really correct. I assume you are referring to this:
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own.
create: function(model) {
if (!model.id) model.id = model.attributes.id = guid();
this.data[model.id] = model;
this.save();
return model;
}
Here, what's happening is that when a create is called on a model, it tries to determine if an id has been set on the model, and if no such id is provided, then the guid function is used to build one. This isn't setting a key randomly, it's fulfilling the requirement that every saved model should have an id (by assigining a random one).
Modifying the backbone-localstorage.js code should be fairly trivial. Let's look at the store constructor as an example
Before:
var Store = function(name) {
this.name = name;
var store = localStorage.getItem(this.name);
this.data = (store && JSON.parse(store)) || {};
};
The only thing we need to update here is the call to localStorage:
After:
var Store = function(name) {
this.name = name;
//Notice here, that jStorage gives us the option to define a default.
var store = $.jStorage.get(this.name, {});
this.data = store;
};
Similarly simple is the save function:
Before:
save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
}
again, simply update the call to localStorage:
After:
save: function() {
$.jStorage.set(this.name, this.data);
}
Update: jStorage handles all the JSON.stringify, and JSON.parse for you, so I've updated the above code to reflect that.
Easy peasy, right!
Anyways, this is a great exercise, and I wish you the best of luck. I hope that I've been able to help you out.
P.s. One thing that bothers me about the localStorage plugin is that it uses model.attributes.xxx, where it's better to use model.get("xxx"). This way, if they ever change the internals of model change, your code won't break.
P.p.s. as far as how to generate guids, and weather a random guid is appropriate for you, depends upon your domain. With the TODO example, there is no backing database to sync with, so any old guid would do the trick, but in many other cases, you may be using html5 storage to create an offline version of your app, so id's need to be compatable with the db. In these cases, using a random guid is probably not that great an idea, but the one provided by the backbone-localstorage plugin is designed so that it isn't likely to collide with your db's actual id's, so it's not _that bad. There isn't really a right answer here, so do what makes sense for your domain.
You should probably look first at the local storage plugin that backbone.js provides. There are plenty of code samples that should give you an idea of how you would swap in jstorage as the underlying storage mechanism. See it here:
backbone-localstorage.js

Categories

Resources