Is adding another method to a class no longer allowed in JavaScript? - javascript

So I was playing around in typescriptlang.org/play, writing a class of Plane with a method of pilot(): void {} and I pasted the JS code inside my Chrome console and played around with that for a minute.
Then I wanted to put into practice the concept of being able to add another method to class Plane {}. So this is what I had on the TypeScript side:
class Plane {
color: string = 'red’;
pilot(): void {
console.log(‘swoosh’);
}
takeoff(): void {
console.log(‘ready for takeoff’);
}
}
This is JS version:
class Plane {
constructor() {
this.color = 'red';
}
pilot() {
console.log(‘swoosh’);
}
takeoff() {
console.log(‘ready for takeoff’);
}
}
When I pasted that into Chrome console I got Uncaught SyntaxError: Identifier 'Plane' has already been declared.
Okay, so how do I add a new method then? I should be able to easily attach as many methods to prototype as I want. Is it because the term prototype does not exist in my JS code?

class functions like const or let in JS-land: it can't be re-declared. When I'm pasting code in the console that uses those terms, I generally refresh the page each time.
But happily, new releases of Chrome are letting you re-declare let and const within the console. I don't know if this will ever extend to class.
Note that you can, indeed, add a line like Plane.prototype.foo = function() {} after Plane has been declared, and this will work as you'd expect.

It seems like you are pasting the JavaScript code into a console that already has your Plane class defined.
If you already pasted the class definition once you cannot redeclare it by pasting an edited version again, you need to refresh your page or just open a console in an empty tab to get a fresh environment.
If though you want to experiment with adding a method to an existing class you can do so like this:
// You start by defining the original class (or picking one of the defined ones like Date)
class Plane {
// Your class definition
getName() { return 'I am a plane'; }
}
// Then somewhere later in your code (or console)
Plane.prototype.sayHello = function() {
return 'Hello ' + this.getName();
}
// And finally
const plane = new Plane();
plane.sayHello(); // Will return Hello I am a plane

Related

Vaadin 14 Springboot javascript does not work

