simplyview 1.0.0 → 2.0.2
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/README.md +9 -6
- package/dist/simply.everything.js +1596 -1139
- package/docs/examples.md +82 -0
- package/docs/readme.md +33 -0
- package/docs/simply.action.md +42 -0
- package/docs/simply.activate.md +27 -0
- package/docs/simply.api.md +188 -0
- package/docs/simply.app.md +27 -0
- package/docs/simply.collect.md +64 -0
- package/docs/simply.command.md +110 -0
- package/docs/simply.include.md +61 -0
- package/docs/simply.keyboard.md +60 -0
- package/docs/simply.path.md +3 -0
- package/docs/simply.route.md +133 -0
- package/docs/simply.view.md +53 -0
- package/docs/simply.viewmodel.md +3 -0
- package/examples/counter.html +1 -0
- package/examples/github.html +39 -0
- package/examples/githubv4.html +107 -0
- package/examples/graphql.html +51 -0
- package/examples/graphql.html~ +35 -0
- package/examples/keyboard.html +41 -0
- package/examples/viewmodel.html +359 -0
- package/js/simply.action.js +14 -5
- package/js/simply.activate.js +12 -4
- package/js/simply.api.js +229 -0
- package/js/simply.app.js +27 -9
- package/js/simply.collect.js +13 -5
- package/js/simply.command.js +36 -6
- package/js/simply.include.js +38 -17
- package/js/simply.keyboard.js +45 -0
- package/js/simply.modules.js +22 -0
- package/js/simply.observe.js +13 -4
- package/js/simply.path.js +12 -4
- package/js/simply.render.js +12 -6
- package/js/simply.resize.js +28 -20
- package/js/simply.route.js +149 -19
- package/js/simply.view.js +16 -9
- package/js/simply.viewmodel.js +174 -0
- package/make +16 -2
- package/make~ +17 -0
- package/package.json +6 -3
- package/package.json~ +8 -5
- package/test/simply.route.test.js +102 -0
- package/examples/todo.html~ +0 -50
- package/js/simply.app.js~ +0 -44
- package/js/simply.bind.js +0 -253
- package/js/simply.command.js~ +0 -166
- package/js/simply.resize.js~ +0 -69
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<script src="https://cdn.jsdelivr.net/npm/superagent"></script>
|
|
5
|
+
<link rel="stylesheet" type="text/css" href="css/theds.css">
|
|
6
|
+
<style>
|
|
7
|
+
.loading-overlay {
|
|
8
|
+
position: absolute;
|
|
9
|
+
position: fixed;
|
|
10
|
+
top: 0;
|
|
11
|
+
left: 0;
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
background-color: rgba(255,255,255,0.2);
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
}
|
|
19
|
+
body:not(.loading) .loading-overlay {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* loading animation from loading.io/css/ */
|
|
24
|
+
.lds-ring {
|
|
25
|
+
display: inline-block;
|
|
26
|
+
position: relative;
|
|
27
|
+
width: 80px;
|
|
28
|
+
height: 80px;
|
|
29
|
+
}
|
|
30
|
+
.lds-ring div {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
display: block;
|
|
33
|
+
position: absolute;
|
|
34
|
+
width: 64px;
|
|
35
|
+
height: 64px;
|
|
36
|
+
margin: 8px;
|
|
37
|
+
border: 8px solid #666;
|
|
38
|
+
border-radius: 50%;
|
|
39
|
+
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
|
40
|
+
border-color: #666 transparent transparent transparent;
|
|
41
|
+
}
|
|
42
|
+
.lds-ring div:nth-child(1) {
|
|
43
|
+
animation-delay: -0.45s;
|
|
44
|
+
}
|
|
45
|
+
.lds-ring div:nth-child(2) {
|
|
46
|
+
animation-delay: -0.3s;
|
|
47
|
+
}
|
|
48
|
+
.lds-ring div:nth-child(3) {
|
|
49
|
+
animation-delay: -0.15s;
|
|
50
|
+
}
|
|
51
|
+
@keyframes lds-ring {
|
|
52
|
+
0% {
|
|
53
|
+
transform: rotate(0deg);
|
|
54
|
+
}
|
|
55
|
+
100% {
|
|
56
|
+
transform: rotate(360deg);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
</style>
|
|
61
|
+
</head>
|
|
62
|
+
<body class="loading">
|
|
63
|
+
<header class="ds-space ds-navbar">
|
|
64
|
+
<div class="demo-search">
|
|
65
|
+
<input type="text" placeholder="Search..." data-simply-command="search" data-simply-immediate="1">
|
|
66
|
+
</div>
|
|
67
|
+
<div class="ds-paging ds-navbar-right" style="margin-right: 0">
|
|
68
|
+
<ul class="ds-align-right ds-navbar-nav ds-paging">
|
|
69
|
+
<li>
|
|
70
|
+
<button data-simply-command="movies-prevpage" class="ds-button" disabled="" title="vorige pagina"
|
|
71
|
+
data-simply-field="movies.paging.prev" data-simply-content="fixed" data-simply-transformer="enable">
|
|
72
|
+
<svg class="ds-icon ds-icon-feather"><use xlink:href="css/feather-sprite.svg#chevron-left"></use></svg>
|
|
73
|
+
</button>
|
|
74
|
+
</li>
|
|
75
|
+
<li class="ds-paging-info">
|
|
76
|
+
<span data-simply-field="movies.paging.page">1</span> /
|
|
77
|
+
<span data-simply-field="movies.paging.max">10</span>
|
|
78
|
+
</li>
|
|
79
|
+
<li>
|
|
80
|
+
<button data-simply-command="movies-nextpage" class="ds-button" title="volgende pagina"
|
|
81
|
+
data-simply-field="movies.paging.next" data-simply-content="fixed" data-simply-transformer="enable">
|
|
82
|
+
<svg class="ds-icon ds-icon-feather"><use xlink:href="css/feather-sprite.svg#chevron-right"></use></svg>
|
|
83
|
+
</button>
|
|
84
|
+
</li>
|
|
85
|
+
</ul>
|
|
86
|
+
|
|
87
|
+
<select class="ds-align-right" style="width: 200px;"
|
|
88
|
+
data-simply-list="movie-genres" data-simply-data="movie-genres" data-simply-entry="genre"
|
|
89
|
+
data-simply-command="genre" data-simply-field="movies.genreFilter.genre"
|
|
90
|
+
>
|
|
91
|
+
<template>
|
|
92
|
+
<option data-simply-field="genre"></option>
|
|
93
|
+
</template>
|
|
94
|
+
</select>
|
|
95
|
+
</div>
|
|
96
|
+
</header>
|
|
97
|
+
<main class="ds-space ds-scrollbox" style="--ds-scrollbox-height: 65vh">
|
|
98
|
+
<table class="ds-datatable ds-datatable-sticky-header ds-datatable-rulers">
|
|
99
|
+
<thead>
|
|
100
|
+
<tr>
|
|
101
|
+
<th data-simply-command="sort" data-simply-value="title" data-simply-field="movies.sort.sort" data-simply-content="fixed" data-simply-transformer="movies-sort">title</th>
|
|
102
|
+
<th data-simply-command="sort" data-simply-value="year" data-simply-field="movies.sort.sort" data-simply-content="fixed" data-simply-transformer="movies-sort">year</th>
|
|
103
|
+
<th class="ds-datatable-disable-sort">genre</th>
|
|
104
|
+
</tr>
|
|
105
|
+
</thead>
|
|
106
|
+
<tbody data-simply-list="movies" data-simply-data="movies">
|
|
107
|
+
<template>
|
|
108
|
+
<tr>
|
|
109
|
+
<td data-simply-field="title"></td>
|
|
110
|
+
<td data-simply-field="year"></td>
|
|
111
|
+
<td data-simply-field="genres" data-simply-transformer="movies-genres"></td>
|
|
112
|
+
</tr>
|
|
113
|
+
</template>
|
|
114
|
+
</tbody>
|
|
115
|
+
</table>
|
|
116
|
+
</main>
|
|
117
|
+
<div class="loading-overlay">
|
|
118
|
+
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
|
119
|
+
</div>
|
|
120
|
+
<script src="https://cdn.simplyedit.io/1/simply-edit.js"></script>
|
|
121
|
+
<script src="../dist/simply.everything.js"></script>
|
|
122
|
+
<script>
|
|
123
|
+
// make these global for easier debugging
|
|
124
|
+
var movies, movieApp;
|
|
125
|
+
|
|
126
|
+
// wait for simplyedit to be loaded
|
|
127
|
+
document.addEventListener('simply-content-loaded',function() {
|
|
128
|
+
|
|
129
|
+
// fetch movie list
|
|
130
|
+
superagent
|
|
131
|
+
.get('https://raw.githubusercontent.com/prust/wikipedia-movie-data/master/movies.json')
|
|
132
|
+
.then(function(result) {
|
|
133
|
+
if (result.ok) {
|
|
134
|
+
return JSON.parse(result.text);
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
.then(function(json) {
|
|
138
|
+
|
|
139
|
+
movies = (function() {
|
|
140
|
+
// create a viewmodel named 'movies'
|
|
141
|
+
// this will automatically add a datasource 'movies' in SimplyEdit.
|
|
142
|
+
var m = simply.viewmodel.create('movies');
|
|
143
|
+
|
|
144
|
+
// add a text search plugin that searches through titles
|
|
145
|
+
// this goes first, in the 'select' pipe
|
|
146
|
+
m.addPlugin('select', simply.viewmodel.createFilter({
|
|
147
|
+
name: 'titleSearch',
|
|
148
|
+
getMatch: function(params) {
|
|
149
|
+
if (params.search && params.search.length>2) {
|
|
150
|
+
var re = new RegExp(params.search, 'i');
|
|
151
|
+
return function(movie) {
|
|
152
|
+
return re.test(movie.title);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
// add a genre select tool
|
|
159
|
+
// first add a datasource of all available genres for all movies, before filtering
|
|
160
|
+
var genres = json.flatMap(m => m.genres);
|
|
161
|
+
var distinctGenres = Array.from(new Set(genres)).map(g => { return {value: g, innerHTML: g}});
|
|
162
|
+
|
|
163
|
+
distinctGenres.unshift({ value: '', innerHTML: 'All genres'});
|
|
164
|
+
|
|
165
|
+
editor.addDataSource('movie-genres',{
|
|
166
|
+
load: function(el, callback) {
|
|
167
|
+
callback(distinctGenres);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// start with the 'select' pipe
|
|
172
|
+
|
|
173
|
+
// if a previous filter has changed the view data
|
|
174
|
+
// then find all available genres in the filtered dataset
|
|
175
|
+
// and mark the others as disabled
|
|
176
|
+
m.addPlugin('select', function() {
|
|
177
|
+
if (this.view.changed) {
|
|
178
|
+
var currentGenres = this.view.data.flatMap(m => m.genres);
|
|
179
|
+
var distinctGenres = new Set(currentGenres);
|
|
180
|
+
var genres = document.querySelector('[data-simply-data="movie-genres"]');
|
|
181
|
+
Array.from(genres.options).forEach(function(genre) {
|
|
182
|
+
if (genre.value && !distinctGenres.has(genre.value)) {
|
|
183
|
+
genre.disabled = 'disabled';
|
|
184
|
+
} else {
|
|
185
|
+
genre.removeAttribute('disabled');
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// only then filter out all movies that don't match
|
|
192
|
+
// the selected genre
|
|
193
|
+
m.addPlugin('select', simply.viewmodel.createFilter({
|
|
194
|
+
name: 'genreFilter',
|
|
195
|
+
getMatch: function(params) {
|
|
196
|
+
if (params.genre) {
|
|
197
|
+
return function(movie) {
|
|
198
|
+
return movie.genres && movie.genres.indexOf(params.genre)>=0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
// the movies dataset contains an array of genres per movie
|
|
205
|
+
// this transformer turns it into a comma seperated string
|
|
206
|
+
// used in the genre column of the table body
|
|
207
|
+
editor.transformers['movies-genres'] = {
|
|
208
|
+
render: function(data) {
|
|
209
|
+
if (data) {
|
|
210
|
+
this.originalValue = data;
|
|
211
|
+
data = data.slice().join(', ');
|
|
212
|
+
this.innerHTML = data;
|
|
213
|
+
}
|
|
214
|
+
return data;
|
|
215
|
+
},
|
|
216
|
+
extract: function() {
|
|
217
|
+
return this.originalValue || [];
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// on with the 'order' pipe
|
|
222
|
+
|
|
223
|
+
// add a sort function that can sort by title or year, both ascending and descending
|
|
224
|
+
m.addPlugin('order', simply.viewmodel.createSort({
|
|
225
|
+
name: 'sort',
|
|
226
|
+
getSort: function(params) {
|
|
227
|
+
if (params.sortOrder == 'ASC') {
|
|
228
|
+
var order = -1;
|
|
229
|
+
} else {
|
|
230
|
+
var order = 1;
|
|
231
|
+
}
|
|
232
|
+
switch (params.sortBy) {
|
|
233
|
+
case 'title':
|
|
234
|
+
return function(a,b) {
|
|
235
|
+
return a.title<b.title ? order : -order;
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
case 'year':
|
|
239
|
+
return function(a,b) {
|
|
240
|
+
return a.year<b.year ? order : -order;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}));
|
|
246
|
+
|
|
247
|
+
// adds a transformer to update the table headings with the correct
|
|
248
|
+
// sort order class
|
|
249
|
+
// this uses the movies.options.sort.sort variable
|
|
250
|
+
// which combines the sortBy and sortOrder in a single string
|
|
251
|
+
// these are set by the command 'sort' in the movieApp
|
|
252
|
+
editor.transformers['movies-sort'] = {
|
|
253
|
+
render: function(data) {
|
|
254
|
+
this.originalValue = data;
|
|
255
|
+
this.classList.remove('ds-datatable-sorted-descending');
|
|
256
|
+
this.classList.remove('ds-datatable-sorted-ascending');
|
|
257
|
+
var sort = data.split(' ');
|
|
258
|
+
if (sort[0]==this.innerHTML) {
|
|
259
|
+
this.classList.add(sort[1]=='ASC' ? 'ds-datatable-sorted-ascending':'ds-datatable-sorted-descending');
|
|
260
|
+
}
|
|
261
|
+
return data;
|
|
262
|
+
},
|
|
263
|
+
extract: function() {
|
|
264
|
+
return this.originalValue;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
// then the 'render' pipe
|
|
271
|
+
|
|
272
|
+
// here we add a paging plugin
|
|
273
|
+
m.addPlugin('render', simply.viewmodel.createPaging());
|
|
274
|
+
|
|
275
|
+
// the paging buttons should be disabled when no further or earlier page
|
|
276
|
+
// is available for the next/prev buttons respectively
|
|
277
|
+
// the paging plugin has a next and prev value in the paging options
|
|
278
|
+
// if no next or prev page is available, the value is set to 0
|
|
279
|
+
// so this transformer sets the disabled property of an object
|
|
280
|
+
// depending on whether the given data evaluates to true or false
|
|
281
|
+
editor.transformers['enable'] = {
|
|
282
|
+
render: function(data) {
|
|
283
|
+
this.originalValue = data;
|
|
284
|
+
if (data) {
|
|
285
|
+
this.disabled = false;
|
|
286
|
+
} else {
|
|
287
|
+
this.disabled = true;
|
|
288
|
+
}
|
|
289
|
+
return data;
|
|
290
|
+
},
|
|
291
|
+
extract: function() {
|
|
292
|
+
return this.originalValue;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
return m;
|
|
297
|
+
})();
|
|
298
|
+
|
|
299
|
+
movieApp = simply.app({
|
|
300
|
+
commands: {
|
|
301
|
+
'search': function(el, value) {
|
|
302
|
+
movies.update({
|
|
303
|
+
titleSearch: {
|
|
304
|
+
search: value
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
'movies-nextpage': function(el, value) {
|
|
309
|
+
movies.update({
|
|
310
|
+
paging: {
|
|
311
|
+
page: movies.options.paging.page + 1
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
'movies-prevpage': function(el, value) {
|
|
316
|
+
movies.update({
|
|
317
|
+
paging: {
|
|
318
|
+
page: movies.options.paging.page - 1
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
'sort': function(el, value) {
|
|
323
|
+
var newOrder = !el.classList.contains('ds-datatable-sorted-ascending') ? 'ASC' : 'DESC';
|
|
324
|
+
movies.update({
|
|
325
|
+
sort: {
|
|
326
|
+
sortBy: value,
|
|
327
|
+
sortOrder: newOrder,
|
|
328
|
+
sort: value+' '+newOrder // this is added to pass all needed data in a single value for the sort order transformer
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
},
|
|
332
|
+
'genre': function(el, value) {
|
|
333
|
+
movies.update({
|
|
334
|
+
genreFilter: {
|
|
335
|
+
genre: value
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// only copy the options into the app view, the data should only be
|
|
343
|
+
// linked through a datasource - for performance
|
|
344
|
+
movieApp.view.movies = movies.options;
|
|
345
|
+
// this gives access to movies.options.paging, movies.options.sort, movies.options.genreFilter and movies.options.titleSearch
|
|
346
|
+
// as data-simply-field="movies.paging", "movies.sort", "movies.genreFilter" and "movies.titleSearch"
|
|
347
|
+
|
|
348
|
+
// load movies list from the fetched json
|
|
349
|
+
movies.update({
|
|
350
|
+
data: json
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
document.body.classList.remove('loading');
|
|
354
|
+
})
|
|
355
|
+
.catch(console.error);
|
|
356
|
+
});
|
|
357
|
+
</script>
|
|
358
|
+
</body>
|
|
359
|
+
</html>
|
package/js/simply.action.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
(function(global) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
2
4
|
var defaultActions = {
|
|
3
5
|
'simply-hide': function(el) {
|
|
4
6
|
el.classList.remove('simply-visible');
|
|
@@ -86,7 +88,7 @@ this.simply = (function(simply, global) {
|
|
|
86
88
|
}
|
|
87
89
|
};
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
var action = function(app, inActions) {
|
|
90
92
|
var actions = Object.create(defaultActions);
|
|
91
93
|
for ( var i in inActions ) {
|
|
92
94
|
actions[i] = inActions[i];
|
|
@@ -101,6 +103,13 @@ this.simply = (function(simply, global) {
|
|
|
101
103
|
return actions;
|
|
102
104
|
};
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
106
|
+
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
107
|
+
module.exports = action;
|
|
108
|
+
} else {
|
|
109
|
+
if (!global.simply) {
|
|
110
|
+
global.simply = {};
|
|
111
|
+
}
|
|
112
|
+
global.simply.action = action;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
})(this);
|
package/js/simply.activate.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
(function(global) {
|
|
2
|
+
'use strict';
|
|
2
3
|
|
|
3
4
|
var listeners = {};
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
var activate = {
|
|
6
7
|
addListener: function(name, callback) {
|
|
7
8
|
if (!listeners[name]) {
|
|
8
9
|
listeners[name] = [];
|
|
@@ -67,5 +68,12 @@ this.simply = (function(simply, global) {
|
|
|
67
68
|
childList: true
|
|
68
69
|
});
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
72
|
+
module.exports = activate;
|
|
73
|
+
} else {
|
|
74
|
+
if (!global.simply) {
|
|
75
|
+
global.simply = {};
|
|
76
|
+
}
|
|
77
|
+
global.simply.activate = activate;
|
|
78
|
+
}
|
|
79
|
+
})(this);
|
package/js/simply.api.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
(function(global) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var api = {
|
|
5
|
+
/**
|
|
6
|
+
* Returns a Proxy object that translates property access to a URL in the api
|
|
7
|
+
* and method calls to a fetch on that URL.
|
|
8
|
+
* @param options: a list of options for fetch(),
|
|
9
|
+
* see the 'init' parameter at https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#parameters
|
|
10
|
+
* additionally:
|
|
11
|
+
* - baseURL: (required) the endpoint of the API
|
|
12
|
+
* - path: the current path in the API, is appended to the baseURL
|
|
13
|
+
* - verbs: list of http verbs to allow as methods, default ['get','post']
|
|
14
|
+
* - handlers.fetch: alternative fetch method
|
|
15
|
+
* - handlers.result: alternative getResult method
|
|
16
|
+
* - handlers.error: alternative error method
|
|
17
|
+
* - user (and password): if set, a basic authentication header will be added
|
|
18
|
+
* - paramsFormat: either 'formData', 'json' or 'search'. Default is search.
|
|
19
|
+
* - responseFormat: test, formData, blob, json, arrayBuffer or unbuffered. Default is json.
|
|
20
|
+
* @return Proxy
|
|
21
|
+
*/
|
|
22
|
+
proxy: function(options) {
|
|
23
|
+
var cache = () => {};
|
|
24
|
+
cache.$options = Object.assign({}, options);
|
|
25
|
+
return new Proxy( cache, getApiHandler(cache.$options) );
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fetches the options.baseURL using the fetch api and returns a promise
|
|
30
|
+
* Extra options in addition to those of global.fetch():
|
|
31
|
+
* - user (and password): if set, a basic authentication header will be added
|
|
32
|
+
* - paramsFormat: either 'formData', 'json' or 'search'
|
|
33
|
+
* By default params, if set, will be added to the baseURL as searchParams
|
|
34
|
+
* @param method one of the http verbs, e.g. get, post, etc.
|
|
35
|
+
* @param options the options for fetch(), with some additions
|
|
36
|
+
* @param params the parameters to send with the request, as javascript/json data
|
|
37
|
+
* @return Promise
|
|
38
|
+
*/
|
|
39
|
+
fetch: function(method, params, options) {
|
|
40
|
+
if (!options.url) {
|
|
41
|
+
if (!options.baseURL) {
|
|
42
|
+
throw new Error('No url or baseURL in options object');
|
|
43
|
+
}
|
|
44
|
+
while (options.baseURL[options.baseURL.length-1]=='/') {
|
|
45
|
+
options.baseURL = options.baseURL.substr(0, options.baseURL.length-1);
|
|
46
|
+
}
|
|
47
|
+
var url = new URL(options.baseURL+options.path);
|
|
48
|
+
} else {
|
|
49
|
+
var url = options.url;
|
|
50
|
+
}
|
|
51
|
+
var fetchOptions = Object.assign({}, options);
|
|
52
|
+
if (!fetchOptions.headers) {
|
|
53
|
+
fetchOptions.headers = {};
|
|
54
|
+
}
|
|
55
|
+
if (params) {
|
|
56
|
+
if (method=='GET') {
|
|
57
|
+
var paramsFormat = 'search';
|
|
58
|
+
} else {
|
|
59
|
+
var paramsFormat = options.paramsFormat;
|
|
60
|
+
}
|
|
61
|
+
switch(paramsFormat) {
|
|
62
|
+
case 'formData':
|
|
63
|
+
var formData = new FormData();
|
|
64
|
+
for (const name in params) {
|
|
65
|
+
formData.append(name, params[name]);
|
|
66
|
+
}
|
|
67
|
+
if (!fetchOptions.headers['Content-Type']) {
|
|
68
|
+
fetchOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case 'json':
|
|
72
|
+
var formData = JSON.stringify(params);
|
|
73
|
+
if (!fetchOptions.headers['Content-Type']) {
|
|
74
|
+
fetchOptions.headers['Content-Type'] = 'application/json';
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case 'search':
|
|
78
|
+
var searchParams = url.searchParams; //new URLSearchParams(url.search.slice(1));
|
|
79
|
+
for (const name in params) {
|
|
80
|
+
searchParams.set(name, params[name]);
|
|
81
|
+
}
|
|
82
|
+
url.search = searchParams.toString();
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
throw Error('Unknown options.paramsFormat '+options.paramsFormat+'. Select one of formData, json or search.');
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (formData) {
|
|
90
|
+
fetchOptions.body = formData
|
|
91
|
+
}
|
|
92
|
+
if (options.user) {
|
|
93
|
+
fetchOptions.headers['Authorization'] = 'Basic '+btoa(options.user+':'+options.password);
|
|
94
|
+
}
|
|
95
|
+
fetchOptions.method = method.toUpperCase();
|
|
96
|
+
var fetchURL = url.toString()
|
|
97
|
+
return fetch(fetchURL, fetchOptions);
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* Creates a function to call one or more graphql queries
|
|
101
|
+
*/
|
|
102
|
+
graphqlQuery: function(url, query, options) {
|
|
103
|
+
options = Object.assign({ paramsFormat: 'json', url: url, responseFormat: 'json' }, options);
|
|
104
|
+
return function(params, operationName) {
|
|
105
|
+
let postParams = {
|
|
106
|
+
query: query
|
|
107
|
+
};
|
|
108
|
+
if (operationName) {
|
|
109
|
+
postParams.operationName = operationName;
|
|
110
|
+
}
|
|
111
|
+
postParams.variables = params || {};
|
|
112
|
+
return simply.api.fetch('POST', postParams, options )
|
|
113
|
+
.then(function(response) {
|
|
114
|
+
return simply.api.getResult(response, options);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* Handles the response and returns a Promise with the response data as specified
|
|
120
|
+
* @param response Response
|
|
121
|
+
* @param options
|
|
122
|
+
* - responseFormat: one of 'text', 'formData', 'blob', 'arrayBuffer', 'unbuffered' or 'json'.
|
|
123
|
+
* The default is json.
|
|
124
|
+
*/
|
|
125
|
+
getResult: function(response, options) {
|
|
126
|
+
if (response.ok) {
|
|
127
|
+
switch(options.responseFormat) {
|
|
128
|
+
case 'text':
|
|
129
|
+
return response.text();
|
|
130
|
+
break;
|
|
131
|
+
case 'formData':
|
|
132
|
+
return response.formData();
|
|
133
|
+
break;
|
|
134
|
+
case 'blob':
|
|
135
|
+
return response.blob();
|
|
136
|
+
break;
|
|
137
|
+
case 'arrayBuffer':
|
|
138
|
+
return response.arrayBuffer();
|
|
139
|
+
break;
|
|
140
|
+
case 'unbuffered':
|
|
141
|
+
return response.body;
|
|
142
|
+
break;
|
|
143
|
+
case 'json':
|
|
144
|
+
default:
|
|
145
|
+
return response.json();
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
throw {
|
|
150
|
+
status: response.status,
|
|
151
|
+
message: response.statusText,
|
|
152
|
+
response: response
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
logError: function(error, options) {
|
|
158
|
+
console.error(error.status, error.message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
var defaultOptions = {
|
|
163
|
+
path: '',
|
|
164
|
+
responseFormat: 'json',
|
|
165
|
+
paramsFormat: 'search',
|
|
166
|
+
verbs: ['get','post'],
|
|
167
|
+
handlers: {
|
|
168
|
+
fetch: api.fetch,
|
|
169
|
+
result: api.getResult,
|
|
170
|
+
error: api.logError
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
function cd(path, name) {
|
|
175
|
+
name = name.replace(/\//g,'');
|
|
176
|
+
if (!path.length || path[path.length-1]!=='/') {
|
|
177
|
+
path+='/';
|
|
178
|
+
}
|
|
179
|
+
return path+encodeURIComponent(name);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function fetchChain(prop, params) {
|
|
183
|
+
var options = this;
|
|
184
|
+
return this.handlers.fetch
|
|
185
|
+
.call(this, prop, params, options)
|
|
186
|
+
.then(function(res) {
|
|
187
|
+
return options.handlers.result.call(options, res, options);
|
|
188
|
+
})
|
|
189
|
+
.catch(function(error) {
|
|
190
|
+
return options.handlers.error.call(options, error, options);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getApiHandler(options) {
|
|
195
|
+
options.handlers = Object.assign({}, defaultOptions.handlers, options.handlers);
|
|
196
|
+
options = Object.assign({}, defaultOptions, options);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
get: function(cache, prop) {
|
|
200
|
+
if (!cache[prop]) {
|
|
201
|
+
if (options.verbs.indexOf(prop)!=-1) {
|
|
202
|
+
cache[prop] = function(params) {
|
|
203
|
+
return fetchChain.call(options, prop, params);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
cache[prop] = api.proxy(Object.assign({}, options, {
|
|
207
|
+
path: cd(options.path, prop)
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return cache[prop];
|
|
212
|
+
},
|
|
213
|
+
apply: function(cache, thisArg, params) {
|
|
214
|
+
return fetchChain.call(options, 'get', params[0] ? params[0] : null)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
221
|
+
module.exports = api;
|
|
222
|
+
} else {
|
|
223
|
+
if (!global.simply) {
|
|
224
|
+
global.simply = {};
|
|
225
|
+
}
|
|
226
|
+
global.simply.api = api;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
})(this);
|