bigpipe-util 0.1.3 → 0.2.1

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 CHANGED
@@ -21,3 +21,30 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
21
21
 
22
22
  ### Fixed
23
23
  - Dialog z-index
24
+
25
+ ## v0.1.4 - 2022-05-06
26
+
27
+ ### Added
28
+ - Async requests counter
29
+ - Support for extra arguments in dialog controller
30
+
31
+ ### Fixed
32
+ - Arguments for non-method require calls
33
+
34
+ ## v0.2.0 - 2022-05-17
35
+
36
+ ### Added
37
+ - Shield to prevent "JSON Hijacking"
38
+ - Invalid node checking for AsyncDOM
39
+
40
+ ### Fixed
41
+ - Backdrop for 2+ opened dialogs
42
+
43
+ ## v0.2.1 - 2022-06-02
44
+
45
+ ### Added
46
+ - Prevent links from being double-clicked
47
+ - Support for keyboard (close by escape)
48
+
49
+ ### Fixed
50
+ - Closing dialogs with [esc] in the correct order
package/README.md CHANGED
@@ -106,10 +106,6 @@ if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
106
106
  rel="dialog">Open Modal</a>
107
107
  ```
108
108
 
109
- ## Credits
110
-
111
- - [Richard Dobroň][link-author]
112
-
113
109
  ## Inspiration
114
110
 
115
111
  BigPipe is inspired by the concept behind Facebook's BigPipe. For more details
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.1.3",
4
+ "version": "0.2.1",
5
5
  "keywords": [
6
6
  "bigpipe",
7
7
  "xhr",
package/src/Primer.js CHANGED
@@ -19,6 +19,11 @@ export default function Primer() {
19
19
  let relationship = linkNodeOnClicked.rel && linkNodeOnClicked.rel.match(RELATIONSHIP_REGEX);
20
20
  relationship = relationship && relationship[0];
21
21
 
22
+ if (linkNodeOnClicked.classList.contains('async-saving')) {
23
+ event.preventDefault();
24
+ return;
25
+ }
26
+
22
27
  switch (relationship) {
23
28
  case "async":
24
29
  case "async-post":
@@ -26,6 +31,15 @@ export default function Primer() {
26
31
 
27
32
  (new AsyncRequest(linkNodeOnClicked.getAttribute("ajaxify")))
28
33
  .setRelative(linkNodeOnClicked)
34
+ .setInitialHandler(() => {
35
+ linkNodeOnClicked.classList.add("async-saving");
36
+ })
37
+ .setHandler(() => {
38
+ linkNodeOnClicked.classList.remove("async-saving");
39
+ })
40
+ .setErrorHandler(() => {
41
+ linkNodeOnClicked.classList.remove("async-saving");
42
+ })
29
43
  .setMethod(relationship === "async-post" ? "POST" : "GET")
30
44
  .send();
31
45
  break;
@@ -34,6 +48,15 @@ export default function Primer() {
34
48
 
35
49
  (new AsyncRequest(linkNodeOnClicked.getAttribute("ajaxify")))
36
50
  .setRelative(linkNodeOnClicked)
51
+ .setInitialHandler(() => {
52
+ linkNodeOnClicked.classList.add("async-saving");
53
+ })
54
+ .setHandler(() => {
55
+ linkNodeOnClicked.classList.remove("async-saving");
56
+ })
57
+ .setErrorHandler(() => {
58
+ linkNodeOnClicked.classList.remove("async-saving");
59
+ })
37
60
  .setMethod("POST")
38
61
  .send();
39
62
  break;
package/src/ServerJS.js CHANGED
@@ -29,8 +29,6 @@ export default class ServerJS {
29
29
 
30
30
  context[method].apply(context, marker || []);
31
31
  } else {
32
- marker = method;
33
-
34
32
  if (marker) {
35
33
  replaceTransportMarkers(this._relativeTo, marker);
36
34
  }
@@ -15,6 +15,11 @@ export default class AsyncDOM {
15
15
  node = (node || document.documentElement).querySelector(selector);
16
16
  }
17
17
 
18
+ if (!node) {
19
+ console.error(`Selector '${selector}' does not match anything!`)
20
+ continue;
21
+ }
22
+
18
23
  switch (type) {
19
24
  case "eval":
20
25
  (new Function(content)).apply(node);
@@ -1,6 +1,8 @@
1
1
  import emptyFunction from "fbjs/lib/emptyFunction";
2
2
  import AsyncResponse from "./AsyncResponse";
3
3
 
4
+ let requests = 0;
5
+
4
6
  function serialize(obj, prefix) {
5
7
  const str = [];
6
8
  for(const p in obj) {
@@ -119,6 +121,17 @@ AsyncRequest.prototype.abort = function () {
119
121
  }
120
122
  };
121
123
 
124
+ AsyncRequest.prototype._unshieldResponseText = function (text) {
125
+ const shield = "for (;;);";
126
+ const shieldLength = shield.length;
127
+
128
+ if (text.length <= shieldLength) {
129
+ throw new Error("Response too short on async to " + this.getURI());
130
+ }
131
+
132
+ return text.substring(shieldLength);
133
+ };
134
+
122
135
  AsyncRequest.prototype.send = function () {
123
136
  const {uri, method} = this;
124
137
  let { data } = this;
@@ -138,7 +151,8 @@ AsyncRequest.prototype.send = function () {
138
151
  if (this.status >= 200 && this.status < 400) {
139
152
  let response;
140
153
  try {
141
- response = eval("(" + this.responseText + ")");
154
+ const safeJson = self._unshieldResponseText(this.responseText);
155
+ response = eval("(" + safeJson + ")");
142
156
  } catch (e) {
143
157
  throw new Error("Failed to handle response: " + e.message + "\n" + this.responseText);
144
158
  }
@@ -152,7 +166,13 @@ AsyncRequest.prototype.send = function () {
152
166
  };
153
167
 
154
168
  request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
155
- if (!(this.data instanceof FormData)) {
169
+
170
+ requests++;
171
+
172
+ if (this.data instanceof FormData) {
173
+ this.data.append('__req', requests);
174
+ } else {
175
+ data.__req = requests;
156
176
  data = serialize(data);
157
177
 
158
178
  if (method === "POST") {
@@ -8,6 +8,16 @@ let modalId = 1;
8
8
  let zIndex;
9
9
  let originalBodyPad = null;
10
10
 
11
+ Modal.prototype._handleKeydownEvent = function(e) {
12
+ if (e.which === 27 && this._options.keyboard) {
13
+ const currentModal = stack[stack.length - 1];
14
+ if (currentModal.el.isEqualNode(this.el)) {
15
+ this.emit('dismiss', this, e, null);
16
+ this.hide();
17
+ }
18
+ }
19
+ }
20
+
11
21
  export default class Dialog {
12
22
  close() {
13
23
  stack.forEach((dialog) => dialog.hide())
@@ -21,7 +31,7 @@ export default class Dialog {
21
31
  }
22
32
  }
23
33
 
24
- render(options) {
34
+ render(options, args) {
25
35
  DOM.appendContent(document.body, this._makeDialog(options.content));
26
36
 
27
37
  modalId++;
@@ -29,29 +39,31 @@ export default class Dialog {
29
39
  const _dialog = this._show({
30
40
  el: document.getElementById(this.id),
31
41
  animate: false,
42
+ keyboard: options.keyboard ?? true,
32
43
  backdrop: options.backdrop ?? true,
33
44
  transition: options.transition ?? 0,
34
45
  backdropTransition: options.backdropTransition ?? 0,
35
- }, options.controller);
46
+ }, options.controller, args);
36
47
 
37
48
  stack.push(_dialog);
38
49
  }
39
50
 
40
- showFromModel(model) {
51
+ showFromModel(model, args) {
41
52
  const _dialog = this._show({
42
53
  title: model.title || '',
43
54
  content: model.body,
44
55
  footer: model.footer || false,
45
56
  animate: false,
57
+ keyboard: model.keyboard ?? true,
46
58
  backdrop: model.backdrop ?? true,
47
59
  transition: model.transition ?? 0,
48
60
  backdropTransition: model.backdropTransition ?? 0,
49
- }, model.controller);
61
+ }, model.controller, args);
50
62
 
51
63
  stack.push(_dialog);
52
64
  }
53
65
 
54
- _show(options, controller) {
66
+ _show(options, controller, args) {
55
67
  if (originalBodyPad === null) {
56
68
  originalBodyPad = document.body.style.paddingRight;
57
69
  }
@@ -59,7 +71,7 @@ export default class Dialog {
59
71
  const _modal = new Modal(options);
60
72
 
61
73
  if (controller) {
62
- (new (window.require(controller))(_modal));
74
+ (new (window.require(controller))(_modal, ...args));
63
75
  }
64
76
 
65
77
  const self = this;
@@ -83,15 +95,17 @@ export default class Dialog {
83
95
  }).show();
84
96
  }
85
97
 
86
- _fixBackdrop(content) {
98
+ _fixBackdrop() {
87
99
  const backdrops = document.querySelectorAll('.modal-backdrop');
100
+ let shown = false;
88
101
 
89
102
  backdrops.forEach((backdrop, index) => {
90
- if (index > 0 && index === backdrops.length - 1) {
103
+ if (shown || index > 0 && index === backdrops.length - 1) {
91
104
  backdrop.style.display = 'none';
92
105
  } else {
93
106
  backdrop.style.zIndex = zIndex - 1;
94
107
  backdrop.style.display = '';
108
+ shown = true;
95
109
  }
96
110
  });
97
111
  }