bigpipe-util 0.2.1 → 0.2.3
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/CHANGELOG.md +23 -17
- package/README.md +5 -3
- package/package.json +1 -1
- package/src/ServerJS.js +2 -1
- package/src/async/AsyncRequest.js +135 -120
- package/src/core/Dialog.js +25 -13
package/CHANGELOG.md
CHANGED
|
@@ -4,23 +4,29 @@ All notable changes to `bigpipe-util` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
|
|
6
6
|
|
|
7
|
-
## v0.
|
|
7
|
+
## v0.2.2 - 2022-08-03
|
|
8
8
|
|
|
9
|
-
###
|
|
10
|
-
-
|
|
9
|
+
### Fixed
|
|
10
|
+
- calls for modules without constructor
|
|
11
|
+
- dialog opening when using instant closing
|
|
11
12
|
|
|
12
|
-
## v0.1
|
|
13
|
+
## v0.2.1 - 2022-06-02
|
|
13
14
|
|
|
14
15
|
### Added
|
|
15
|
-
-
|
|
16
|
+
- Prevent links from being double-clicked
|
|
17
|
+
- Support for keyboard (close by escape)
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
### Fixed
|
|
20
|
+
- Closing dialogs with [esc] in the correct order
|
|
21
|
+
|
|
22
|
+
## v0.2.0 - 2022-05-17
|
|
18
23
|
|
|
19
24
|
### Added
|
|
20
|
-
-
|
|
25
|
+
- Shield to prevent "JSON Hijacking"
|
|
26
|
+
- Invalid node checking for AsyncDOM
|
|
21
27
|
|
|
22
28
|
### Fixed
|
|
23
|
-
-
|
|
29
|
+
- Backdrop for 2+ opened dialogs
|
|
24
30
|
|
|
25
31
|
## v0.1.4 - 2022-05-06
|
|
26
32
|
|
|
@@ -31,20 +37,20 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
|
|
|
31
37
|
### Fixed
|
|
32
38
|
- Arguments for non-method require calls
|
|
33
39
|
|
|
34
|
-
## v0.
|
|
40
|
+
## v0.1.3 - 2022-04-27
|
|
35
41
|
|
|
36
42
|
### Added
|
|
37
|
-
-
|
|
38
|
-
- Invalid node checking for AsyncDOM
|
|
43
|
+
- Method to close only current dialog
|
|
39
44
|
|
|
40
45
|
### Fixed
|
|
41
|
-
-
|
|
46
|
+
- Dialog z-index
|
|
42
47
|
|
|
43
|
-
## v0.2
|
|
48
|
+
## v0.1.2 - 2022-04-14
|
|
44
49
|
|
|
45
50
|
### Added
|
|
46
|
-
-
|
|
47
|
-
- Support for keyboard (close by escape)
|
|
51
|
+
- Dialogs support.
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
## v0.1.1 - 2022-03-27
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- Part of BigPipe implementation for Webpack.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<img src="bigpipe.svg">
|
|
1
|
+
<img src="bigpipe.svg" alt="BigPipe logo">
|
|
2
2
|
|
|
3
3
|
This library currently implements small part of [Facebook BigPipe][blog] so far, but the advantage is to efficiently insert/replace content and work with the DOM. It is also possible to easily call JavaScript modules from PHP.
|
|
4
4
|
|
|
@@ -8,7 +8,6 @@ Try the app with [live demo](http://bigpipe.xf.cz).
|
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
10
|
* PHP 7.1 or higher
|
|
11
|
-
* Node 8, 10+.
|
|
12
11
|
* Webpack
|
|
13
12
|
|
|
14
13
|
## Installation
|
|
@@ -58,7 +57,7 @@ export default class ServerJS extends ServerJSImpl {
|
|
|
58
57
|
```javascript
|
|
59
58
|
import AsyncRequest from 'bigpipe-util/src/AsyncRequest';
|
|
60
59
|
|
|
61
|
-
const request = AsyncRequest('/ajax/remove.php')
|
|
60
|
+
const request = (new AsyncRequest('/ajax/remove.php'))
|
|
62
61
|
// or .setURI('/ajax/remove.php')
|
|
63
62
|
.setMethod('POST')
|
|
64
63
|
.setData({
|
|
@@ -73,6 +72,9 @@ const request = AsyncRequest('/ajax/remove.php')
|
|
|
73
72
|
.setErrorHandler((xhr) => {
|
|
74
73
|
// A function to be called if the request fails
|
|
75
74
|
})
|
|
75
|
+
.setFinallyHandler((xhr) => {
|
|
76
|
+
// after request callback function
|
|
77
|
+
})
|
|
76
78
|
.send();
|
|
77
79
|
|
|
78
80
|
if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bigpipe-util",
|
|
3
3
|
"description": "This library currently implements small part of Facebook BigPipe so far, but the advantage is to efficiently insert/replace content and work with the DOM. It is also possible to easily call JavaScript modules from PHP.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.3",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bigpipe",
|
|
7
7
|
"xhr",
|
package/src/ServerJS.js
CHANGED
|
@@ -21,7 +21,8 @@ export default class ServerJS {
|
|
|
21
21
|
replaceTransportMarkers(this._relativeTo, marker);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const factory = window.require(modulePath);
|
|
25
|
+
const context = typeof factory === 'function' ? new factory : factory;
|
|
25
26
|
|
|
26
27
|
if (!context[method]) {
|
|
27
28
|
throw new TypeError(`Module ${modulePath} has no method "${method}"`);
|
|
@@ -26,166 +26,181 @@ function validateResponseHandler(handler) {
|
|
|
26
26
|
return valid;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export default
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
export default class AsyncRequest {
|
|
30
|
+
constructor(uri) {
|
|
31
|
+
this.method = "POST";
|
|
32
|
+
this.uri = "";
|
|
33
|
+
this.relative = null;
|
|
34
|
+
this.data = {};
|
|
35
|
+
this.headers = {};
|
|
36
|
+
this.initialHandler = emptyFunction;
|
|
37
|
+
this.handler = null;
|
|
38
|
+
this.finallyHandler = emptyFunction;
|
|
39
|
+
this.errorHandler = emptyFunction;
|
|
40
|
+
|
|
41
|
+
if (uri !== undefined) {
|
|
42
|
+
this.setURI(uri);
|
|
43
|
+
}
|
|
41
44
|
}
|
|
42
|
-
}
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
46
|
+
setMethod(method) {
|
|
47
|
+
this.method = method.toString().toUpperCase();
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
51
|
+
getMethod() {
|
|
52
|
+
return this.method;
|
|
53
|
+
}
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
55
|
+
setRelative(relative) {
|
|
56
|
+
this.relative = relative;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
60
|
+
getRelative() {
|
|
61
|
+
return this.relative;
|
|
62
|
+
}
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
64
|
+
setData(obj) {
|
|
65
|
+
this.data = obj;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
69
|
+
getData() {
|
|
70
|
+
return this.data;
|
|
71
|
+
}
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
73
|
+
setRequestHeader(name, value) {
|
|
74
|
+
this.headers[name] = value;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
setURI(uri) {
|
|
79
|
+
this.uri = uri;
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
getURI() {
|
|
84
|
+
return this.uri;
|
|
85
|
+
}
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
87
|
+
setInitialHandler(fn) {
|
|
88
|
+
this.initialHandler = fn;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
92
|
+
getInitialHandler() {
|
|
93
|
+
return this.initialHandler || emptyFunction;
|
|
94
|
+
}
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
setHandler(fn) {
|
|
97
|
+
if (validateResponseHandler(fn)) {
|
|
98
|
+
this.handler = fn;
|
|
99
|
+
}
|
|
100
|
+
return this;
|
|
97
101
|
}
|
|
98
|
-
return this;
|
|
99
|
-
};
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
setFinallyHandler(fn) {
|
|
104
|
+
this.finallyHandler = fn;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.errorHandler = fn;
|
|
108
|
+
getFinallyHandler() {
|
|
109
|
+
return this.finallyHandler || emptyFunction;
|
|
108
110
|
}
|
|
109
|
-
return this;
|
|
110
|
-
};
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
112
|
+
getHandler() {
|
|
113
|
+
return this.handler || emptyFunction;
|
|
114
|
+
}
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
setErrorHandler(fn) {
|
|
117
|
+
if (validateResponseHandler(fn)) {
|
|
118
|
+
this.errorHandler = fn;
|
|
119
|
+
}
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
getErrorHandler() {
|
|
124
|
+
return this.errorHandler || emptyFunction;
|
|
121
125
|
}
|
|
122
|
-
};
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const shieldLength = shield.length;
|
|
127
|
+
abort() {
|
|
128
|
+
const transport = this.transport;
|
|
127
129
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
if (transport) {
|
|
131
|
+
transport.abort();
|
|
132
|
+
}
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
_unshieldResponseText(text) {
|
|
136
|
+
const shield = "for (;;);";
|
|
137
|
+
const shieldLength = shield.length;
|
|
134
138
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
if (text.length <= shieldLength) {
|
|
140
|
+
throw new Error("Response too short on async to " + this.getURI());
|
|
141
|
+
}
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const initialHandler = this.getInitialHandler();
|
|
143
|
+
return text.substring(shieldLength);
|
|
144
|
+
}
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
send() {
|
|
147
|
+
const {uri, method} = this;
|
|
148
|
+
let { data } = this;
|
|
144
149
|
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
const handler = this.getHandler();
|
|
151
|
+
const errorHandler = this.getErrorHandler();
|
|
152
|
+
const initialHandler = this.getInitialHandler();
|
|
153
|
+
const getFinallyHandler = this.getFinallyHandler();
|
|
147
154
|
|
|
148
|
-
|
|
155
|
+
initialHandler();
|
|
149
156
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
let response;
|
|
153
|
-
try {
|
|
154
|
-
const safeJson = self._unshieldResponseText(this.responseText);
|
|
155
|
-
response = eval("(" + safeJson + ")");
|
|
156
|
-
} catch (e) {
|
|
157
|
-
throw new Error("Failed to handle response: " + e.message + "\n" + this.responseText);
|
|
158
|
-
}
|
|
157
|
+
const request = new XMLHttpRequest();
|
|
158
|
+
const self = this;
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
request.open(method, uri, true);
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
request.onload = function () {
|
|
163
|
+
if (this.status >= 200 && this.status < 400) {
|
|
164
|
+
let response;
|
|
165
|
+
try {
|
|
166
|
+
const safeJson = self._unshieldResponseText(this.responseText);
|
|
167
|
+
response = eval("(" + safeJson + ")");
|
|
168
|
+
} catch (e) {
|
|
169
|
+
throw new Error("Failed to handle response: " + e.message + "\n" + this.responseText);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
(new AsyncResponse).handle(response, self.relative);
|
|
173
|
+
|
|
174
|
+
handler(response);
|
|
175
|
+
} else {
|
|
176
|
+
errorHandler(this);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getFinallyHandler(this);
|
|
165
180
|
}
|
|
166
|
-
};
|
|
167
181
|
|
|
168
|
-
|
|
182
|
+
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
169
183
|
|
|
170
|
-
|
|
184
|
+
requests++;
|
|
171
185
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
186
|
+
if (data instanceof FormData) {
|
|
187
|
+
data.append('__req', requests);
|
|
188
|
+
} else {
|
|
189
|
+
data.__req = requests;
|
|
190
|
+
data = serialize(data);
|
|
177
191
|
|
|
178
|
-
|
|
179
|
-
|
|
192
|
+
if (method === "POST") {
|
|
193
|
+
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
194
|
+
}
|
|
180
195
|
}
|
|
181
|
-
}
|
|
182
196
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
197
|
+
Object.keys(this.headers).forEach((name) => {
|
|
198
|
+
request.setRequestHeader(name, this.headers[name]);
|
|
199
|
+
});
|
|
186
200
|
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
request.onerror = errorHandler;
|
|
202
|
+
request.send(data);
|
|
189
203
|
|
|
190
|
-
|
|
204
|
+
return request;
|
|
205
|
+
}
|
|
191
206
|
};
|
package/src/core/Dialog.js
CHANGED
|
@@ -3,16 +3,16 @@ import DOM from "./DOM";
|
|
|
3
3
|
|
|
4
4
|
const DEFAULT_Z_INDEX = 1040;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
let
|
|
6
|
+
let stack = [];
|
|
7
|
+
let dialogId = 1;
|
|
8
8
|
let zIndex;
|
|
9
9
|
let originalBodyPad = null;
|
|
10
10
|
|
|
11
|
-
Modal.prototype._handleKeydownEvent = function(
|
|
12
|
-
if (
|
|
11
|
+
Modal.prototype._handleKeydownEvent = function(event) {
|
|
12
|
+
if (event.which === 27 && this._options.keyboard) {
|
|
13
13
|
const currentModal = stack[stack.length - 1];
|
|
14
14
|
if (currentModal.el.isEqualNode(this.el)) {
|
|
15
|
-
this.emit('dismiss', this,
|
|
15
|
+
this.emit('dismiss', this, event, null);
|
|
16
16
|
this.hide();
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -34,9 +34,9 @@ export default class Dialog {
|
|
|
34
34
|
render(options, args) {
|
|
35
35
|
DOM.appendContent(document.body, this._makeDialog(options.content));
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
dialogId++;
|
|
38
38
|
|
|
39
|
-
const
|
|
39
|
+
const dialog = this._show({
|
|
40
40
|
el: document.getElementById(this.id),
|
|
41
41
|
animate: false,
|
|
42
42
|
keyboard: options.keyboard ?? true,
|
|
@@ -45,11 +45,13 @@ export default class Dialog {
|
|
|
45
45
|
backdropTransition: options.backdropTransition ?? 0,
|
|
46
46
|
}, options.controller, args);
|
|
47
47
|
|
|
48
|
-
stack.push(
|
|
48
|
+
stack.push(dialog);
|
|
49
|
+
|
|
50
|
+
return dialog;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
showFromModel(model, args) {
|
|
52
|
-
const
|
|
54
|
+
const dialog = this._show({
|
|
53
55
|
title: model.title || '',
|
|
54
56
|
content: model.body,
|
|
55
57
|
footer: model.footer || false,
|
|
@@ -60,7 +62,9 @@ export default class Dialog {
|
|
|
60
62
|
backdropTransition: model.backdropTransition ?? 0,
|
|
61
63
|
}, model.controller, args);
|
|
62
64
|
|
|
63
|
-
stack.push(
|
|
65
|
+
stack.push(dialog);
|
|
66
|
+
|
|
67
|
+
return dialog;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
_show(options, controller, args) {
|
|
@@ -71,7 +75,13 @@ export default class Dialog {
|
|
|
71
75
|
const _modal = new Modal(options);
|
|
72
76
|
|
|
73
77
|
if (controller) {
|
|
74
|
-
(
|
|
78
|
+
if (typeof controller === 'string') {
|
|
79
|
+
controller = new (window.require(controller));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof controller === 'function') {
|
|
83
|
+
controller(_modal, ...args);
|
|
84
|
+
}
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
const self = this;
|
|
@@ -81,7 +91,9 @@ export default class Dialog {
|
|
|
81
91
|
el.style.zIndex = zIndex;
|
|
82
92
|
setTimeout(() => document.querySelector('.modal-backdrop').style.zIndex = zIndex - 1);
|
|
83
93
|
}).on('showBackdrop', this._fixBackdrop).on('hidden', function ({el}) {
|
|
84
|
-
stack.
|
|
94
|
+
stack = stack.filter((modal) => {
|
|
95
|
+
return ! modal.el.isEqualNode(el);
|
|
96
|
+
});
|
|
85
97
|
|
|
86
98
|
if (stack.length) {
|
|
87
99
|
document.body.classList.add('modal-open');
|
|
@@ -111,7 +123,7 @@ export default class Dialog {
|
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
_makeDialog(content) {
|
|
114
|
-
this.id = `js_${
|
|
126
|
+
this.id = `js_${dialogId.toString(16)}`;
|
|
115
127
|
return `<div id="${this.id}" class="modal fade" tabindex="-1" role="dialog">
|
|
116
128
|
${content}
|
|
117
129
|
</div>`;
|