In my code, I do the following (very simplified):
class AddOrSelectAddress {
static body; // <-- Error
static async open() {
await $.get(basePath + 'Manage/AddOrSelectAddress', null, result => {
this.body = document.createElement('div');
this.body.innerHTML = result;
});
// ...
}
static someOtherMethod() {
// do something with body
}
}
My code works fine in Chrome. Firefox, though, complaints an error in the second line of code:
SyntaxError: bad method definition
I'm relatively new to class-based JavaScript programming. What am I doing wrong here?
Static variables in JavaScript doesn't really help me, because it mainly uses old syntax.
Static class fields are a stage 3 proposal, meaning they're not yet an official part of the JavaScript language. (Stage 4 is the final stage.) You can read more about the proposal here and the proposal process here.
Currently, Chrome (as of version 72) is the only browser that supports static class fields.
To use this feature in other browsers you would need to use Babel with #babel/plugin-proposal-class-properties to transpile your code. If you're not already using Babel, however, this might be overkill.
Alternatively, you can assign a property to the class after initializing it. This isn't semantically identical, but works for your (and, indeed, most) use cases.
class AddOrSelectAddress {
// ...
}
AddOrSelectAddress.body = 'some initial value';
You can see this working in the below snippet.
class AddOrSelectAddress {
static changeBody(val) {
this.body = val;
}
static someMethod() {
console.log('in someMethod body is', this.body);
}
static someOtherMethod() {
console.log('in someOtherMethod body is', this.body);
}
}
AddOrSelectAddress.body = 'some initial value';
AddOrSelectAddress.someMethod();
AddOrSelectAddress.changeBody('some other value');
AddOrSelectAddress.someOtherMethod();
If you don't want to set an initial value for body then you could just omit the line (since accessing a nonexistent property of an object returns undefined), or you could explicitly set it to undefined.
Static methods are perfectly fine to use. However static properties are a recent addition that dont work in all browsers yet. It works in Chrome but like you said not in firefox. Please take a look at this article as it backs up my answer : https://javascript.info/static-properties-methods. To fix your issue you could declare the variable inside your static method.
Related
I'm trying to create a class with some fields on Google Apps Scripts. I can't even save the file. From what I understand of this SO answer, I'm using the correct syntax for class fields.
V8 runtime is enabled.
The error:
Syntax error: ParseError: Unexpected token = line: 5 file: Airtable_Class.gs
Line 5 is: foo = "bar";
Here's the whole code:
class FieldsTest{
foo = "bar";
}
This is a known issue. Add a star (★ on top left) to the issue, if you want this to be implemented.
https://issuetracker.google.com/issues/195752915
According to the tracker, it is supported, but it is blocked by the parser.
There's a way to simulate static fields in Apps Script. It involves using properties instead of a field. We can create a lazily initiated property that replaces itself with a field, using the following code:
class MyClass {
static get c() {
// Delete this property. We have to delete it first otherwise we cannot set it (due to it being a get-only property)
delete MyClass.c;
// Replace it with a static value.
return MyClass.c = new StaticObject();
}
}
This approach is better than using a static property, because it also works when instantiating static objects. To confirm this works, we can use the following:
SpreadsheetApp.getUi().alert(MyClass.c === MyClass.c)
This will only evaluate to true if the object was generated once and stored. If the field remains a property, it will return false, because the object is generated twice.
Although it seems that Google Apps Script doesn't support static class fields, it does support static methods/getters/setters, so you can do this:
class X {
// ⭐️ static getters & setters
static get a(){ return this._a || 0 } // this === X
static set a(value){ this._a = value } // this === X
}
then you can get/set X.a as usual:
X.a // get: 0.0
X.a = 3 // set: 3.0
X.a // get: 3.0
I'm just missing something (which is terrible considering the kind of thing I'm missing) about "this". I have a class for log formatting. In only one method, which is intended only for debugging/testing, "this" turns out to be undefined. I removed almost everything for demonstration purposes only. The code goes as following:
class LogFormatter {
constructor(source) {
// nothing relevant.
}
formatted() {
return 'returns a formatted string whatever';
}
clog(message, sub) {
console.log(this.formatted());
}
// made only for testing
showThis() {
console.log(this);
}
}
And it is used like this:
const
LogFormatter = require('log-formatter'),
lgf = new LogFormatter('asd');
lgf.clog('whatever');
If I access this from formatted method, or even by calling showThis, it works just fine. So it does not seems to be something related to console.log.
When I call clog method, the next error is thrown:
console.log(this.formatted());
^
TypeError: Cannot read property 'formatted' of undefined
So, only in clog method, "this" is undefined. So what's the elephant in front of my that I'm not seeing? :)
EDIT: Note that if I add a console.log(this) inside the formatted method, works fine too.
Also, I've noticed that if I -try to- create a new object from LogFormatter this way:
const
LogFormatter = new require('log-formatter')('whatever')
Throws an exception, saying that I cant execute LogFormatter without new keyword, which AFAIK, should work, given the fact that the class is the only thing being exported via module.exports = LogFormatter. Also, I did not have this problem using good old constructor functions syntax.
Thanks in advance :)
I have a CustomElement with the following constructor:
export default class SomeCustomElement extends HTMLElement {
constructor(templateId) {
super();
this.insertTemplateInstance(templateId);
}
...
}
I can register that Element in Chrome without any Problems.
But using Firefox with the polyfill loaded by webcomponents-loader.js from https://github.com/webcomponents/webcomponentsjs I get the ErrorMessage TypeError: Illegal constructor when calling super().
Does anybody know what is causing this?
Some more Background:
Registering of the custom Elements happens like this:
window.addEventListener("WebComponentsReady", function () {
customElements.define(elementName, SomeCustomElement);
});
Use webcomponents-lite.js instead of webcomponent-loader.js if you don't want to have this kind of error, which is caused by the fact that the polyfills will be loaded asynchronously if you use webcomponents-loader.js.
The example below works fine with Firefox (and every modern browser):
class SomeCustomElement extends HTMLElement
{
constructor()
{
console.log( 'created' )
super()
}
connectedCallback() {
console.log( 'connected' )
this.innerHTML = "Hello"
}
}
customElements.define( 'c-e', SomeCustomElement )
<script src=https://rawgit.com/webcomponents/webcomponentsjs/master/webcomponents-lite.js></script>
<c-e></c-e>
However if you still want to use webcomponents-loader.js, you'll have to insert your custom element definition in an external file, and load it with HTML Imports:
<link rel="import" href="my-element.html">
Caveat upfront: I'm not a huge fan of html imports. I stumbled across this trying to get ES 6 class-based custom elements to work in Firefox. For a conditional-polyfill-loading-no-html-import solution based on the accepted answer, read on...
To conditionally load the polyfills gets a little tricky. Per #Supersharp's answer/comments, for some reason the polyfill must be loaded synchronously (despite there being no mention of this in the official documentation). So now you have two unappealing options: include it unconditionally to get the necessary synchronous loading or...use document.write:
<script>
;(function() {
var str = '';
// You could make this more fine-grained if desired by doing
// more feature detection and loading the minimal polyfill file
if (!window.customElements) str += '<script src="./node_modules/#webcomponents/webcomponentsjs/webcomponents-lite.js"></script>';
str += '<script src="./elements.js"></script>';
document.write(str);
})();
</script>
<foo-bar></foo-bar>
Then in elements.js:
class FooBar extends HTMLElement {
constructor () {
console.log("constructing");
super();
}
connectedCallback () {
console.log("connecting");
}
disconnectedCallback () {
console.log("disconnecting");
}
};
// Note that because of the synchronous loading we don't
// need to listen for the event
customElements.define('foo-bar', FooBar);
document.write is widely disliked for good reasons but this is IMHO a legitimate use case. Note that most of the objections here (no pre-fetch, etc.) can be ameliorated through use of service workers (for browsers that support them).
I'm trying to throw a custom error with my "CustomError" class name printed in the console instead of "Error", with no success:
class CustomError extends Error {
constructor(message: string) {
super(`Lorem "${message}" ipsum dolor.`);
this.name = 'CustomError';
}
}
throw new CustomError('foo');
The output is Uncaught Error: Lorem "foo" ipsum dolor.
What I expect: Uncaught CustomError: Lorem "foo" ipsum dolor.
I wonder if that can be done using TS only (without messing with JS prototypes)?
Are you using typescript version 2.1, and transpiling to ES5? Check this section of the breaking changes page for possible issues and workaround: https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
The relevant bit:
As a recommendation, you can manually adjust the prototype immediately after any super(...) calls.
class FooError extends Error {
constructor(m: string) {
super(m);
// Set the prototype explicitly.
Object.setPrototypeOf(this, FooError.prototype);
}
sayHello() {
return "hello " + this.message;
}
}
However, any subclass of FooError will have to manually set the prototype as well. For runtimes that don't support Object.setPrototypeOf, you may instead be able to use __proto__.
Unfortunately, these workarounds will not work on Internet Explorer 10 and prior. One can manually copy methods from the prototype onto the instance itself (i.e. FooError.prototype onto this), but the prototype chain itself cannot be fixed.
The problem is that Javascript's built-in class Error breaks the prototype chain by switching the object to be constructed (i.e. this) to a new, different object, when you call super and that new object doesn't have the expected prototype chain, i.e. it's an instance of Error not of CustomError.
This problem can be elegantly solved using 'new.target', which is supported since Typescript 2.2, see here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html
class CustomError extends Error {
constructor(message?: string) {
// 'Error' breaks prototype chain here
super(message);
// restore prototype chain
const actualProto = new.target.prototype;
if (Object.setPrototypeOf) { Object.setPrototypeOf(this, actualProto); }
else { this.__proto__ = actualProto; }
}
}
Using new.target has the advantage that you don't have to hardcode the prototype, like some other answers here proposed. That again has the advantage that classes inheriting from CustomError will automatically also get the correct prototype chain.
If you were to hardcode the prototype (e.g. Object.setPrototype(this, CustomError.prototype)), CustomError itself would have a working prototype chain, but any classes inheriting from CustomError would be broken, e.g. instances of a class VeryCustomError < CustomError would not be instanceof VeryCustomError as expected, but only instanceof CustomError.
See also: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
As of TypeScript 2.2 it can be done via new.target.prototype.
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example
class CustomError extends Error {
constructor(message?: string) {
super(message); // 'Error' breaks prototype chain here
this.name = 'CustomError';
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
}
}
It works correctly in ES2015 (https://jsfiddle.net/x40n2gyr/). Most likely, the problem is that the TypeScript compiler is transpiling to ES5, and Error cannot be correctly subclassed using only ES5 features; it can only be correctly subclassed using ES2015 and above features (class or, more obscurely, Reflect.construct). This is because when you call Error as a function (rather than via new or, in ES2015, super or Reflect.construct), it ignores this and creates a new Error.
You'll probably have to live with the imperfect output until you can target ES2015 or higher...
I literally never post on SO, but my team is working on a TypeScript project, and we needed to create many custom error classes, while also targeting es5. It would have been incredibly tedious to do the suggested fix in every single error class. But we found that we were able to have a downstream effect on all subsequent error classes by creating a main custom error class, and having the rest of our errors extend that class. Inside of that main error class we did the following to have that downstream effect of updating the prototype:
class MainErrorClass extends Error {
constructor() {
super()
Object.setPrototypeOf(this, new.target.prototype)
}
}
class SomeNewError extends MainErrorClass {}
...
Using new.target.prototype was the key to getting all of the inheriting error classes to be updated without needing to update the constructor of each one.
Just hoping this saves someone else a headache in the future!
I ran into the same problem in my typescript project a few days ago. To make it work, I use the implementation from MDN using only vanilla js. So your error would look something like the following:
function CustomError(message) {
this.name = 'CustomError';
this.message = message || 'Default Message';
this.stack = (new Error()).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;
throw new CustomError('foo');
It doesn't seem to work in SO code snippet, but it does in the chrome console and in my typescript project:
I was having this problem in a nodejs server. what worked for me was to transpile down to es2017 in which these issues seems to be fixed.
Edit tsconfig to
"target": "es2017"
Try this...
class CustomError extends Error {
constructor(message: string) {
super(`Lorem "${message}" ipsum dolor.`)
}
get name() { return this.constructor.name }
}
throw new CustomError('foo')
As of Meteor 0.9.4, defining Template.MyTemplate.MyHelperFunction() is no longer valid.
We deprecated the Template.someTemplate.myHelper = ... syntax in favor of Template.someTemplate.helpers(...). Using the older syntax still works, but it prints a deprecation warning to the console.
This seemed fine to me, as it would (at the least) save some mis-typing and duplicated text. However, I soon discovered that the way I was building Meteor apps had leaned on an ability that this new version has deprecated. In my apps, I've been defining helpers/functions with the old syntax, then calling those methods from other helpers. I found it helped me keep my code clean and consistent.
For example, I might have a control like this:
//Common Method
Template.myTemplate.doCommonThing = function()
{
/* Commonly used method is defined here */
}
//Other Methods
Template.myTemplate.otherThing1 = function()
{
/* Do proprietary thing here */
Template.myTemplate.doCommonThing();
}
Template.myTemplate.otherThing2 = function()
{
/* Do proprietary thing here */
Template.myTemplate.doCommonThing();
}
But this does not appear to be available with the new method Meteor suggests (which makes me think I was wrong all along). My question is, What is the preferred way to share common, template-specific logic between a template's helpers?
Sorry if I'm being dull, but couldn't you declare the function as an object and assign it to multiple helpers? For instance:
// Common methods
doCommonThing = function(instance) // don't use *var* so that it becomes a global
{
/* Commonly used method is defined here */
}
Template.myTemplate.helpers({
otherThing1: function() {
var _instance = this; // assign original instance *this* to local variable for later use
/* Do proprietary thing here */
doCommonThing(_instance); // call the common function, while passing in the current template instance
},
otherThing2: function() {
var _instance = this;
/* Do some other proprietary thing here */
doCommonThing(_instance);
}
});
By the way, if you notice you're constantly duplicating the same helpers across multiple templates, it might help to use Template.registerHelper instead of assigning the same function to multiple places.