Skip to content

Commit 516e074

Browse files
committed
more explanation + sample library
1 parent d320e2c commit 516e074

File tree

1 file changed

+174
-11
lines changed

1 file changed

+174
-11
lines changed

README.md

+174-11
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,190 @@
11
# modularLibraries.gs
22

3-
A Google Apps Script solution for library management.
3+
A native Google Apps Script solution for library creation, usage, and development.
44

5-
This repo consists of:
5+
This readme consists of:
6+
7+
- Documentation of how it works:
8+
- The global `Import` namespace
9+
- Package instantiation and namespacing
10+
- How to write your own libraries following this pattern
611

7-
- Foundational code that enables modular libraries
8-
- Import.gs provides the scaffolding needed for library usage and creation
9-
- Requests.gs allows you to interact with Google APIs, external APIs
1012
- Sample libraries which extends Google APIs, by using the foundational code above
13+
14+
- Sample HelloWorld library
15+
- Requests.gs is a heavy wrapper around UrlFetchApp, which allows granular control of Google API interactions
1116
- Sheets.gs is a light wrapper around the Spreadsheet APIs
1217
- SheetsDB.gs extends Sheets.gs by introducing sessions
1318

14-
All aspects are well documented, including tutorials.
19+
- How to write your own libraries
20+
21+
22+
## Motivation
23+
24+
I love the Python/Github ecosphere but I have to use AppsScripts at work. In particular, **D**on't **R**epeat **Y**ourself. Be able to do reuse code that solves particular problems, but you only have to understand how to interact with it. The library should just encapsulate a particular functionality.
25+
26+
I use these libraries for several daily scripts and for AppMaker projects.
27+
28+
## HelloWorld
29+
30+
The sample HelloWorld library illustrates the essential features. Interact with it like this:
31+
32+
```js
33+
function myFunction() {
34+
var lib, chi;
35+
lib = Import.HelloWorld();
36+
lib.sayHi(); // Hello, World
37+
chi = Import.HelloWorld.chinese({
38+
noun: 'shijie' // shijie = world in chinese
39+
});
40+
chi.sayHi(); // Ni hao, shijie
41+
}
42+
```
43+
44+
## Documentation
45+
46+
### Terms
47+
48+
The library is the code that the library writer exposes to the end developer. The package is the wrapped library. We have to instantiate the package in order to get a library instance. The package can accept configuration settings which are passed to the library, and the package can also derive namespaces. These are explained below.
49+
50+
51+
### The `Import` namespace
52+
53+
When you use any of the modular libraries in this repo, you gain the the `Import` global variable which acts as a namespace to access the library you pasted in. Any library pasted in will add a property onto the `Import` variable, and is the entry point to accessing and interacting with the target library. The name of the property added is defined in the library itself in the packaging code (#2 below in "Anatomy of a Library).
54+
55+
The library references are only guaranteed to be available in the `Import` namespace from within a function that is called in the editor, or invoked through triggers, etc.
56+
57+
#### Using `Import` Examples
58+
59+
Copying and pasting a SampleLibrary.gs example:
60+
61+
```js
62+
function MyFunction () {
63+
var lib, ss;
64+
65+
// instantiate a package with default settings
66+
lib = Import.SampleLibrary();
67+
lib.sayHi(); // 'hi'
68+
69+
// instantiate a package with passed configuration (long form)
70+
lib = Import.SampleLibrary({
71+
config: {
72+
lang: 'ch'
73+
}
74+
});
75+
lib.sayHi(); // 'ni hao'
76+
77+
// instantiate a package from a creator method (short form)
78+
lib = Import.SampleLibrary.create({
79+
lang: 'ch'
80+
});
81+
lib.sayHi(); // 'ni hao'
82+
83+
// instantiate a package from a creator method
84+
ss = Import.SampleLibrary.fromId('<spreadsheetId>');
85+
ss.withSheet('Sheet1', function (sheet) {
86+
sheet.write('B3', ['b3']);
87+
sheet.write('B4', ['b4']);
88+
});
89+
}
90+
```
91+
92+
93+
94+
### Anatomy of a Library
95+
96+
All libraries can only be one file long. They all follow a boilerplate pattern, and any of them will provide the project in which they are copied into with the global `Import` variable.
97+
98+
A modularLibrary.gs library, in code, consists of:
99+
100+
1. `(function (global, name, Package, helpers, creators) { /* code */ } (this, `
101+
2. `"SampleLibrary",`
102+
3. `function Package_(config) { return {}; },`
103+
4. `{ /* helpers */ },`
104+
5. `{ /* creators */ }`
105+
6. `)`
106+
107+
Explanation:
108+
109+
1. The first part is the Import.gs code that gives the project the `Import` global. The `/* code */` portion is about 50 lines of code that can be found in the package. Notice that trailing `(this,` which gets passed as the `global` parameter in the anonymous function. This is how Import.gs gains access to the global context.
110+
2. The name of the package, which is used to expose the entry point into the library in the project as `Import.SampleLibrary`. This is passed to Import.gs in the `name` variable.
111+
3. The package itself, which is just a function that takes one variable, `config`. When the entry point to the library is invoked, such as `Import.SampleLibrary({config: {}})` this function is run with `this` keyword referring to itself and contents of `config` are passed to it.
112+
4. The first of two convenience functions: helper functions. These are intended to be functions that are self-contained that the package itself uses to implement its features, but also intended to be exposed to the project as well. These functions have no access to the library or to any of the other entry points, for these are on the library instance, or `Import.SampleLibrary().helper()`.
113+
5. The second of two convenience functions: the creators. These are methods that are used to instantiate the library itself, such as `Import.SampleLibrary.create`
114+
115+
### Instantiation
116+
117+
Using these libraries requires the developer to copy and paste into their project. Then, you use either `Import.SampleLibrary` that resolved into a library reference, or `Import.SampleLibrary({config:{}})` that returns a library instance initiated with configuration options, which is how libraries expose its features and APIs.
118+
119+
Libraries can define convenient methods for instantiation. These **creator methods** are a convienient method that lives on the top-level library reference. They are typically called `.fromX` or just `.create`. For example:
120+
121+
```js
122+
var ss = Import.Sheets.fromId('<spreadsheetid>');
123+
var ce = Import.CustomErrors.create('Custom Error');
124+
```
125+
126+
These creator methods are the preferred way to instantiate things. The first one creates an object and have a longer-form equivalents:
127+
128+
```js
129+
var ss = Import.Sheets({
130+
config: {
131+
spreadsheetId: '<spreadsheetid>'
132+
}
133+
});
134+
var ce = Import.CustomErrors({
135+
config: {
136+
name: 'Custom Error'
137+
}
138+
});
139+
```
140+
141+
An important concept is remembering a **library reference** is what is provided as a property of the `Import` global. There is nothing exposed there except for creator functions. The developer writing a creator has the `this` keyword which is itself a library reference. When you invoke (or call) the library reference you end up with a **library instance** which exposes the core functionality (usually with an object, but can be anything). The developer writing the library returns that object in the Package function.
142+
143+
### Configuration Options
144+
145+
146+
147+
### Namespacing
148+
149+
The `Import` variable also has other powers: You can build namespaces with it for use in your application:
150+
151+
```js
152+
var app = {};
153+
app.libraries = {};
154+
Import.SampleLibrary({
155+
base: app.libraries,
156+
attr: lib1,
157+
config: {}
158+
});
159+
Import.OtherLibrary({
160+
base: app.libraries,
161+
attr: lib2,
162+
config: {}
163+
});
164+
```
165+
166+
Now you have `app.libraries.lib1` and `app.libraries.lib2`.
167+
168+
An interesting feature of namespace though, is that you can actually define global variables if you choose to do so. You do that using the `namespace` property.
169+
170+
```js
171+
Import.SampleLibrary({
172+
namespace: 'app.libraries.lib1',
173+
config: {}
174+
});
175+
```
176+
177+
The `config` property is passed to the library itself as the first parameter in the library definition.
15178

16-
## Quickstart
179+
## Writing a library
17180

18-
To use the code here, the idea is to copy and paste from the Code section below into your project. A forthcoming package manager solution will automate this process. Note that some of these libraries have dependencies on other modular libraries.
181+
### Helper methods
19182

20-
Note that Import.gs isn't a typical "library" but is boilerplate code that enables the `Import` global and its functionality. The other libraries are built using that boilerplate.
183+
The other kind of convenient methods are *helper methods* that live on the library instance and are availble from within the library code as well. This is how developers can write a useful function inside their package and expose it to the end developer. For example the CustomErrors.gs library contains a useful function that it uses to derive stack information, which it uses to provide features. It is exposed as a helper function
21184

22185
## Code
23186

24-
### Foundational:
187+
### Foundational Libraries:
25188

26189
Import.gs below is intended to be used as framework for writing a library, and Requests.gs is a very versatile library for interacting with endpoints, including Google ones.
27190

@@ -33,7 +196,7 @@ A Google Apps Script solution to writing and using modular libraries so that app
33196

34197
A modular library for Google Apps Scripting wrapping `UrlFetchApp`. It also has support for interacting with Google APIs via the Discovery service, and support for concurrent processing. [[Link](https://github.com/classroomtechtools/modularLibraries.gs/blob/master/Requests)]
35198

36-
### Testing and Debugging:
199+
### Testing and Debugging Libraries:
37200

38201
Ideally modular libraries need to have unit tests that come along with the project. Tests are useful for building out improvements, and can even serve as useful insight into how the library works.
39202

0 commit comments

Comments
 (0)