structured-fw 0.8.6 → 0.8.7

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 CHANGED
@@ -5,9 +5,6 @@ Framework allows the developer to develop self-contained components which are re
5
5
 
6
6
  It works with Node.js and Deno runtimes. Other runtimes are not tested.
7
7
 
8
- > [!NOTE]
9
- > While Structured framework is in development for a couple of years and is production tested, the npm package is introduced recently and there were still issues that came up with it in the past few versions. Since version 0.8.3 all should be functional. Please update to latest version and check for updates regularly until the npm package is as stable as the framework itself. Feel free to open issues on the github page if you have any issues with the npm package or the framework. _Structured followed versioning x.y.z where z was a single digit 0-9, but since there were a couple of versions that introduced no changes to the framework and were just npm package tweaks, I decided to make an exception to the rule and allow myself to use 2 digits for such updates._
10
-
11
8
  - [Why Structured](#why-structured)
12
9
  - [Audience](#audience)
13
10
  - [Getting started](#getting-started)
@@ -691,6 +688,69 @@ then in ComponentName.html:
691
688
  <div data-if="showDiv()"></div>
692
689
  ```
693
690
 
691
+ ### Layout
692
+ Prior to version 0.8.7:
693
+
694
+ 1) `/app/views/layout.html`
695
+ ```
696
+ ...
697
+ {{{layoutComponent component data attributes}}}
698
+ ...
699
+ ```
700
+ 2) `/app/routes/Test.ts`
701
+ ```
702
+ import Document from 'structured-fw/Document';
703
+
704
+ app.request.on('GET', '/test', async (ctx) => {
705
+ const doc = new Document(app, 'Title', ctx);
706
+ await doc.loadComponent('layout', {
707
+ component: 'ComponentName',
708
+ data: {
709
+ something: 123
710
+ }
711
+ });
712
+ return doc;
713
+ });
714
+ ```
715
+
716
+ Version 0.8.7 introduced the `Layout` class, which allows accomplishing the above in a nicer way:
717
+ 1) `/app/views/layout.html`
718
+ ```
719
+ ...
720
+ <template></template>
721
+ ...
722
+ ```
723
+ 2) `/index.ts` (`app` is an instance of `Application`)
724
+ ```
725
+ export const layout = new Layout(app, 'layout');
726
+ ```
727
+ 3) `/app/routes/Test.ts`
728
+ ```
729
+ import { layout } from '../../index.js';
730
+
731
+ app.request.on('GET', '/test', async (ctx) => {
732
+ return await layout.document(ctx, 'Test', 'Conditionals', {
733
+ something: 123
734
+ });
735
+ });
736
+
737
+ ```
738
+
739
+ While with the new approach there is an extra step where we create the instance(s) of `Layout`, it makes the route/template code cleaner (you will create your layout instance(s) only once, while you will likely use it in many routes, so adding an extra step is worth it).
740
+
741
+ ```
742
+ Layout.document(
743
+ ctx: RequestContext,
744
+ title: string,
745
+ componentName: string,
746
+ data?: LooseObject
747
+ ): Promise<Document>
748
+ ```
749
+ `Layout.document` the only method of Layout you will use, it creates an instance of Document, loads template component (provided as second argument to Layout constructor) into it and loads `componentName` component in place of `<template></template>` found within your template.
750
+
751
+ > [!TIP]
752
+ > You will often want to use a few different layouts in your web application. You can achieve that by creating and exporting multiple instances of Layout and use the appropriate one where you need it.
753
+
694
754
  **Basic animation/transitions**\
695
755
  If you use conditionals on any DOM node, you may also enable basic animations/transitions using following attributes:
696
756
  - Enable transition:
@@ -4,6 +4,7 @@ export declare class Net {
4
4
  request(method: RequestMethod, url: string, headers?: IncomingHttpHeaders, body?: any, responseType?: XMLHttpRequestResponseType): Promise<string>;
5
5
  get(url: string, headers?: IncomingHttpHeaders): Promise<string>;
6
6
  delete(url: string, headers?: IncomingHttpHeaders): Promise<string>;
7
+ private serializeData;
7
8
  post(url: string, data: any, headers?: IncomingHttpHeaders): Promise<string>;
8
9
  put(url: string, data: any, headers?: IncomingHttpHeaders): Promise<string>;
9
10
  getJSON<T>(url: string, headers?: IncomingHttpHeaders): Promise<T>;
@@ -10,18 +10,19 @@ export class Net {
10
10
  async delete(url, headers = {}) {
11
11
  return this.request('DELETE', url, headers);
12
12
  }
13
- async post(url, data, headers = {}) {
13
+ serializeData(data, headers) {
14
14
  if (typeof data === 'object' && !headers['content-type'] && !(data instanceof FormData)) {
15
15
  headers['content-type'] = 'application/json';
16
- data = JSON.stringify(data);
16
+ return JSON.stringify(data);
17
17
  }
18
+ return data;
19
+ }
20
+ async post(url, data, headers = {}) {
21
+ data = this.serializeData(data, headers);
18
22
  return await this.request('POST', url, headers, data);
19
23
  }
20
24
  async put(url, data, headers = {}) {
21
- if (typeof data === 'object' && !headers['content-type']) {
22
- headers['content-type'] = 'application/json';
23
- data = JSON.stringify(data);
24
- }
25
+ data = this.serializeData(data, headers);
25
26
  return this.request('PUT', url, headers, data);
26
27
  }
27
28
  async getJSON(url, headers) {
@@ -8,7 +8,7 @@ import { Handlebars } from './Handlebars.js';
8
8
  import { Cookies } from './Cookies.js';
9
9
  export declare class Application {
10
10
  readonly config: StructuredConfig;
11
- private initialized;
11
+ initialized: boolean;
12
12
  server: null | Server;
13
13
  listening: boolean;
14
14
  private readonly eventEmitter;
@@ -26,7 +26,7 @@ export declare class Application {
26
26
  importEnv<T extends LooseObject>(smartPrimitives?: boolean): T;
27
27
  exportContextFields(...fields: Array<keyof RequestContextData>): void;
28
28
  contentType(extension: string): string | false;
29
- registerPlugin(callback: (app: Application, ...args: Array<any>) => void | Promise<void>, ...args: Array<any>): Promise<void>;
29
+ registerPlugin<Opt extends Readonly<LooseObject>>(callback: (app: Application, options: Opt) => void | Promise<void>, opts: NoInfer<Opt>): Promise<void>;
30
30
  private respondWithComponent;
31
31
  memoryUsage(): NodeJS.MemoryUsage;
32
32
  printMemoryUsage(): void;
@@ -141,11 +141,11 @@ export class Application {
141
141
  contentType(extension) {
142
142
  return mime.contentType(extension);
143
143
  }
144
- async registerPlugin(callback, ...args) {
144
+ async registerPlugin(callback, opts) {
145
145
  if (this.initialized) {
146
146
  console.warn('Plugin registered after app is initialized, some plugin features may not work.');
147
147
  }
148
- await callback.apply(this, [this, ...args]);
148
+ await callback.apply(this, [this, opts]);
149
149
  }
150
150
  async respondWithComponent(ctx, componentName, attributes, unwrap = true) {
151
151
  const component = this.components.getByName(componentName);
@@ -0,0 +1,10 @@
1
+ import { LooseObject, RequestContext } from "../Types.js";
2
+ import { Application } from "./Application.js";
3
+ import { Document } from "./Document.js";
4
+ export declare class Layout {
5
+ layoutComponent: string;
6
+ app: Application;
7
+ constructor(app: Application, layoutComponent: string);
8
+ private layoutComponentExists;
9
+ document(ctx: RequestContext, title: string, componentName: string, data?: LooseObject): Promise<Document>;
10
+ }
@@ -0,0 +1,30 @@
1
+ import { Component } from "./Component.js";
2
+ import { Document } from "./Document.js";
3
+ export class Layout {
4
+ constructor(app, layoutComponent) {
5
+ this.app = app;
6
+ this.layoutComponent = layoutComponent;
7
+ if (this.app.initialized) {
8
+ this.layoutComponentExists();
9
+ }
10
+ else {
11
+ this.app.on('afterComponentsLoaded', () => { this.layoutComponentExists(); });
12
+ }
13
+ }
14
+ layoutComponentExists() {
15
+ if (this.app.components.getByName(this.layoutComponent) === null) {
16
+ throw new Error(`Layout component "${this.layoutComponent}" not found`);
17
+ }
18
+ }
19
+ async document(ctx, title, componentName, data) {
20
+ const doc = new Document(this.app, title, ctx);
21
+ await doc.loadComponent(this.layoutComponent, data);
22
+ const layoutComponent = doc.dom.queryByTagName('template');
23
+ if (layoutComponent.length === 0) {
24
+ throw new Error(`<template></template> not found in the layout component ${this.layoutComponent}`);
25
+ }
26
+ const component = new Component(componentName, layoutComponent[0], doc, false);
27
+ await component.init(`<${componentName}></${componentName}>`, data);
28
+ return doc;
29
+ }
30
+ }
package/index.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import { Application } from "structured-fw/Application";
2
2
  import { config } from './Config.js';
3
+ import { Layout } from "./system/server/Layout.js";
3
4
 
4
5
  const app = new Application(config);
5
6
 
6
- app.registerPlugin((app, a, b) => {
7
- console.log(a, b)
8
- }, 'a', 'b');
7
+ app.registerPlugin(async (app) => {
8
+
9
+ }, {
10
+ poop: 123
11
+ });
9
12
 
10
13
  // app.on('afterComponentsLoaded', (components) => {
11
14
  // components.componentNames.forEach((componentName) => {
@@ -22,4 +25,6 @@ app.on('documentCreated', (document) => {
22
25
  document.on('componentCreated', (component) => {
23
26
  document.head.add(`<script>console.log('${component.name}')</script>`);
24
27
  });
25
- })
28
+ });
29
+
30
+ export const layout: Layout = new Layout(app, 'layout');
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "license": "MIT",
15
15
  "type": "module",
16
16
  "main": "build/index",
17
- "version": "0.8.6",
17
+ "version": "0.8.7",
18
18
  "scripts": {
19
19
  "develop": "tsc --watch",
20
20
  "startDev": "cd build && nodemon --watch '../app/**/*' --watch '../build/**/*' -e js,html,css index.js",
@@ -48,6 +48,7 @@
48
48
  "./Util": "./build/system/Util.js",
49
49
  "./Application": "./build/system/server/Application.js",
50
50
  "./Document": "./build/system/server/Document.js",
51
+ "./Layout": "./build/system/server/Layout.js",
51
52
  "./FormValidation": "./build/system/server/FormValidation.js",
52
53
  "./ClientComponent": "./build/system/client/ClientComponent.js"
53
54
  }