@qbix/q 1.0.5

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +678 -0
  3. package/dist/Metrics.js +2873 -0
  4. package/dist/Metrics.min.js +95 -0
  5. package/dist/Q.js +15677 -0
  6. package/dist/Q.min.js +385 -0
  7. package/dist/Q.minimal.js +11594 -0
  8. package/dist/Q.minimal.min.js +286 -0
  9. package/dist/handlebars-v4.0.10.min.js +29 -0
  10. package/dist/handlebars.minimal.min.js +1 -0
  11. package/dist/img/hints/rotate-left.gif +0 -0
  12. package/dist/img/hints/swipe-down.gif +0 -0
  13. package/dist/img/hints/swipe-up.gif +0 -0
  14. package/dist/img/hints/tap.gif +0 -0
  15. package/dist/img/throbbers/loading.gif +0 -0
  16. package/dist/jquery.minimal.min.js +18 -0
  17. package/dist/methods/Q/Audio/load.js +29 -0
  18. package/dist/methods/Q/Audio/loadVoices.js +36 -0
  19. package/dist/methods/Q/Audio/play.js +47 -0
  20. package/dist/methods/Q/Audio/speak.js +149 -0
  21. package/dist/methods/Q/Crypto/delegate.js +186 -0
  22. package/dist/methods/Q/Crypto/internalKeypair.js +170 -0
  23. package/dist/methods/Q/Crypto/sign.js +200 -0
  24. package/dist/methods/Q/Crypto/verify.js +212 -0
  25. package/dist/methods/Q/Crypto/verifyDelegated.js +214 -0
  26. package/dist/methods/Q/Data/Bloom/_internal.js +163 -0
  27. package/dist/methods/Q/Data/Bloom/create.js +23 -0
  28. package/dist/methods/Q/Data/Bloom/fromBase64.js +21 -0
  29. package/dist/methods/Q/Data/Bloom/fromBytes.js +15 -0
  30. package/dist/methods/Q/Data/Bloom/fromElements.js +33 -0
  31. package/dist/methods/Q/Data/Merkle/_internal.js +68 -0
  32. package/dist/methods/Q/Data/Merkle/build.js +29 -0
  33. package/dist/methods/Q/Data/Merkle/proof.js +50 -0
  34. package/dist/methods/Q/Data/Merkle/verify.js +32 -0
  35. package/dist/methods/Q/Data/Prolly/_internal.js +190 -0
  36. package/dist/methods/Q/Data/Prolly/build.js +28 -0
  37. package/dist/methods/Q/Data/Prolly/delete.js +28 -0
  38. package/dist/methods/Q/Data/Prolly/diff.js +70 -0
  39. package/dist/methods/Q/Data/Prolly/get.js +38 -0
  40. package/dist/methods/Q/Data/Prolly/set.js +32 -0
  41. package/dist/methods/Q/Data/compress.js +45 -0
  42. package/dist/methods/Q/Data/decompress.js +35 -0
  43. package/dist/methods/Q/Data/decrypt.js +55 -0
  44. package/dist/methods/Q/Data/derive.js +76 -0
  45. package/dist/methods/Q/Data/digest.js +29 -0
  46. package/dist/methods/Q/Data/encrypt.js +61 -0
  47. package/dist/methods/Q/Data/generateKey.js +40 -0
  48. package/dist/methods/Q/Data/hkdf.js +44 -0
  49. package/dist/methods/Q/Data/importKey.js +34 -0
  50. package/dist/methods/Q/Data/sign.js +42 -0
  51. package/dist/methods/Q/Data/verify.js +45 -0
  52. package/dist/methods/Q/Onboarding/handle.js +50 -0
  53. package/dist/methods/Q/Onboarding/start.js +165 -0
  54. package/dist/methods/Q/Onboarding/stop.js +21 -0
  55. package/dist/methods/Q/Sandbox/run.js +392 -0
  56. package/dist/methods/Q/Tool/define/component.js +218 -0
  57. package/dist/methods/Q/globalMemoryWalk.js +82 -0
  58. package/dist/methods/Q/leaves.js +34 -0
  59. package/dist/methods/Q/registerWebComponent.js +0 -0
  60. package/dist/methods/Q/sanitize.js +142 -0
  61. package/dist/test.html +6 -0
  62. package/dist/tools/Q/lazyload.js +433 -0
  63. package/package.json +26 -0
