simplyview 3.1.4 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simplyview",
3
- "version": "3.1.4",
3
+ "version": "3.3.0",
4
4
  "description": "Library to rapidly build UI components, using declarative tools",
5
5
  "main": "src/everything.mjs",
6
6
  "type": "module",
package/src/action.mjs CHANGED
@@ -1,17 +1,66 @@
1
- export function actions(options, optionsCompat) {
1
+ export function actions(options, optionsCompat)
2
+ {
2
3
  if (optionsCompat) {
3
4
  let app = options
4
5
  options = optionsCompat
5
6
  options.app = app
6
7
  }
8
+
7
9
  if (options.app) {
10
+ const waitHandler = {
11
+ apply(target, thisArg, argumentsList)
12
+ {
13
+ try {
14
+ const result = target(...argumentsList)
15
+ if (result instanceof Promise) {
16
+ options.app.hooks.wait(true)
17
+ return result.finally(() => {
18
+ options.app.hooks.wait(false, target)
19
+ })
20
+ }
21
+ return result
22
+ } catch(err) {
23
+ }
24
+ }
25
+ }
26
+
27
+ const functionHandler = {
28
+ apply(target, thisArg, argumentsList)
29
+ {
30
+ try {
31
+ const result = target(...argumentsList)
32
+ if (result instanceof Promise) {
33
+ if (options.app.hooks.wait) {
34
+ options.app.hooks.wait(true, target)
35
+ return result.catch(err => {
36
+ return options.app.hooks.error(err, target)
37
+ })
38
+ .finally(() => {
39
+ options.app.hooks.wait(false, target)
40
+ })
41
+ } else {
42
+ return result.catch(err => {
43
+ return options.app.hooks.error(err, target)
44
+ })
45
+ }
46
+ }
47
+ return result
48
+ } catch(err) {
49
+ return options.app.hooks.error(err, target)
50
+ }
51
+ }
52
+ }
53
+
8
54
  const actionHandler = {
9
- get(target, property) {
55
+ get(target, property)
56
+ {
10
57
  if (!target[property]) {
11
58
  return undefined
12
59
  }
13
- if (target.catch) {
60
+ if (options.app.hooks?.error) {
14
61
  return new Proxy(target[property].bind(options.app), functionHandler)
62
+ } else if (options.app.hooks?.wait) {
63
+ return new Proxy(target[property].bind(options.app), waitHandler)
15
64
  } else {
16
65
  return target[property].bind(options.app)
17
66
  }
@@ -21,20 +70,4 @@ export function actions(options, optionsCompat) {
21
70
  } else {
22
71
  return options
23
72
  }
24
- }
25
-
26
- const functionHandler = {
27
- apply(target, thisArg, argumentsList) {
28
- try {
29
- const result = target(...argumentsList)
30
- if (result instanceof Promise) {
31
- return result.catch(err => {
32
- return thisArg.catch(err)
33
- })
34
- }
35
- return result
36
- } catch(err) {
37
- return thisArg.catch(err)
38
- }
39
- }
40
73
  }
package/src/activate.mjs CHANGED
@@ -1,3 +1,7 @@
1
+ if (!Symbol.onDestroy) {
2
+ Symbol.onDestroy = Symbol('onDestroy')
3
+ }
4
+
1
5
  const listeners = new Map()
2
6
 
3
7
  export const activate = {
@@ -31,7 +35,12 @@ function callListeners(node) {
31
35
  const activate = node?.dataset?.simplyActivate
32
36
  if (activate && listeners.has(activate)) {
33
37
  for (let callback of listeners.get(activate)) {
34
- callback.call(node)
38
+ const onDestroy = callback.call(node)
39
+ if (typeof onDestroy == 'function') {
40
+ node[Symbol.onDestroy] = onDestroy
41
+ } else if (typeof onDestroy != 'undefined') {
42
+ console.warn('activate listener may only return a de-activate function, instead got', onDestroy)
43
+ }
35
44
  }
36
45
  }
37
46
  }
@@ -42,13 +51,26 @@ function handleChanges(changes) {
42
51
  if (change.type == 'childList') {
43
52
  for (let node of change.addedNodes) {
44
53
  if (node.querySelectorAll) {
45
- var toActivate = Array.from(node.querySelectorAll('[data-simply-activate]'))
54
+ let toActivate = Array.from(node.querySelectorAll('[data-simply-activate]'))
46
55
  if (node.matches('[data-simply-activate]')) {
47
56
  toActivate.push(node)
48
57
  }
49
58
  activateNodes = activateNodes.concat(toActivate)
50
59
  }
51
60
  }
61
+ for (let node of change.removedNodes) {
62
+ if (node.querySelectorAll) {
63
+ let toDestroy = Array.from(node.querySelectorAll('[data-simply-activate]'))
64
+ if (node.matches['[data-simply-activate']) {
65
+ toDestroy.push(node)
66
+ }
67
+ for (let child of toDestroy) {
68
+ if (child[Symbol.onDestroy]) {
69
+ child[Symbol.onDestroy].call(child)
70
+ }
71
+ }
72
+ }
73
+ }
52
74
  }
53
75
  }
54
76
  for (let node of activateNodes) {
package/src/app.mjs CHANGED
@@ -16,16 +16,12 @@ class SimplyApp {
16
16
  case 'keyboard': // backwards compatible
17
17
  this.keys = keys({ app: this, keys: options.keys })
18
18
  break
19
+ case 'root': // backwards compatibility
20
+ case 'baseURL':
21
+ this.baseURL = options[key]
22
+ break
19
23
  case 'routes':
20
24
  this.routes = routes({ app: this, routes: options.routes})
21
- this.routes.handleEvents();
22
- globalThis.setTimeout(() => {
23
- if (this.routes.has(globalThis.location?.hash)) {
24
- this.routes.match(globalThis.location.hash)
25
- } else {
26
- this.routes.match(globalThis.location?.pathname+globalThis.location?.hash)
27
- }
28
- });
29
25
  break
30
26
  case 'actions':
31
27
  this.actions = actions({app: this, actions: options.actions})
@@ -39,8 +35,32 @@ class SimplyApp {
39
35
  case 'view':
40
36
  this.view = view({app: this, view: options.view})
41
37
  break
38
+ case 'hooks':
39
+ const moduleHandler = {
40
+ get: (target, property) => {
41
+ if (!target[property]) {
42
+ return undefined
43
+ }
44
+ if (typeof target[property]=='function') {
45
+ return new Proxy(target[property], functionHandler)
46
+ } else if (target[property] && typeof target[property]=='object') {
47
+ return new Proxy(target[property], moduleHandler)
48
+ } else {
49
+ return target[property]
50
+ }
51
+ }
52
+ }
53
+ const functionHandler = {
54
+ apply: (target, thisArg, argumentsList) => {
55
+ // note: must use short function syntax so this is set to the app
56
+ return target.apply(this, argumentsList)
57
+ }
58
+ }
59
+ this[key] = new Proxy(options[key], moduleHandler)
60
+ break
42
61
  default:
43
- this[key] = options[key] // allows easy additions
62
+ console.log('simply.app: unknown initialization option "'+key+'", added as-is')
63
+ this[key] = options[key]
44
64
  break
45
65
  }
46
66
  }
@@ -48,6 +68,24 @@ class SimplyApp {
48
68
  get app() {
49
69
  return this
50
70
  }
71
+ async start() {
72
+ if (this.hooks?.start) {
73
+ await this.hooks.start()
74
+ }
75
+ if (this.routes) {
76
+ if (this.baseURL) {
77
+ this.routes.init({ baseURL: this.baseURL })
78
+ }
79
+ this.routes.handleEvents();
80
+ globalThis.setTimeout(() => {
81
+ if (this.routes.has(globalThis.location?.hash)) {
82
+ this.routes.match(globalThis.location.hash)
83
+ } else {
84
+ this.routes.match(globalThis.location?.pathname+globalThis.location?.hash)
85
+ }
86
+ });
87
+ }
88
+ }
51
89
  }
52
90
 
53
91
  export function app(options={}) {
package/src/route.mjs CHANGED
@@ -12,7 +12,7 @@ class SimplyRoute
12
12
  {
13
13
  constructor(options={})
14
14
  {
15
- this.root = options.root || '/'
15
+ this.baseURL = options.baseURL || '/'
16
16
  this.app = options.app || {}
17
17
  this.addMissingSlash = !!options.addMissingSlash
18
18
  this.matchExact = !!options.matchExact
@@ -63,7 +63,7 @@ class SimplyRoute
63
63
  matches = route.match.exec(path+'/')
64
64
  if (matches) {
65
65
  path+='/'
66
- history.replaceState({}, '', getURL(path, this.root))
66
+ history.replaceState({}, '', getURL(path, this.baseURL))
67
67
  }
68
68
  }
69
69
  }
@@ -91,7 +91,7 @@ class SimplyRoute
91
91
 
92
92
  runListeners(action, params)
93
93
  {
94
- if (!Object.keys(this.listeners[action])) {
94
+ if (!this.listeners[action] || !Object.keys(this.listeners[action])) {
95
95
  return
96
96
  }
97
97
  Object.keys(this.listeners[action]).forEach((route) => {
@@ -112,8 +112,8 @@ class SimplyRoute
112
112
  handleEvents()
113
113
  {
114
114
  globalThis.addEventListener('popstate', () => {
115
- if (this.match(getPath(document.location.pathname + document.location.hash, this.root)) === false) {
116
- this.match(getPath(document.location.pathname, this.root))
115
+ if (this.match(getPath(document.location.pathname + document.location.hash, this.baseURL)) === false) {
116
+ this.match(getPath(document.location.pathname, this.baseURL))
117
117
  }
118
118
  })
119
119
  this.app.container.addEventListener('click', (evt) => {
@@ -136,7 +136,7 @@ class SimplyRoute
136
136
  let check = [link.hash, link.pathname+link.hash, link.pathname]
137
137
  let path
138
138
  do {
139
- path = getPath(check.shift(), this.root);
139
+ path = getPath(check.shift(), this.baseURL);
140
140
  } while(check.length && !this.has(path))
141
141
  if ( this.has(path) ) {
142
142
  let params = this.runListeners('goto', { path: path});
@@ -154,13 +154,13 @@ class SimplyRoute
154
154
 
155
155
  goto(path)
156
156
  {
157
- history.pushState({},'',getURL(path, this.root))
157
+ history.pushState({},'',getURL(path, this.baseURL))
158
158
  return this.match(path)
159
159
  }
160
160
 
161
161
  has(path)
162
162
  {
163
- path = getPath(path, this.root)
163
+ path = getPath(path, this.baseURL)
164
164
  for (let route of this.routeInfo) {
165
165
  var matches = route.match.exec(path)
166
166
  if (matches && matches.length) {
@@ -196,22 +196,22 @@ class SimplyRoute
196
196
 
197
197
  init(options)
198
198
  {
199
- if (options.root) {
200
- this.root = options.root
199
+ if (options.baseURL) {
200
+ this.baseURL = options.baseURL
201
201
  }
202
202
  }
203
203
  }
204
204
 
205
- function getPath(path, root='/')
205
+ function getPath(path, baseURL='/')
206
206
  {
207
- if (path.substring(0,root.length)==root
207
+ if (path.substring(0,baseURL.length)==baseURL
208
208
  ||
209
- ( root[root.length-1]=='/'
210
- && path.length==(root.length-1)
211
- && path == root.substring(0,path.length)
209
+ ( baseURL[baseURL.length-1]=='/'
210
+ && path.length==(baseURL.length-1)
211
+ && path == baseURL.substring(0,path.length)
212
212
  )
213
213
  ) {
214
- path = path.substring(root.length)
214
+ path = path.substring(baseURL.length)
215
215
  }
216
216
  if (path[0]!='/' && path[0]!='#') {
217
217
  path = '/'+path
@@ -219,16 +219,16 @@ function getPath(path, root='/')
219
219
  return path
220
220
  }
221
221
 
222
- function getURL(path, root)
222
+ function getURL(path, baseURL)
223
223
  {
224
- path = getPath(path, root)
225
- if (root[root.length-1]==='/' && path[0]==='/') {
224
+ path = getPath(path, baseURL)
225
+ if (baseURL[baseURL.length-1]==='/' && path[0]==='/') {
226
226
  path = path.substring(1)
227
227
  }
228
228
  if (path[0]=='#') {
229
229
  return path
230
230
  }
231
- return root + path
231
+ return baseURL + path
232
232
  }
233
233
 
234
234
  function getRegexpFromRoute(route, exact=false)