I'm trying to use a class defined in a library but I only receive an error as a result.
[LibraryProject]/library/model/Update.gs
class Update {
constructor(obj = {}) {
if(typeof obj == "string"){
options = JSON.parse(obj);
}
Object.assign(this, obj);
}
text(){
return (this.message && this.message.text)?this.message.text:''
}
}
TASKS
✅ Create a new version of the project. (File > Manage versions...)
✅ Load this library in another project [Alias: CustomService] (Resources > Libraries...)
✅ Use functions of CustomService
❌ Use class of CustomService
If I try to use a Class
[NormalProject]/index.gs
function test (){
Logger.log(CustomService.libraryFunction())
var update = new CustomService.Update("");
Logger.log(update)
}
TypeError: CustomService.Update is not a constructor (línea 3, archivo "Code")
How can I instantiate an Object of this Class?
If I run...
Logger
As written in the official documentation,
Only the following properties in the script are available to library users:
enumerable global properties
function declarations,
variables created outside a function with var, and
properties explicitly set on the global object.
This would mean every property in the global this object are available to library users.
Before ES6, All declarations outside a function (and function declaration themselves) were properties of this global object. After ES6, There are two kinds of global records:
Object record- Same as ES5.
Function declarations
Function generators
Variable assignments
Declarative record - New
Everything else - let, const, class
Those in the declarative record are not accessible from the global "object", though they are globals themselves. Thus, the class declaration in the library is not accessible to library users. You could simply add a variable assignment to the class to add a property to the global object(outside any function):
var Update = class Update{/*your code here*/}
References:
Library official documentation
Global environment records
Related Answers:
ES6- What about introspection
Do let statements create properties on the global object
Based on your tests, it appears that you cannot directly import a class from a GAS library. I'd recommend creating a factory method to instantiate the class instead.
Something along these lines:
// Library GAS project
/**
* Foo class
*/
class Foo {
constructor(params) {...}
bar() {...}
}
/* globally accessible factory method */
function createFoo(fooParams) {
return new Foo(fooParams);
}
// Client GAS project
function test() {
var foo = FooService.createFoo(fooParams);
Logger.log(foo.bar());
}
Related
I'm in the process of converting a JavaScript project to Typescript.
I have an old module file that was require()-d from the main.js file of the project.
Here is the simplified example:
//required_module.js
const foo = require("bar");
module.exports = {
start(myvar) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type) {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type) {
return this.myvar.asd(type);
}
};
As you can see, this is just a simple JS module file (not a class), that uses this many times, and it just works as expected.
However as I try to convert this module to a TypeScript module without creating a class from it, I get different kind of errors at the this references as I try different approaches, like:
Object is possibly 'undefined'.ts(2532)
'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
An outer value of 'this' is shadowed by this container.
Is the only solution to create a class or TypeScript also allows using this outside of classes in a specific way?
Based on the helpful comments, the answer is:
You probably don't have to convert it to a class, but you'd be better off: you are going to run into issues dynamically adding properties (like myvar) or calling methods on untyped objects. Typescript doesn't want you treating JS objects as a random grab-bag of properties: there's already another data structure for that- Jared Smith
I'd focus less on converting to typescript and more on converting to modern module syntax. If you used named exports instead of object methods, you wouldn't have these issues. You would however have a stateful, static, module-scoped myvar variable (as you currently do), which should be avoided. Instead of the singleton, a class that can be instantiated multiple times might be the better approach.- Alex Wayne
The example, simply converted code would look like something like this in TypeScript:
import foo from 'bar';
export default {
myvar: 0,
start(myvar: number) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type: 'str1' | 'str2') {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type: string) {
//...
}
};
Is it possible for a factory function imported from a module to return a closure that has access to the importing scope? Struggling to understand this in the world of closures and IIFEs. See below
module.js
function makeClosure() {
return function() {
//I want to access variables from any context where makeClosure is called here
//I always know variable names here
//eg:
console.log(someVar)
}
}
export default makeClosure
anotherModule's.js
import makeClosure from './module.js'
let someVar = 'hello!'
let closure = makeClosure()
closure() //print 'hello!'
I get a working closure if makeClosure is defined in the module where it is being used, but not if importing from another module. The application in this case is to put a set of functions that operate on the Google Maps API in a module so I can reuse them with different map instances in different parts of my app.
I've come across a problem while trying to build a simple jQuery plugin, having to do with scopes I guess.
The problem in short: A class (A) creates an object (B), in which a property (C) is set to one of the class methods (D). How can I access class A's methods not contained inside the object (B) through the property ( C)?
Longer version (code below): I'm declaring an object (lets call it publicMethods) inside the plugin, comprised of a bunch of methods. These methods should be some default methods (declared inside the plugin), or user declared ones if the user has declared her own when initializing the plugin.
The idea is that when the user defines her own custom method, there should be some functions and variables accessible to her (like this.someVar) in that function.
This creates some limitations though.
I want the default methods to have access to some internal functions and variables, not contained inside the object publicMethods. But when I access these methods through the object they are inside, instead of calling them directly, I do not have access to another variables/functions not inside that object.
I'm trying to find a way to let the default methods have access to it's class siblings. I know I can do some conditional statements before calling the method (if it is user defined or not), or even declare a global variable pointing to "this", but I'd rather keep it clean.
var Plugin = function (opt) {
this.settings = $.extend({
"someVar" : "Value",
"someFunc" : null
});
this.anotherVar = "Hello World";
this.setPublic();
this.run();
}
Plugin.prototype = {
setPublic: function() {
this.publicMethods.someFunc = this.someFunc;
if ($.isFunction(this.settings.someFunc)) {
this.publicMethods.someFunc = this.settings.someFunc;
} else {
this.publicMethods.someFunc = this.someFunc;
}
},
someFunc: function(arg) {
return this.anotherVar; // returns type error the second time
},
run: function () {
this.someFunc();
this.publicMethods.someFunc();
}
}
From MDN: Function.prototype.bind():
The bind() method creates a new function that, when called, has its this keyword set to the provided value, [...].
So the following should work:
setPublic: function() {
this.publicMethods.someFunc = this.someFunc.bind(this);
if ($.isFunction(this.settings.someFunc)) {
this.publicMethods.someFunc = this.settings.someFunc.bind(this);
}
// This is redundant anyway:
/* else {
this.publicMethods.someFunc = this.someFunc.bind(this);
}*/
},
I'm trying to explore using ES6 classes instead of how we do it currently, using the Function.prototype means. Currently our API looks like:
var myclass = createClass('MyClass', {
test : function() {}
});
We iterate through the object and apply those properties onto the Function that we return, basically a prettier way than to do so that it's more inline with other programming languages of sorts:
function MyClass() {}
MyClass.prototype.test = function() {};
We also cache the class onto an object where name is the key and the function is the value for use throughout our application. The class name can be namespaced so you can have My.Cls and it will split by the period and then cache it onto the manager but it also can be retrieved via window.My.Cls.
Looking into ES6 classes, I don't see how I can keep the createClass function. Would love something like:
function createClass(name, config) {
return class name config;
}
I didn't expect it to work and it doesn't.
Two issues I have here:
How can I create a class using a variable as the class name?
How can I create a class and assign the properties via the config object argument?
Not sure this would be possible. We don't plan on keeping the createClass, we hope to keep it for now and upgrade our legacy "classes". I'd like to start using ES6 classes but not break the whole app for however long it'll take us to fully upgrade.
The only good upgrade route is to refactor the property hashes into proper classes. You can start that work and keep using your hash-based classes in the meantime, which will lighten the requirement to do it all at once.
If you have a limited number of "class" name:config pairs -- which you should for maintainability reasons -- then you can replace createClass with an implementation that does:
class Foo { ... }
class Bar { ... }
let classes = {'Foo': Foo, 'Bar': Bar};
function createClass(name, config) {
if (classes[name]) {
return classes[name];
}
// old impl
}
This will ignore the config if a "real" implementation exists, but keep using the legacy behavior if you haven't replaced the class. If it is, you can implement createClass more like:
function createClass(name, config) {
if (classes[name]) {
return new classes[name](config);
}
// old impl
}
and pass the config arguments into the class ctor. In this case, you may want to filter out function properties (methods) first, as the class probably implements them already. Something like:
function createClass(name, config) {
if (classes[name]) {
let fields = Object.keys(config).filter(key => {
return typeof config[key] !== 'function';
}).map(key => config[key]);
return new classes[name](fields);
}
// old impl
}
Export Objects {} vs Export function
I'm developing an exercise application, I came across to the question When do I need to exports an object {} instead of a function class?
Scenario example:
I'm building a simple authentication module using the object style.
// file auth.js
module.exports = {
login: function() {
// code login
},
logout: function() {
// code logout
},
register: function() {
// code register
}
}
Here I'm using the anonymous function style
module.exports = function() {
return {
login: function() {
// code login
},
logout: function() {
// code logout
},
register: function() {
// code register
}
}
}
Requiring
When I want to require this module I just do:
var auth = require('auth');
auth.login(); // trigger login function via object
auth().login() // trigger login function via function
It will work with both the approaches, but I'm confused to choose which fit better and why.
Questions
How do you understand in your design of a module, when is appropriate to exports, an object, anonymous function, named function to Instantiate?
Which are the difference and how the require method behave, when requiring these functions or Objects ?
How do you understand in your design of a module, when is appropriate to exports, an object, anonymous function, named function to Instantiate?
true minimalists aim to export a single function if that will suffice. This is based on the scope of your module and assumes a small and focused purpose.
Export an object of related functions when you need a set of functions to have a meaningful set of functionality. For example, a tempConversion module might have both cToF and fToC functions.
If you are doing OO, exporting the constructor function is fine.
a closure function that returns the real payload (which can be an object or a function) is useful for configuration options. For example var tip = require('computeTip')(18); could store 18 in closure and return a function that would calculate 18% tip when called with a number.
Here's a rule of thumb: if you export only one named function a la require('average').average(listOfNumbers), it's redundant, so just export the average function directly to make it more concise. If you have more than one function you want to expose, use an object whose properties are the functions.
Which are the difference and how the require method behave, when requiring these functions or Objects ?
There are no differences. require has a single behavior that accommodates each of these techniques.
In both your examples you actually return an object and not a function class. In the second case you get the object by executing the function. In order to use the function as a class you should return a function which acts as the constructor, and then instantiate with the new operator.
That being said, I would prefer an object over a class if I consider my module as a singleton, something that would be common to every file that would include it. An example would be a module that implements a common access layer to my database.
A class makes more sense if you intent to instantiate it multiple times. An example would be a specific model class for your database schema, like a User class.