I'm trying to develop my first Vaadin 14 application, I'm also using Spring boot framework for it.
I've created a javascript file, I put it into the folder frontend/src with the name of teams.js and I'm trying to import it with #JsModule("src/teams.js"). My root view class looks like this:
#Route("")
#PageTitle("Teams organization store init")
#Slf4j
#JsModule("src/teams.js")
#Tag("TeamsEntry")
public class TeamsEntryView extends VerticalLayout implements BeforeEnterObserver {
public TeamsEntryView() {
initTeams();
}
private void initTeams() {
var ui = UI.getCurrent();
var page = ui.getPage();
log.info("Teams initialization...");
page.executeJs(getTeamsConfig()).toCompletableFuture().whenComplete(((jsonValue, throwable) ->
log.info("Teams initialization completed: {} with throwable {}", jsonValue.toJson(), throwable.getMessage())
));
}
private String getTeamsConfig() {
return """
console.log('ss');
window.initTeams();
console.log('xx');
return true;
""";
}
...
My js file looks like this:
window = {
initTeams: function () {
console.log('initTeams...');
}
}
In this case, I see "ss" in the browser's console, but nothing more.
If I remove the window.initTeams(); line I get "ss" and "xx" as well.
If I declare a simple function in the js file and call it without the "window" class I get similar results.
If I use #Javascript or page.addJavascript("src/teams.js") I get this error when the page loads: "> unexpected character"
If I try to call join() or get() on the completable future the browser freeze.
If I use #Javascript("frontend://src/teams.js") I get some CORS error like if it is trying to download something from the "frontend" host.
I've tried to put the #JsModule on a component instead of my view.. it does not work.
I've tried to put my js file to the root, and to the resources folder.
I could not find any other solution to import and use my js file into vaadin14 with spring boot.
Also, I'm not sure why the browser freeze if I call "join()" on completable future, and also the on the result of it sentToBrowser returns false even if I see the console logs in the browsers log...
Can somebody explain to me how should I do the javascript import, why my current code does not work, and why the "join()" freezes the browser, please?
Thank you in advance!
#Edit
I have also tried with this annotation #JavaScript("./src/teams.js") and a js like this:
function initTeams () {
console.log('initTeams...');
console.log("Teams initialized!")
}
#Edit
Okay so finally I got it working.
The js file has to be under root/src/main/webapp/src folder.
Nor #JavaScript and nor the #JsModule worked for me, so I had to import my js file as:
var ui = UI.getCurrent();
var page = ui.getPage();
page.addJavaScript("src/teams.js");
Then I can call it like window.initTeams() or like initTeams() and both works fine. Altough the completable future below still never executes, and isSentToBrowser() always returns false.
page.executeJs(getTeamsConfig()).toCompletableFuture().whenComplete(((jsonValue, throwable) ->
log.info("Teams initialization completed: {} with throwable {}", jsonValue.toJson(), throwable.getMessage())
));
I must mention if I start the path with '.' like page.addJavaScript(".src/teams.js"); then it does not find the file.
Does anybody have an answer, why the completable future never executes?
The problem is that the following code redefines the window object:
window = {
initTeams: function () {
console.log('initTeams...');
}
}
Did you meant to add a function to the window object? Like so:
window.initTeams = function () {
console.log('initTeams...');
};
If you want to keep code visually similar to yours:
window = {
...window,
{
initTeams: function() {
console.log('initTeams...');
}
}
}
Other options:
window['initTeams'] = function() {...}
Object.assign(window, {initTeams: function() {...}})
Object.defineProperty(window, 'initTeams', {value: function() {console.log('foo')}});
Gotta love JavaScript...
Also, for more knowledge, the code mentioned in your #Edit section could not be called. Calling initTeams() is equivalent to window.initTeams(). The function must exist in the window object. This is why, for example, you see some custom elements defined like customElements.define(...) and window.customElements.define(...).

Equivalent of exportFunction or cloneInto for classes?

The title says it all. I'm trying to inject a class into the global object / default scope of affected webpages, from a WebExtension. (I have no objections to supporting Chrome; however, I'm tagging this question as Firefox-specific for now, since it seems that Chrome does not yet support this use-case at all.)
For functions, exportFunction() may be used to inject functions into the global object.
For Objects, cloneInto() may be used.
However, neither of these seemed to work for a class.
Here is a sample content_script that I've been using for troubleshooting:
console.log("Content script initializing...");
function patch_function(f, name, dest=window) {
exportFunction(f, dest, {defineAs: name});
console.log(`Patched function ${name} into window!`);
}
function patch_object(o, name, dest=window) {
window.wrappedJSObject[name] = cloneInto(o, dest, {cloneFunctions: true});
console.log(`Patched object ${name} into window!`);
}
let basicObject = {'hello': 'world'};
class MyClass {
constructor(x=7) {
this.x = x;
}
return_x() {
return this.x;
}
}
console.log("Patching...");
// This works PERFECTLY from the webpage...
patch_object(basicObject, 'basicObject');
// ...but neither of these works! Swap the comment to try out the other one
//patch_function(MyClass, 'MyClass');
patch_object(MyClass, 'MyClass');
console.log("Content script done!")
In particular, running a statement such as (new MyClass(3)).return_x() yields Permission denied to access property "return_x" no matter how it was injected!
How can I inject a custom class (which — crucially — might even call WebExtension-privileged code) into a webpage from a WebExtension? (Or, if not possible: what is the tracker # for this — I can't currently find it.)

Google Chrome Snippets: Identifier '...' has already been declared

I'm using Google Chrome's Snippets (inside Dev Tools) for some JS development and testing.
When declaring ES6 classes, the console throws an
Uncaught SyntaxError: Identifier 'Foo' has already been declared at...
after the first time it was run.
class Foo {
constructor(val) {
this.bar = val;
}
}
var f = new Foo('myVal');
console.log(f.bar); // prints 'myVal'
I did some research and found out that wrapping the code in a block scope with {} would help in avoiding this problem.
But when I do this, although the code runs without error, Chrome doesn't recognize any subsequent edits that I may do to my code.
So, if I changed the above code to the following:
{
class Foo {
constructor(val) {
this.bar = val;
}
}
}
var f = new Foo('myVal');
console.log(f.bar); // prints 'myVal'
So far everything works fine.
Now, I realize that there is a problem with my class and I change the code to the following:
{
class Foo {
constructor(val) {
this.bar = 'MyHardcodedVal'; // Here is the changed code
}
}
}
var f = new Foo('myVal');
console.log(f.bar); // STILL prints 'myVal'
As you can see, the edits I made to my class are not taking effect.
It appears that Google Chrome has put my code in a sandbox that is immune from my editing.
A way to look behind the scene and see what Google Chrome is doing is to introduce an intentional mistake into the code and then click on the source of the mistake that Chrome shows in the console. There you will see that the code for the class is still the old one and not changed at all, while the code that has been outside of the block scope is up to date.
I could always close the tab I am working in and open it again, but that isn't practical.
What am I doing wrong?
Is there a sane way to use Snippets for such tests?
Hope that makes sense!
Based off the comments, it sounds like the solution is to wrap the entire snippet in braces.
{
class Foo {
constructor(val) {
this.bar = 'MyHardcodedVal'; // Here is the changed code
}
}
var f = new Foo('myVal');
console.log(f.bar); // STILL prints 'myVal'
}

Why is 'this' undefined in the debugger but printable in a console.log?

When setting a breakpoint on the console.log statement, why would this be undefined in the debugger but the statement print with no issues? Am I missing something in regards to scope here?
export class OptionsSidebarComponent {
...
public filters: Object = new Object();
...
public generateFilters() {
for (let property in this.terms) {
if (this.terms.hasOwnProperty(property) && this.terms[property] !== null) {
if (!this.filters.hasOwnProperty(this.productGroup.id)){
this.filters[this.productGroup.id] = new Filters();
}
this.terms[property].forEach((term) => {
console.log(this.filters);
});
}
}
}
}
With typescript While debugging, keep in mind that transpilers can rename variables. Using the original name in the console without sourcemaps that include renaming will show you undefined even if the original value isn't. Make sure you use the transpiled name in watches or console commands.
When you're referencing this with your console.log statement inside its own class, you're using it in its relevant scope. Depending on the framework you are using, different terms are used to reference your class... AngularJS used $scope- Angular 2+ uses this to reference the current class (or current scope/context).
When you use this in your debugger you're using it outside of its scope. It no longer references the class you intend it to.
Each one of your components in Angular uses this to reference itself. Here's an article to explain in further detail: https://angular-2-training-book.rangle.io/handout/features/refresher_on_this.html
If we simplify it to basic javascript and look at your class as just a function a good example would be this:
function example(){
var anything = 10;
console.log(anything); //output: 10
}
console.log(anything); //undefined! we're out of scope :)
So looking at it from this perspective, this is nothing magical. It's just provided to us by Angular to make our references to other things inside our components easier.

javascript: dynamically create class instances

I am writing an app that has features that can be turned on and off via a config.json that looks something like this:
"appFeatures": {
"header": {
"create": true,
"title": "Here Comes the Sun"
},
"imageStrip": {
"create": false,
"imageDirectory": "../data/images",
"imageDimensions": [288, 162]
},
"areaChart": {
"create": true
},
"axes": {
"create": true
}
}
For each feature there is already a corresponding class of the same name that implements the feature. I'd like to use the name of the feature to create a new instance of the class. After fetching the config.json, I have code (within a Main.js class) that looks like:
this.features = Object.entries(this.config.appFeatures)
.filter((entry) => {
return entry[1].create === true;
});
this.features.forEach((feature) => { this.createFeatureInstances(feature[0]); });
And then I try to create instances, a la this.header = new Header():
createFeatureInstances(featureName) {
const className = `${featureName.replace(featureName[0], featureName[0].toUpperCase())}`;
this[`${featureName}`] = new Function(`
return class ${className} {}
`)();
This creates a new, empty Header class and, I suppose, it's instance. It is not the Header class that I have already written and want to create an instance for. How might I write the createFeatureInstances function so that I can create the instance of each class that corresponds to a feature?
EDIT Because new features may be added to this app in the future by others, I would like to minimize the times that I hard code which features are available to the app. With my current design, another developer can add another feature by writing a new feature class, importing that class into the Main.js class, and pop the config entries into the config .json without having to touch anything else in the code. For this reason, solutions like this one: Create an instance of a class in ES6 with a dynamic name? won't give me a solution because they rely on having a complete list of the classes that should already exist.
You need to have a name-class mapping somewhere. Factory function or somewhere else is your call. Your current solution lacks this, which is causing the problem:
...creates a new, empty Header class and, I suppose, it's instance. It is not the Header class that I have already written and want to create an instance for
Some explanation with a simple example
// Imagine you've defined Test
class Test {
callMe() {}
}
// And your consumer wants a className Test to be created
const className = "Test";
// This is a simple map of the name to class
const nameToClass = {
"Test": Test
}
// This creates a new class called Test, not your defined class
const AttemptedDynamicTest = new Function(`
return class ${className} {}
`)();
// The next two logs just prove the previous comment
console.log(Test === AttemptedDynamicTest); // false
console.log(typeof AttemptedDynamicTest.prototype.callMe); // undefined
// What you really need is to maintain a map, and just use it to dynamically
// create a new instance every time
console.log(typeof nameToClass[className].prototype.callMe); // function
You can use a string to initialize a cooresponding (valid and existing) class using the following snippet:
var dynamicClassInstance = new this[classNameString]();

Categories

Resources