simplyview 2.1.1 → 3.0.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/src/route.mjs ADDED
@@ -0,0 +1,222 @@
1
+ export function routes(options) {
2
+ return new SimplyRoute(options)
3
+ }
4
+
5
+ class SimplyRoute {
6
+ constructor(options={}) {
7
+ this.root = options.root || '/'
8
+ this.app = options.app
9
+ this.clear()
10
+ if (options.routes) {
11
+ this.load(options.routes)
12
+ }
13
+ }
14
+
15
+ load(routes) {
16
+ parseRoutes(routes, this.routeInfo)
17
+ }
18
+
19
+ clear() {
20
+ this.routeInfo = []
21
+ this.listeners = {
22
+ match: {},
23
+ call: {},
24
+ finish: {}
25
+ }
26
+ }
27
+
28
+ match(path, options) {
29
+ let args = {
30
+ path,
31
+ options
32
+ }
33
+ args = this.runListeners('match',args)
34
+ path = args.path ? args.path : path;
35
+
36
+ let matches;
37
+ if (!path) {
38
+ if (this.match(document.location.pathname+document.location.hash)) {
39
+ return true;
40
+ } else {
41
+ return this.match(document.location.pathname);
42
+ }
43
+ }
44
+ path = getPath(path);
45
+ for ( let route of this.routeInfo) {
46
+ matches = route.match.exec(path)
47
+ if (matches && matches.length) {
48
+ var params = {};
49
+ route.params.forEach((key, i) => {
50
+ if (key=='*') {
51
+ key = 'remainder'
52
+ }
53
+ params[key] = matches[i+1]
54
+ })
55
+ Object.assign(params, options)
56
+ args.route = route
57
+ args.params = params
58
+ args = this.runListeners('call', args)
59
+ params = args.params ? args.params : params
60
+ args.result = route.action.call(route, params)
61
+ this.runListeners('finish', args)
62
+ return args.result
63
+ }
64
+ }
65
+ if (path && path[path.length-1]!='/') {
66
+ return this.match(path+'/', options)
67
+ }
68
+ return false
69
+ }
70
+
71
+ runListeners(action, params) {
72
+ if (!Object.keys(this.listeners[action])) {
73
+ return
74
+ }
75
+ Object.keys(this.listeners[action]).forEach((route) => {
76
+ var routeRe = getRegexpFromRoute(route);
77
+ if (routeRe.exec(params.path)) {
78
+ var result;
79
+ for (let callback of this.listeners[action][route]) {
80
+ result = callback.call(this.app, params)
81
+ if (result) {
82
+ params = result
83
+ }
84
+ }
85
+ }
86
+ })
87
+ return params
88
+ }
89
+
90
+ handleEvents() {
91
+ globalThis.addEventListener('popstate', () => {
92
+ if (this.match(getPath(document.location.pathname + document.location.hash, this.root)) === false) {
93
+ this.match(getPath(document.location.pathname, this.root))
94
+ }
95
+ })
96
+ globalThis.document.addEventListener('click', (evt) => {
97
+ if (evt.ctrlKey) {
98
+ return;
99
+ }
100
+ if (evt.which != 1) {
101
+ return; // not a 'left' mouse click
102
+ }
103
+ var link = evt.target;
104
+ while (link && link.tagName!='A') {
105
+ link = link.parentElement;
106
+ }
107
+ if (link
108
+ && link.pathname
109
+ && link.hostname==globalThis.location.hostname
110
+ && !link.link
111
+ && !link.dataset.simplyCommand
112
+ ) {
113
+ let path = getPath(link.pathname+link.hash, this.root);
114
+ if ( !this.has(path) ) {
115
+ path = getPath(link.pathname, this.root);
116
+ }
117
+ if ( this.has(path) ) {
118
+ let params = this.runListeners('goto', { path: path});
119
+ if (params.path) {
120
+ this.goto(params.path);
121
+ }
122
+ evt.preventDefault();
123
+ return false;
124
+ }
125
+ }
126
+ })
127
+ }
128
+
129
+ goto(path) {
130
+ history.pushState({},'',getURL(path))
131
+ return this.match(path)
132
+ }
133
+
134
+ has(path) {
135
+ path = getPath(path, this.root)
136
+ for (let route of this.routeInfo) {
137
+ var matches = route.match.exec(path)
138
+ if (matches && matches.length) {
139
+ return true
140
+ }
141
+ }
142
+ return false
143
+ }
144
+
145
+ addListener(action, route, callback) {
146
+ if (['goto','match','call','finish'].indexOf(action)==-1) {
147
+ throw new Error('Unknown action '+action)
148
+ }
149
+ if (!this.listeners[action][route]) {
150
+ this.listeners[action][route] = []
151
+ }
152
+ this.listeners[action][route].push(callback)
153
+ }
154
+
155
+ removeListener(action, route, callback) {
156
+ if (['match','call','finish'].indexOf(action)==-1) {
157
+ throw new Error('Unknown action '+action)
158
+ }
159
+ if (!this.listeners[action][route]) {
160
+ return
161
+ }
162
+ this.listeners[action][route] = this.listeners[action][route].filter((listener) => {
163
+ return listener != callback
164
+ })
165
+ }
166
+
167
+ init(options) {
168
+ if (options.root) {
169
+ this.root = options.root
170
+ }
171
+ }
172
+ }
173
+
174
+ function getPath(path, root='/') {
175
+ if (path.substring(0,root.length)==root
176
+ ||
177
+ ( root[root.length-1]=='/'
178
+ && path.length==(root.length-1)
179
+ && path == root.substring(0,path.length)
180
+ )
181
+ ) {
182
+ path = path.substring(root.length)
183
+ }
184
+ if (path[0]!='/' && path[0]!='#') {
185
+ path = '/'+path
186
+ }
187
+ return path
188
+ }
189
+
190
+ function getURL(path, root) {
191
+ path = getPath(path, root)
192
+ if (root[root.length-1]==='/' && path[0]==='/') {
193
+ path = path.substring(1)
194
+ }
195
+ return root + path;
196
+ }
197
+
198
+ function getRegexpFromRoute(route) {
199
+ return new RegExp('^'+route.replace(/:\w+/g, '([^/]+)').replace(/:\*/, '(.*)'));
200
+ }
201
+
202
+ function parseRoutes(routes) {
203
+ let routeInfo = []
204
+ const paths = Object.keys(routes)
205
+ const matchParams = /:(\w+|\*)/g
206
+ for (let path of paths) {
207
+ let matches = []
208
+ let params = []
209
+ do {
210
+ matches = matchParams.exec(path)
211
+ if (matches) {
212
+ params.push(matches[1])
213
+ }
214
+ } while(matches)
215
+ routeInfo.push({
216
+ match: getRegexpFromRoute(path),
217
+ params: params,
218
+ action: routes[path]
219
+ })
220
+ }
221
+ return routeInfo
222
+ }