package/README.md ADDED
@@ -0,0 +1,678 @@
1
+ # Q.js
2
+ All-In-One Front-End Web Framework from Qbix, alternative to jQuery, Angular, Vue etc.
3
+
4
+ Size: ~40KB (Minified + GZipped), [compare to other frameworks](https://gist.github.com/Restuta/cda69e50a853aa64912d)
5
+
6
+ How to use: copy contents of `dist` into your project, and then include it like this:
7
+ | File Type | Code to Use |
8
+ |------------|-------------|
9
+ |`.html` files| `<script type="module" src="https://unpkg.com/@qbix/q.js@1.0.2/dist/Q.min.js">`|
10
+ |`.js` or `.ts` files|`import Q from 'https://unpkg.com/@qbix/q.js@1.0.2/dist/Q.min.js';`
11
+ |<img src="https://github.com/user-attachments/assets/ba3df93e-0cd8-4189-93fc-11947b63b684" alt="Description" width="100" height="87"> | Full documentation here: https://qbix.com/platform/guide/javascript |
12
+
13
+ This is part of the much larger full-stack [Qbix Platform](https://github.com/Qbix/Platform) that contains many pre-built reusable tools, plugins, and requires PHP and Node.js on the back-end. If you want to build an entire full-stack social network like Facebook you're well-advised to go with that. But if you just want to use the lightweight front-end core, with your own back-end and other frameworks, then start with this framework here.
14
+
15
+ # 🌟 Advantages of Q.js vs other frameworks
16
+
17
+ **1. No build step**
18
+ React/Vue/Angular need bundlers, transpilers, tree-shakers, and hydration layers. Q.js works with plain `.html` and `.js` β€” drop it in and it runs.
19
+
20
+ **2. Tiny footprint (~40KB gzipped)**
21
+ Q.minimal.js is smaller than React (even without ReactDOM), Vue runtime, and far smaller than Angular. Yet it packs components, events, routing, caching, batching, i18n, animations, IndexedDB, service workers, and more β€” all in core.
22
+
23
+ **3. Direct DOM updates (no Virtual DOM overhead)**
24
+ No diffing, no reconciler. Q.js uses `requestAnimationFrame` and `.rendering()` hooks for high-performance, granular updates.
25
+
26
+ **4. Autoloading methods & tools**
27
+ Any method, template, or tool can be defined in its own file and is only loaded when first used. No code-splitting configs, no manual lazy-loading hacks β€” it’s automatic.
28
+
29
+ **5. Tools = components + behaviors**
30
+ Like React components or Vue directives, but attachable as *behaviors* to any DOM element. Multiple tools can live on the same element, making composition more flexible than β€œone component per node.”
31
+
32
+ **6. Built-in batching & caching**
33
+ APIs like `Q.getter()` and `Q.batcher()` deduplicate, throttle, and combine calls automatically. If multiple parts of the UI request the same object, it’s fetched once and everyone gets the result.
34
+
35
+ **7. Templates that Just Workβ„’**
36
+ Use Handlebars, `.html <template>` files (Vue-style), or JS-defined templates. Designers can work in HTML, devs can work in JS β€” both paths integrate seamlessly.
37
+
38
+ **8. First-class events & lifecycle**
39
+ Events are observable, chainable (`a.and(b)`, `a.or(b)`, `a.until(b)`), and auto-clean up when a tool or page unloads. No more memory leaks or dangling listeners.
40
+
41
+ **9. Progressive enhancement & SEO-friendly**
42
+ Pages can render server-side HTML and Q.js simply activates tools as needed. No β€œblank page until hydration” problem β€” works with or without JS.
43
+
44
+ **10. Unified, full-stack philosophy**
45
+ It’s not just a front-end library. Q.js is part of the larger Qbix platform, which powers real social apps (with accounts, feeds, groups, payments, etc.). You can start small with Q.minimal.js, and later plug into the full stack without rewriting.
46
+
47
+ Q.js is **smaller than jQuery, faster than React, easier than Vue, and more complete than Angular**.
48
+
49
+ # πŸ” Features
50
+
51
+ Despite its size, Q.js implements many features not found in other front-end frameworks.
52
+ Here is an overview of the main ones:
53
+
54
+ | Class Name | Description |
55
+ |------------|-------------|
56
+ | `Q.Tool` | reusable components, activate with `Q.activate(element)` |
57
+ | `Q.Page` | HTML pages, [for your SPA](https://developer.mozilla.org/en-US/docs/Glossary/SPA), routes, browser history |
58
+ | `Q.Event` | events and handlers, loaded and unloaded automatically |
59
+ | `Q.Template` | for rendering templates, integrates with [Handlebars](https://github.com/handlebars-lang/handlebars.js) |
60
+ | `Q.Text` | for loading internationalized translations and text for templates |
61
+ | `Q.Method` | defines methods loading JS files asynchronously as needed |
62
+ | `Q.Visual` | managing the front end interface, standard hints, as well as `Q.Mask` |
63
+ | `Q.Animation` | for animating using [native Javascript animation](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame) |
64
+ | `Q.Audio` | speaking, loading and playing audio, etc. |
65
+ | `Q.IndexedDB` | for easy interaction with the built-in IndexedDB |
66
+ | `Q.ServiceWorker` | to manage service workers in a standard way |
67
+ | Requests | `Q.request()`, `Q.handle()`, `Q.loadUrl()`, `Q.addScript()`, `Q.addStylesheet()` |
68
+ | Flow | `Q.chain()`, `Q.getter()`, `Q.batcher()`, `Q.promisify()`, `Q.debounce()` |
69
+ | Helpers | `Q.find()`, `Q.activate()`, `Q.cookie()`, `Q.handle()` |
70
+
71
+ # Overview
72
+
73
+ ## Main functionality
74
+
75
+ ```
76
+ // Core
77
+ Q
78
+ .init(options)
79
+ .activate(element, options)
80
+ .replace(element, source)
81
+ .extend(target, ...sources)
82
+ .copy(object, fields, levels)
83
+ .exports(fn)
84
+ .require(src, callback)
85
+ .import(src)
86
+ .handle(eventOrURL, context, args)
87
+
88
+ // Events
89
+ Q.Event
90
+ .add(handler, key)
91
+ .set(handler, key)
92
+ .remove(key)
93
+ .removeAllHandlers()
94
+ .factory(name)
95
+ .and(otherEvent)
96
+ .or(otherEvent)
97
+ .until(otherEvent)
98
+ .then()
99
+ .filter(testFn)
100
+ .map(transformFn)
101
+ .debounce(ms)
102
+ .throttle(ms)
103
+ .queue(ms)
104
+
105
+ // Tools
106
+ Q.Tool
107
+ .define(name, constructor, defaults, methods)
108
+ .prepare(elementOrTag, toolName, options)
109
+ .remove(element)
110
+ .clear(container)
111
+ .onActivate(toolName)
112
+
113
+ // Pages
114
+ Q.Page
115
+ .onLoad(urlOrName)
116
+ .onUnload(urlOrName)
117
+ .push(url, options)
118
+ .pop()
119
+ .currentUrl
120
+
121
+ // Templates
122
+ Q.Template
123
+ .set(name, template, options)
124
+ .remove(name)
125
+ .compile(source, type, options)
126
+ .render(name, fields, callback, options)
127
+ .load(name, callback, options)
128
+
129
+ // Text / i18n
130
+ Q.Text
131
+ .setLanguage(lang, locale)
132
+ .set(name, data, options)
133
+ .get(name, callback, options)
134
+ .addFor(events, prefix, names)
135
+ .addedFor(event, name, options)
136
+
137
+ // Methods
138
+ Q.Method
139
+ .define(methods, namespace, closureFn)
140
+ .options
141
+
142
+ // Visual
143
+ Q.Visual
144
+ .intersection(a, b)
145
+ .scrollLeft()
146
+ .scrollTop()
147
+ .windowWidth()
148
+ .windowHeight()
149
+ .boundingRect(element, classFilter, includeHidden)
150
+ .getX(event, index)
151
+ .getY(event, index)
152
+ .shouldOpenInNewWindow(event)
153
+
154
+ // Animation
155
+ Q.Animation
156
+ .play(fn, options)
157
+ .pause()
158
+ .jump(timeOrFrame)
159
+ .rewind()
160
+ .nextFrame()
161
+
162
+ // Audio
163
+ Q.Audio
164
+ .play(src, options)
165
+ .pause(id)
166
+ .stop(id)
167
+ .speak(text, options)
168
+
169
+ // IndexedDB
170
+ Q.IndexedDB
171
+ .open(name, version, upgradeCallback)
172
+ .get(store, key)
173
+ .put(store, value)
174
+ .remove(store, key)
175
+ .clear(store)
176
+
177
+ // Service Workers
178
+ Q.ServiceWorker
179
+ .register(script, options)
180
+ .unregister(scope)
181
+ .message(handler)
182
+
183
+ // Requests
184
+ Q.request(url, slotName, callback, options)
185
+ Q.loadUrl(url, options)
186
+ Q.addScript(src, callback, options)
187
+ Q.addStylesheet(href, options)
188
+ Q.removeStylesheet(href)
189
+ Q.cookie(name, value, options)
190
+
191
+ // Flow
192
+ Q.chain(tasks, callback)
193
+ Q.getter(original, options)
194
+ Q.batcher(batchFn, options)
195
+ Q.promisify(fn, useThis, callbackIndex)
196
+ Q.debounce(fn, ms)
197
+ ```
198
+
199
+ ## πŸ“ Templates
200
+
201
+ You can dynamically create elements in a React-like way, such as this:
202
+
203
+ ```javascript
204
+ Q.element('div', {id: "foo", "class": "bar baz"}, [
205
+ Q.element('img', {src: "foo.png"}),
206
+ Q.element('img', {src: "bar.png"})
207
+ ]);
208
+ ```
209
+
210
+ Instead, you can define [Handlebars templates](https://github.com/handlebars-lang/handlebars.js) like this:
211
+ ```javascript
212
+ Q.Template.set("Namespace/some/name", `put your template here`);
213
+ ```
214
+
215
+ And then render them later like this:
216
+ ```javascript
217
+ Q.Template.render("Namespace/some/name").then(html => Q.replace(element, html));
218
+ ```
219
+
220
+ Rendering templates that haven't been set yet causes requests to autoload from inside `Q.Template.load.options.dir`
221
+
222
+ ## πŸ› οΈ Tools
223
+
224
+ Tools represent re-usable components in Q.js -- just like in other front-end libraries, except Tools are only one part of a unified framework!
225
+
226
+ Here's how to define new types of Tools. Normally you'd define each one in its own file:
227
+
228
+ File: `Namespace/js/tools/cool/name.js`:
229
+ ```javascript
230
+ Q.Tool.define("Namespace/cool/name", constructor, defaultOptions, methods);
231
+ ```
232
+ File: `Namespace/js/tools/another.js`:
233
+ ```javascript
234
+ Q.Tool.define("Namespace/another", function (options) {
235
+ this.refresh(); // call method of tool
236
+ }, {
237
+ x: 1,
238
+ y: 2
239
+ }, {
240
+ refresh: function () {
241
+ this.state; // copy of options
242
+ this.element; // the element it was activated on
243
+ this.renderTemplate('Namespace/another/view', this.state,
244
+ function (html, elements, tools) {
245
+ // now this.x and this.y point to elements from
246
+ // the template that was rendered, while
247
+ this.element.forEachTool('Namespace/cool/name', function () {
248
+ // run whenever a child tool activates
249
+ });
250
+ }, {
251
+ some: options,
252
+ 'Namespace_cool_name_tool': {
253
+ some: childToolOptions
254
+ }
255
+ });
256
+ // this is how we handle in-place updates if x or y changes:
257
+ this.rendering([x, y], (changed, previous, timestamp) => {
258
+ Q.replace(this.elements.x, x); // very quick
259
+ Q.replace(this.elements.y, y); // very quick
260
+ this.element.addClass('updated_flash'); // some CSS effect
261
+ });
262
+ // to trigger these, anyone can simply call tool.stateChanged('x')
263
+ },
264
+ Q: {
265
+ onInit: function () {
266
+ // all child tools have been initialized
267
+ }
268
+ beforeRemove: function () {
269
+ // cleanup, but see Events section!
270
+ }
271
+ }
272
+ });
273
+
274
+ // define a template with a child tool
275
+ Q.Template.set("Namespace/another/view",
276
+ `<span class="Namespace_another_x">{{x}}</span>
277
+ <span class="Namespace_another_y">{{y}}</span>
278
+ {{{tool "Namespace/cool/name" "some-child-id" x=x y=3 z="foo"}}}`,
279
+ {
280
+ "elements": {
281
+ "x": ".Namespace_another_x",
282
+ "y": ".Namespace_another_y"
283
+ }
284
+ }
285
+ );
286
+ ```
287
+
288
+ Optionally, you can also define tools in HTML files, Vue-style, which may result in nicer
289
+ syntax highlighting for both the HTML templates and the Javascript:
290
+
291
+ ```html
292
+ <template id="Namespace/another/view">
293
+ <span class="Namespace_another_x">{{x}}</span>
294
+ </template>
295
+ <script>
296
+ Q.Tool.define("Namespace/another/x", constructor, defaultOptions, methods);
297
+ // you can Q.Template.render("Namespace/another/view") as above
298
+ </script>
299
+ ```
300
+
301
+ How to include tools in HTML:
302
+ ```html
303
+ <div class="Q_tool Namespace_tool_name Namespace-another-tool"
304
+ data-namespace-cool-name='{
305
+ "some": { "options": "go"}, "here": 2
306
+ }'
307
+ data-namespace-another='{
308
+ "some": [ "other", options", here" ]
309
+ }'
310
+ >
311
+ <p>optionally might give the tool initial content</p>
312
+ </div>
313
+ ```
314
+
315
+ How to prepare tools in JS:
316
+ ```javascript
317
+ // new component:
318
+ Q.Tool.prepare('div', toolName, options); // new div
319
+ // or add behaviors to existing element:
320
+ Q.Tool.prepare(element, toolName, options);
321
+ Q.Tool.prepare(element, anotherTool, otherOptions);
322
+ ```
323
+
324
+ Multiple Tools can be defined on the same element.
325
+ The `Q.activate()` function uses `Q.find()` to recursively find all elements with `Q_tool` CSS class, and then
326
+ activates the tools in the order they've been defined.
327
+ Tools are activated parent->child->grandchild, and then initialized grandchild->child->parent.
328
+ You can pass options at activation-time, too, targeting tools by `.classname` or `#id`:
329
+
330
+ ```javascript
331
+ Q.activate(element, {
332
+ '#specific_id': {
333
+ "override": { "some": "options" },
334
+ },
335
+ '.Namespace_another_tool': {
336
+ "some": ["more", "options" ]
337
+ }
338
+ }); // finds and activates tools, then initializes them
339
+ ```
340
+
341
+ Whenever you call `Q.activate(document.body)` it will traverse the whole document body, so it's slightly faster to call
342
+ `Q.activate()` on containers where you've recently replaced HTML, rather than the whole document body.
343
+
344
+ You can remove tools manually before removing elements:
345
+ ```javascript
346
+ Q.Tool.clear(container); // child elements only
347
+ Q.Tool.remove(element); // also on element itself
348
+ ```
349
+
350
+ but you usually don't need to, because `Q.replace(element, source)` does this for you automatically.
351
+ The `source` can be an element, document fragment, or some HTML string.
352
+
353
+ Some tools might have `data-q-retain` attributes, causing them to be retained, and not removed and re-activated, if the incoming
354
+ HTML has the same tool IDs. When a tool is retained, its `tool.Q.onRetain` event is triggered.
355
+ However, incoming tools with a `data-q-replace` attributes replace even tools that had `data-q-retain` set.
356
+
357
+ Q.js even overrides `$.fn.html()` and `$.fn.activate()` in libraries like jQuery and $cash so that you can call the following
358
+ to automatically cause old tools to be removed and new tools to be activated inside the element:
359
+ ```javascript
360
+ $(element).html(html).activate();
361
+ ```
362
+
363
+ When tools are removed, all associated event handlers are removede automaitically! (See **Events** section below).
364
+
365
+ More information: https://qbix.com/platform/guide/tools
366
+
367
+ ## πŸ“„ Pages
368
+
369
+ While Tools are reusable components, the concept of Pages is tied to HTTP resources and URLs in browsers.
370
+
371
+ `Q.Page` class can be used to manage pages and browser page history, loading and unloading their contents, stylesheets, etc.
372
+ This happens automatically when you call `Q.handle(url)` or `Q.loadUrl(url)`.
373
+ The server-side can send a JSON payload with keys like `scripts`, `stylesheets`, etc. and the framework will
374
+ call `Q.addScript()` and `Q.addStylesheet()` to add any new scripts and stylesheets, and `Q.removeStylesheet()` for any
375
+ stylesheets that are not in the new page.
376
+
377
+ Pages can be divided into named `slots` (e.g. "navigation", "content") so that only parts of a page are requested from the server.
378
+ The server typically responds with JSON containing `slots`, which is a map of `{slotName: html}` pairs, containing new HTML content.
379
+ Then `Q.replace()` is used on slots that should be replaced, and finally `Q.activate()` is called to activate any new tools.
380
+
381
+ Here is how you add code to run when a page loads and before it unloads:
382
+
383
+ ```javascript:
384
+ Q.page('Namespace/action', function () {
385
+ // runs when page is loaded
386
+ return function () {
387
+ // runs before page is unloaded
388
+ };
389
+ });
390
+ ```
391
+
392
+ `Q.Page.onLoad` and `Q.Page.onUnload` are events that occur when pages are loaded an unloaded.
393
+
394
+ `Q.Page.push()`, `Q.Page.pop()` and `Q.Page.currentUrl` works with browser history.
395
+
396
+ More information: https://qbix.com/platform/guide/pages
397
+
398
+ ## ⏰ Events
399
+
400
+ Add `onFoo: new Q.Event()` as properties on any object. By convention, the properties are called `onFoo`, and optionally `beforeFoo` (before the event occurs).
401
+
402
+ To trigger an event, call `Q.handle(event, context, arguments)`. This will call all the handlers set on the event. If any of the handlers returns `false` then all subsequent handlers are skipped and `Q.handle` returns false. While being handled, `event.occurring = true`, and afterwards, `event.occured = true`.
403
+
404
+ Use `onFoo.set(handler, key)` to set a handler on an event, or `onFoo.add(handler, key)` to set it but also call it, if the event already `.occurred === true`.
405
+
406
+ You can call `onFoo.remove(key)` to remove handlers previously set for that key. Or call `onFoo.removeAllHandlers()` to remove all handlers for that event.
407
+
408
+ Authors of new Tools and Pages use `Q.Tool.define(name, constructor)` and `Q.page(name, constructor)`. During the lifetime of a tool or page, outside code may add handlers to some events associated with those tools or pages. Normally, they'd need to follow it up with code to manually remove those events when the tool is removed or page is unloaded, _i.e._ during `tool.Q.beforeRemove` and `Q.Page.beforeUnload` events, respectively.
409
+
410
+ However, Q.js has a great way to automate the removal of events. Simply pass the `tool` or `true` instead of the key, as follows:
411
+
412
+ | Call Type | Description |
413
+ |------------|-------------|
414
+ | `event.set(handler, string)` | need to manually call `event.remove(key)` |
415
+ | `event.set(handler, tool)` | automatically removed when tool is removed |
416
+ | `event.set(handler, true)` | automatically removed when current page is unloaded |
417
+ | `$cash.on(event, tool, handler)` | automatically calls `.off()` when tool is removed |
418
+ | `$cash.on(event, true, handler)` | automatically removed when current page is unloaded |
419
+
420
+ Calling `set()` or `add()` again with the same String key replaces previous handlers set with that same key. But if the `key` is not a `String` (i.e. it's a `Q.Tool` or `true`) then the handlers are added to the existing ones, while `event.remove(tool)` `event.remove(true)` removes everything added for that tool or current page.
421
+
422
+ You can define your own event factory very easily using the `Q.Event.factory()` function. The Event Factory pattern is used in order to create events on demand. For example, here we call an event factory to produce an event and add a handler to be run whenever a tool is activated
423
+ ```javascript
424
+ Q.Tool.onActivate(toolType) // retrieves or creates new Q.Event
425
+ .add(handler, "MyModule"); // adds the handler with a key
426
+ ```
427
+
428
+ `Q.Event` also supports Observable Streams / Reactive Events, through methods like
429
+
430
+ | method | the new event |
431
+ |------------|-------------|
432
+ | `a.and(b)` | occurs when both occurred` |
433
+ | `a.or(b)` | occurs when either occurred |
434
+ | `a.stop()` | indicates event won't be occurring anymore |
435
+ | `a.until(b)` | occurs when a occurs, until b starts occurring |
436
+ | `a.then()` | occurs only after a stops occurring |
437
+ | `a.filter(test)` | occurrs only if test returns true |
438
+ | `a.map(transform)` | occurrs after transforming parameters |
439
+ | `a.debounce(ms)` | occurs only during a pause in a being fired |
440
+ | `a.throttle(ms)` | occurs when a occurs, but at most once every ms |
441
+ | `a.queue(ms)` | queues up occurrences to happen every ms |
442
+
443
+ More information: https://qbix.com/platform/guide/eventsClient
444
+
445
+ ## βš™οΈ Methods
446
+
447
+ There are multiple ways to autoload external tools on demand.
448
+
449
+ ```javascript
450
+ // Analogues of node.js modules
451
+ Q.exports(function () { ... }); // in a file
452
+ Q.require(src, callback); // loading the file
453
+ Q.import(src).then(...); // wrapper around native import
454
+ ```
455
+
456
+ But also, any asynchronous methods that utilize a callback or promise can be autoloaded:
457
+
458
+ ```javascript
459
+ Q.Data = Q.Method.define({
460
+ all: function (a, b) {
461
+ // regular method
462
+ },
463
+ digest: new Q.Method(),
464
+ compress: new Q.Method(),
465
+ decompress: new Q.Method(),
466
+ sign: new Q.Method(),
467
+ verify: new Q.Method(),
468
+ }, "Q/Data", function() {
469
+ // pass variables in a closure
470
+ return [Q, something];
471
+ }));
472
+ ```
473
+
474
+ and then you can define the methods in files like `Q/Data/digest.js`:
475
+
476
+ ```javascript
477
+ Q.exports(function (Q, something) { // receive closures from main file
478
+ return function Q_Data_digest(algorithm, payload, callback) {
479
+ // here you have access to both the parameters and the closures!!
480
+ return doStuff.then(function (result) {
481
+ callback && callback(null, result); // callback interface
482
+ return result; // promise interface
483
+ });
484
+ };
485
+ });
486
+ ```
487
+
488
+ You should consider using this extensively, to organize your front-end code and load only as needed:
489
+
490
+ ```javascript
491
+ Q.Data.sign(algorithm, payload) // autoload method's code on demand
492
+ .then(...) // continue after promise resolves
493
+ ```
494
+
495
+ ## πŸ“‚ Objects
496
+
497
+ Use `Q.copy(object, fields, levels)` to copy an object. Sub-objects may expose custom `.copy()` methods to be used.
498
+
499
+ Do `Q.extend(target, levels, obj1, obj2)` method is used to modify `target`, adding from `extension`.
500
+
501
+ Do `result = Q.extend({}, defaults, levels, obj1, obj2)` to overrides some defaults with specific values.
502
+
503
+ You can extend `Q.Event` objects, `Q.extend(target.onFoo, {"bar": handler))` will call `target.onFoo.set(handler, bar)`.
504
+ This allows you to pass custom event handlers that will be added to **a copy of** an event:
505
+
506
+ ```javascript
507
+ Q.Tool.define("Foo/bar", function (options) {
508
+ this.state // this is Q.extend({}, defaults, options);
509
+ }, {
510
+ foo: ['a', 'b'],
511
+ bar: ['a', 'b'],
512
+ onFoo: new Event(function () {
513
+ // default handler here
514
+ }, "Foo/bar"), // with sensible key name
515
+ childToolOptions: {
516
+ onBaz: new Event() // empty event
517
+ }
518
+ });
519
+ ```
520
+
521
+ Every instance of the `Foo/bar` tool actually makes a copy of the default options, and then extends them, saving the result
522
+ in `tool.state`, ready to use. So suppose you instantiated the tool as follows:
523
+
524
+ ```javascript
525
+ const element = Q.Tool.prepare('div', 'Foo/bar', {
526
+ // override some options when making element
527
+ foo: ['c', 'd'],
528
+ bar: {replace: ['c', 'd']},
529
+ onFoo: {
530
+ "Foo/bar": function () {
531
+ // override default handler, but only for this instance
532
+ }
533
+ }
534
+ });
535
+ document.body.appendChild(element);
536
+ Q.activate(element, {
537
+ // override more options at activation time
538
+ childToolOptions: {
539
+ onBaz: {
540
+ "myCoolPage": function () {
541
+ // add this handler, but only for this instance
542
+ }
543
+ }
544
+ }
545
+ });
546
+ ```
547
+
548
+ Other tool instances won't have these handlers added. They'll have **a copy of** the default options, including the event objects, that wasn't extended with these.
549
+
550
+ Also notice what `Q.extend()` does with arrays. The `foo` above would become `['a', 'b', 'c', 'd']` while the `bar` would become `['c', 'd']`, because the array was being replaced by an object with a key "replace", so it replaces the array with the given value (another array).
551
+
552
+ ## βž• Functions
553
+
554
+ By convention, methods that take options have default options defined in the `options` property on the method itself.
555
+ When called, a copy of the default options extended with any options passed to the method. Example:
556
+
557
+ ```
558
+ A.method = function (a, b, options) {
559
+ const o = Q.extend({}, A.method.options, 10, options);
560
+ }
561
+ A.method.options = {
562
+ some: {
563
+ "default": options
564
+ },
565
+ many: "levels"
566
+ }
567
+ ```
568
+
569
+ You've already seen this in action above with tools, but you're highly encouraged to use this pattern with any functions
570
+ or methods that take an object of options. It's a unified way to override default values, including events.
571
+
572
+ # πŸš€ Putting It All Together
573
+
574
+ ## πŸ“¦ Main Module
575
+
576
+ When you write an app or a plugin, you'll probably want to have Javascript file that acts as your main module.
577
+ In it, you will define the tools, methods, and other things. Here is an example:
578
+
579
+ ```javascript
580
+ /**
581
+ * Streams plugin's front end code
582
+ *
583
+ * @module Streams
584
+ * @class Streams
585
+ */
586
+ "use strict";
587
+ (function(Q, $) { // $ can be jQuery, $Cash, in any case it's optional
588
+
589
+ // defaults are in english, but you can override with Q.Text.get below
590
+ // by convention, modules usually store all user-facing text in Q.text:
591
+ Q.text.Streams = {
592
+ onboarding: {
593
+ prompt: "Fill our your basic information to complete your signup.",
594
+ title: "Basic Information"
595
+ }
596
+ };
597
+
598
+ // set default text file for tools and templates
599
+ // with names that start with "Streams/"
600
+ Q.Text.addFor(
601
+ ['Q.Tool.define', 'Q.Template.set'],
602
+ 'Streams/', ["Streams/content"]
603
+ );
604
+
605
+ Q.Tool.define({
606
+ // specify js and css
607
+ "Streams/chat": {
608
+ js: "{{Streams}}/js/tools/chat.js",
609
+ css: "{{Streams}}/css/tools/chat.css"
610
+ },
611
+ // or define tools as html file (Vue-style)
612
+ "Streams/comments": {
613
+ html: "Streams/html/tools/comments.html"
614
+ }
615
+ // override another module's tool:
616
+ "Users/avatar": "{{Streams}}/js/tools/avatar.js",
617
+ });
618
+
619
+ Q.onInit.add(function _Streams_onInit() {
620
+ Q.Text.get('Streams/content').then(text => {
621
+ Q.extend(Q.text.Streams, 10, text);
622
+ });
623
+ });
624
+ })(Q, jQuery);
625
+ ```
626
+
627
+ # βš–οΈ Comparison with React, Vue, Angular, Svelte
628
+
629
+ | Feature πŸ† | **Q.js** ⚑ (40KB) | **React** πŸ—οΈ | **Vue** 🎨 | **Angular** πŸ›οΈ | **Svelte** πŸ”₯ |
630
+ |---------------------------|------------------|--------------|------------|----------------|---------------|
631
+ | **Bundle Size** πŸ“¦ | **~40KB gzipped (core + tools + events + routing)** | 42KB + ReactDOM (120KB) | ~60KB runtime | 140KB+ | ~50KB compiler/runtime |
632
+ | **Build Step** πŸ› οΈ | **None (drop-in, works with `.html` + `.js`)** | Required | Required | Required | Required |
633
+ | **Rendering Approach** 🎨 | **Direct DOM Updates (No Diffing, No Virtual DOM)** | Virtual DOM diffing | Virtual DOM diffing | Change detection via zones | Precompiled updates (no VDOM, still re-renders) |
634
+ | **Performance** ⚑ | **Ultra-Fast (Only Updates What’s Needed, No Extra Work)** | Good, but reconciliation overhead | Good, but reconciliation overhead | Heavy watchers/zones | Fast, but dependencies rerender |
635
+ | **Memory Usage** 🧠 | **Low (No Virtual DOM, Minimal Garbage Collection)** | Higher (VDOM objects + GC) | Higher (VDOM overhead) | Higher (framework runtime) | Lower than React, some overhead |
636
+ | **State Management** πŸ“¦ | **Q.Streams + Events (lightweight, no reconciliation)** | React state / Redux / Context | Vuex / Pinia | Services | Reactive stores |
637
+ | **SSR & Hydration** 🌍 | **Pre-renders HTML + activates Tools dynamically** | Hydrates VDOM (slower) | Hydrates VDOM | Hydrates Angular components | Needs hydration after precompile |
638
+ | **Component Model** 🧩 | **Q.Tools (behaviors on any DOM element)** | JSX + Hooks | Directives + templates | Components + decorators | Compiled components |
639
+ | **Interactivity & Events** 🎭 | **Direct event binding (auto-cleans on removal)** | Event handlers in JSX (hook dependencies) | Event handlers in templates | Angular event bindings | Reactive bindings |
640
+ | **Batch Updates** πŸš€ | **Efficient (requestAnimationFrame + `.rendering()`)** | `setState` batching | NextTick batching | Zone-based batching | Dependency-based, no explicit batching |
641
+ | **Lazy Loading** πŸ’€ | **Built-in (images, tools, components auto-lazyload)** | Needs 3rd party libs | Needs 3rd party libs | Built-in, but heavy | Manual setup |
642
+ | **Internationalization** 🌐 | **Built-in (`Q.Text`)** | 3rd party | 3rd party | i18n module | 3rd party |
643
+ | **Incremental Adoption** πŸ”Œ | **Yes (drop-in, enhance existing HTML without rewrite)** | No | No | No | No |
644
+ | **SEO & Progressive Enhancement** πŸ” | **Works with static HTML (enhances dynamically)** | Needs JS hydration | Needs JS hydration | Needs JS hydration | Needs JS for interactivity |
645
+ | **Ecosystem Dependence** 🌐 | **All-in-one (routing, templates, events, batching, caching built-in)** | Needs Router, Redux, i18n, etc. | Needs Vuex, Router, i18n | Huge framework but still many extra libs | Needs Kit/Sapper + libs |
646
+ | **Learning Curve** πŸ“š | **Simple (declarative, minimal magic)** | Medium-high (hooks, context, JSX) | Medium (directives, reactivity caveats) | High (decorators, DI, RxJS) | Medium |
647
+ | **Best For** βœ… | **High-performance apps, real-time dashboards, low-latency UI, social platforms** | Full-scale apps, large component hierarchies | Small-to-medium apps, good DX | Enterprise-scale apps | Small-to-medium apps, hobby projects |
648
+
649
+ ```
650
+
651
+
652
+ # πŸ“Š Metrics.js β€” Standalone Telemetry
653
+
654
+ This repo also includes **Metrics.js** β€” a standalone telemetry library that tracks scroll depth, section engagement, and video/audio playback across 9 embed providers. No dependencies, no build step, works on any website.
655
+
656
+ ```html
657
+ <script src="https://unpkg.com/@qbix/q.js/dist/Metrics.js"></script>
658
+ <script>
659
+ Metrics.init({ endpoint: '/telemetry', page: document.title });
660
+ Metrics.ScrollTracker.init({ sections: 'h2[id]' });
661
+ Metrics.MediaTracker.init(); // auto-discovers YouTube, Vimeo, SoundCloud, Wistia, JW Player, Dailymotion, Spotify, Twitch, Muse.ai
662
+ </script>
663
+ ```
664
+
665
+ Unique features:
666
+ - **Watched seconds** β€” tracks unique seconds of video/audio covered, not just total playback. Seeking back and replaying doesn't inflate the number.
667
+ - **Section engagement** β€” knows which heading the user is actually reading, not just how far they scrolled.
668
+ - **Persistent visitor ID** β€” `localStorage` with `sessionStorage` fallback. ITP-safe because it's first-party storage.
669
+ - **9 media providers** β€” auto-detected, APIs loaded on demand, no configuration needed.
670
+ - **Zero Q.js dependency** β€” works completely standalone. But if Q.js is loaded, Metrics automatically gains CSRF tokens, `Q.Page` SPA tracking, `Q/tabs` and `Q/columns` auto-hooks, and debounced server-side state updates.
671
+
672
+ πŸ‘‰ **[Full Metrics.js documentation β†’](Metrics.md)**
673
+
674
+ ## From Metrics.js to the Qbix Platform
675
+
676
+ Metrics.js is the lightweight entry point. For the full server-side stack β€” visit attribution, referral chain walking, multi-level commissions, real-time WebSocket updates, and per-component Merkle-tree cache invalidation β€” install the [Qbix Platform](https://github.com/Qbix/Platform) with the Metrics plugin.
677
+
678
+ The platform includes a high-performance [webserver](https://github.com/Qbix/webserver) (Cosmopolitan C binary) that maintains a Merkle tree of rendered component hashes. When data changes, only the affected components re-render β€” not the whole page. Combined with static site generation for CDN caching and `Streams/check` for per-stream change detection over WebSocket, you get sub-second updates with near-zero server load. No PHP required for serving β€” the webserver handles everything.