bigpipe-util 0.2.8 â 0.2.11
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 +13 -0
- package/README.md +22 -32
- package/package.json +1 -1
- package/src/Primer.js +7 -2
- package/src/async/AsyncDOM.js +2 -6
- package/src/async/AsyncRequest.js +5 -1
- package/src/core/Dialog.js +34 -39
- package/src/core/onAfterLoad.js +13 -0
- package/src/core/waitForLoad.js +57 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ 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.2.11 - 2025-12-27
|
|
8
|
+
- Added `incrementRequests` method to AsyncRequest.js
|
|
9
|
+
- Added submitter to form data in onSubmit handler
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Added `timeout` to open dialog with a delay in method `showFromModel`
|
|
13
|
+
|
|
14
|
+
## v0.2.10 - 2025-05-15
|
|
15
|
+
- Added `onAfterLoad.js`, `waitForLoad.js`
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Added `timeout` to open dialog with a delay in method `showFromModel`
|
|
19
|
+
|
|
7
20
|
## v0.2.8 - 2025-05-02
|
|
8
21
|
|
|
9
22
|
### Added
|
package/README.md
CHANGED
|
@@ -3,30 +3,30 @@
|
|
|
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
|
|
|
5
5
|
|
|
6
|
-
## Demo App
|
|
6
|
+
## đ Demo App
|
|
7
7
|
Try the app with [live demo](http://bigpipe.xf.cz).
|
|
8
8
|
|
|
9
|
-
## Full documentation
|
|
9
|
+
## đ Full documentation
|
|
10
10
|
https://richarddobron.github.io/bigpipe-php/
|
|
11
11
|
|
|
12
|
-
## Requirements
|
|
12
|
+
## âšī¸ Requirements
|
|
13
13
|
* PHP 7.1 or higher
|
|
14
14
|
* Webpack
|
|
15
15
|
|
|
16
|
-
## Installation
|
|
16
|
+
## đĻ Installation
|
|
17
|
+
Follow these steps to install and set up:
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
1. Install composer package:
|
|
19
|
+
### 1. Install composer package:
|
|
20
20
|
```shell
|
|
21
21
|
$ composer require richarddobron/bigpipe
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
2. Install npm package:
|
|
24
|
+
### 2. Install npm package:
|
|
25
25
|
```shell
|
|
26
26
|
$ npm install bigpipe-util
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
3. Add
|
|
29
|
+
### 3. Add the following to /path/to/resources/js/app.js:
|
|
30
30
|
```javascript
|
|
31
31
|
import Primer from 'bigpipe-util/src/Primer';
|
|
32
32
|
|
|
@@ -39,23 +39,14 @@ window.require = (modulePath) => {
|
|
|
39
39
|
};
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
4.
|
|
43
|
-
- this step is optional, but if you skip it, use this in next step:
|
|
44
|
-
```require("bigpipe-util/ServerJS")```
|
|
45
|
-
```javascript
|
|
46
|
-
import ServerJSImpl from 'bigpipe-util/src/ServerJS';
|
|
47
|
-
export default class ServerJS extends ServerJSImpl {
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
5. Add this lines to page footer:
|
|
42
|
+
### 4. Add these lines to the page footer:
|
|
52
43
|
```html
|
|
53
44
|
<script>
|
|
54
|
-
(new (require("ServerJS"))).handle(<?=json_encode(\dobron\BigPipe\BigPipe::jsmods())?>);
|
|
45
|
+
(new (require("bigpipe-util/src/ServerJS"))).handle(<?=json_encode(\dobron\BigPipe\BigPipe::jsmods())?>);
|
|
55
46
|
</script>
|
|
56
47
|
```
|
|
57
48
|
|
|
58
|
-
## Request API
|
|
49
|
+
## đ Request API
|
|
59
50
|
|
|
60
51
|
```javascript
|
|
61
52
|
import AsyncRequest from 'bigpipe-util/src/AsyncRequest';
|
|
@@ -85,16 +76,16 @@ if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
|
|
|
85
76
|
}
|
|
86
77
|
```
|
|
87
78
|
|
|
88
|
-
# What all can be Ajaxifed?
|
|
79
|
+
# âĄī¸ What all can be Ajaxifed?
|
|
89
80
|
|
|
90
|
-
## Links
|
|
81
|
+
## đ Links
|
|
91
82
|
```html
|
|
92
83
|
<a href="#"
|
|
93
84
|
ajaxify="/ajax/remove.php"
|
|
94
85
|
rel="async">Remove Item</a>
|
|
95
86
|
```
|
|
96
87
|
|
|
97
|
-
## Forms
|
|
88
|
+
## đ Forms
|
|
98
89
|
```html
|
|
99
90
|
<form action="/submit.php"
|
|
100
91
|
method="POST"
|
|
@@ -104,31 +95,30 @@ if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
|
|
|
104
95
|
</form>
|
|
105
96
|
```
|
|
106
97
|
|
|
107
|
-
## Dialogs
|
|
98
|
+
## đŦ Dialogs
|
|
108
99
|
```html
|
|
109
100
|
<a href="#"
|
|
110
101
|
ajaxify="/ajax/modal.php"
|
|
111
102
|
rel="dialog">Open Modal</a>
|
|
112
103
|
```
|
|
113
104
|
|
|
114
|
-
## Inspiration
|
|
105
|
+
## đ Inspiration
|
|
115
106
|
|
|
116
|
-
BigPipe is inspired by
|
|
107
|
+
BigPipe is inspired by Facebook's BigPipe. For more details
|
|
117
108
|
read their blog post: [Pipelining web pages for high performance][blog].
|
|
118
109
|
|
|
119
|
-
## Motivation
|
|
110
|
+
## đĄ Motivation
|
|
120
111
|
|
|
121
112
|
There is a large number of PHP projects for which moving to modern frameworks like Laravel Livewire, React, Vue.js (and many more!) could be very challenging.
|
|
122
113
|
|
|
123
114
|
The purpose of this library is to rapidly reduce the continuously repetitive code to work with the DOM and improve the communication barrier between PHP and JavaScript.
|
|
124
115
|
|
|
125
|
-
##
|
|
116
|
+
## đ¤ Contributing
|
|
126
117
|
|
|
127
|
-
|
|
118
|
+
We welcome contributions! If you'd like to help improve this project, feel free to open an issue or submit a pull request.
|
|
128
119
|
|
|
129
|
-
## License
|
|
120
|
+
## đ License
|
|
130
121
|
|
|
131
|
-
The MIT License (MIT). Please see [License File](LICENSE
|
|
122
|
+
The MIT License (MIT). Please see [License File](LICENSE) for more information.
|
|
132
123
|
|
|
133
|
-
[link-author]: https://github.com/richardDobron
|
|
134
124
|
[blog]: https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
|
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.11",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bigpipe",
|
|
7
7
|
"xhr",
|
package/src/Primer.js
CHANGED
|
@@ -83,13 +83,18 @@ export default function Primer() {
|
|
|
83
83
|
eventTarget.getAttribute("rel") === "async") {
|
|
84
84
|
event.preventDefault();
|
|
85
85
|
|
|
86
|
+
const formData = new FormData(eventTarget);
|
|
86
87
|
const submitter = event.submitter || eventTarget.querySelector("button[type='submit']");
|
|
87
88
|
const activeControls = eventTarget.querySelectorAll("input:not([readonly]),select:not([readonly]),textarea:not([readonly])");
|
|
88
89
|
|
|
90
|
+
if (submitter.name) {
|
|
91
|
+
formData.append(submitter.name, submitter.value);
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
(new AsyncRequest(eventTarget.getAttribute("ajaxify") || eventTarget.getAttribute("action")))
|
|
90
95
|
.setMethod(eventTarget.method || "POST")
|
|
91
96
|
.setRelative(eventTarget)
|
|
92
|
-
.setData(
|
|
97
|
+
.setData(formData)
|
|
93
98
|
.setInitialHandler(function () {
|
|
94
99
|
eventTarget.classList.add("async-saving");
|
|
95
100
|
|
|
@@ -111,7 +116,7 @@ export default function Primer() {
|
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
})
|
|
114
|
-
.setHandler(function (
|
|
119
|
+
.setHandler(function () {
|
|
115
120
|
eventTarget.classList.remove("async-saving");
|
|
116
121
|
|
|
117
122
|
activeControls.forEach(function (control) {
|
package/src/async/AsyncDOM.js
CHANGED
|
@@ -3,11 +3,7 @@ import DOM from "../core/DOM";
|
|
|
3
3
|
export default class AsyncDOM {
|
|
4
4
|
invoke(domOps, element) {
|
|
5
5
|
for (let i = 0; i < domOps.length; ++i) {
|
|
6
|
-
let
|
|
7
|
-
type = domOp[0],
|
|
8
|
-
selector = domOp[1],
|
|
9
|
-
enableTarget = domOp[2],
|
|
10
|
-
content = domOp[3];
|
|
6
|
+
let [type, selector, enableTarget, content] = domOps[i];
|
|
11
7
|
|
|
12
8
|
let node = enableTarget && element || null;
|
|
13
9
|
|
|
@@ -15,7 +11,7 @@ export default class AsyncDOM {
|
|
|
15
11
|
node = (node || document.documentElement).querySelector(selector);
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
if (!node) {
|
|
14
|
+
if (__DEV__ && !node) {
|
|
19
15
|
console.error(`Selector '${selector}' does not match anything!`)
|
|
20
16
|
continue;
|
|
21
17
|
}
|
|
@@ -3,6 +3,10 @@ import AsyncResponse from "./AsyncResponse";
|
|
|
3
3
|
|
|
4
4
|
let requests = 0;
|
|
5
5
|
|
|
6
|
+
export function incrementRequests() {
|
|
7
|
+
return requests++;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
function serialize(obj, prefix) {
|
|
7
11
|
const str = [];
|
|
8
12
|
for(const p in obj) {
|
|
@@ -181,7 +185,7 @@ export default class AsyncRequest {
|
|
|
181
185
|
|
|
182
186
|
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
183
187
|
|
|
184
|
-
|
|
188
|
+
incrementRequests();
|
|
185
189
|
|
|
186
190
|
if (data instanceof FormData) {
|
|
187
191
|
data.append('__req', requests);
|
package/src/core/Dialog.js
CHANGED
|
@@ -42,47 +42,40 @@ export default class Dialog {
|
|
|
42
42
|
|
|
43
43
|
dialogId++;
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}, options.controller, args);
|
|
54
|
-
|
|
55
|
-
return stack.push(dialog);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
if (options.timeout && typeof options.timeout === 'number') {
|
|
59
|
-
return new Promise(resolve => setTimeout(() => resolve(renderDialog()), options.timeout));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return renderDialog();
|
|
45
|
+
return this._maybeWithTimeout(options.timeout, () => this._show({
|
|
46
|
+
el: document.getElementById(this.id),
|
|
47
|
+
animate: this._getOption(options, 'animate', false),
|
|
48
|
+
keyboard: this._getOption(options, 'keyboard', true),
|
|
49
|
+
backdrop: this._getOption(options, 'backdrop', true),
|
|
50
|
+
transition: this._getOption(options, 'transition', 0),
|
|
51
|
+
backdropTransition: this._getOption(options, 'backdropTransition', 0)
|
|
52
|
+
}, options.controller, args));
|
|
63
53
|
}
|
|
64
54
|
|
|
65
55
|
showFromModel(model, args) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (model.timeout && typeof model.timeout === 'number') {
|
|
82
|
-
return new Promise(resolve => setTimeout(() => resolve(renderDialog()), model.timeout));
|
|
56
|
+
return this._maybeWithTimeout(model.timeout, () => this._show({
|
|
57
|
+
title: model.title || '',
|
|
58
|
+
content: model.body,
|
|
59
|
+
footer: model.footer || false,
|
|
60
|
+
animate: this._getOption(model, 'animate', false),
|
|
61
|
+
keyboard: this._getOption(model, 'keyboard', true),
|
|
62
|
+
backdrop: this._getOption(model, 'backdrop', true),
|
|
63
|
+
transition: this._getOption(model, 'transition', 0),
|
|
64
|
+
backdropTransition: this._getOption(model, 'backdropTransition', 0)
|
|
65
|
+
}, model.controller, args));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_maybeWithTimeout(timeout, callback) {
|
|
69
|
+
if (typeof timeout === 'number') {
|
|
70
|
+
return new Promise(resolve => setTimeout(() => resolve(callback()), timeout));
|
|
83
71
|
}
|
|
72
|
+
return callback();
|
|
73
|
+
}
|
|
84
74
|
|
|
85
|
-
|
|
75
|
+
_getOption(obj, key, defaultVal) {
|
|
76
|
+
return obj[key] !== null && obj[key] !== undefined
|
|
77
|
+
? obj[key]
|
|
78
|
+
: defaultVal;
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
_show(options, controller, args) {
|
|
@@ -91,6 +84,7 @@ export default class Dialog {
|
|
|
91
84
|
}
|
|
92
85
|
|
|
93
86
|
const _modal = new Modal(options);
|
|
87
|
+
stack.push(_modal);
|
|
94
88
|
|
|
95
89
|
if (controller) {
|
|
96
90
|
if (typeof controller === 'string') {
|
|
@@ -128,7 +122,7 @@ export default class Dialog {
|
|
|
128
122
|
let shown = false;
|
|
129
123
|
|
|
130
124
|
backdrops.forEach((backdrop, index) => {
|
|
131
|
-
if (shown || index > 0 && index === backdrops.length - 1) {
|
|
125
|
+
if (shown || (index > 0 && index === backdrops.length - 1)) {
|
|
132
126
|
backdrop.style.display = 'none';
|
|
133
127
|
} else {
|
|
134
128
|
backdrop.style.zIndex = zIndex - 1;
|
|
@@ -138,9 +132,10 @@ export default class Dialog {
|
|
|
138
132
|
});
|
|
139
133
|
}
|
|
140
134
|
|
|
141
|
-
_makeDialog(content,
|
|
135
|
+
_makeDialog(content, className = '') {
|
|
142
136
|
this.id = `js_${dialogId.toString(16)}`;
|
|
143
|
-
return
|
|
137
|
+
return `
|
|
138
|
+
<div id="${this.id}" class="modal fade ${className}" tabindex="-1" role="dialog">
|
|
144
139
|
${content}
|
|
145
140
|
</div>`;
|
|
146
141
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import EventListener from 'fbjs/lib/EventListener';
|
|
2
|
+
|
|
3
|
+
export default function onAfterLoad(callback) {
|
|
4
|
+
if (document.readyState === 'complete') {
|
|
5
|
+
setTimeout(callback, 0);
|
|
6
|
+
} else {
|
|
7
|
+
const event = EventListener.capture(window, 'load', () => {
|
|
8
|
+
event.remove();
|
|
9
|
+
|
|
10
|
+
callback();
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import onAfterLoad from "./onAfterLoad";
|
|
2
|
+
|
|
3
|
+
let loadingCounter = 0;
|
|
4
|
+
let originalCursor = '';
|
|
5
|
+
|
|
6
|
+
export default function waitForLoad(elementOnClicked, event, callback = () => {
|
|
7
|
+
}) {
|
|
8
|
+
const boundCallback = callback.bind(elementOnClicked, event);
|
|
9
|
+
const body = document.body;
|
|
10
|
+
|
|
11
|
+
if (document.readyState === 'complete') {
|
|
12
|
+
return boundCallback();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const type = (event || window.event).type;
|
|
16
|
+
loadingCounter++;
|
|
17
|
+
|
|
18
|
+
if (loadingCounter === 1) {
|
|
19
|
+
originalCursor = body.style.cursor;
|
|
20
|
+
body.style.cursor = 'progress';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
switch (type) {
|
|
24
|
+
case 'load':
|
|
25
|
+
case 'focus':
|
|
26
|
+
onAfterLoad(boundCallback);
|
|
27
|
+
return;
|
|
28
|
+
|
|
29
|
+
case 'click':
|
|
30
|
+
elementOnClicked.style.cursor = 'progress';
|
|
31
|
+
|
|
32
|
+
onAfterLoad(() => {
|
|
33
|
+
loadingCounter--;
|
|
34
|
+
|
|
35
|
+
if (loadingCounter === 0) {
|
|
36
|
+
body.style.cursor = originalCursor;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
elementOnClicked.style.cursor = '';
|
|
40
|
+
|
|
41
|
+
const result = boundCallback();
|
|
42
|
+
const href = elementOnClicked.getAttribute('href');
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
elementOnClicked.tagName.toLowerCase() === 'a' &&
|
|
46
|
+
result !== false &&
|
|
47
|
+
href
|
|
48
|
+
) {
|
|
49
|
+
window.location.href = href;
|
|
50
|
+
} else {
|
|
51
|
+
elementOnClicked.click();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return false;
|
|
57
|
+
}
|