native-document 1.0.0
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/dist/native-document.dev.js +2346 -0
- package/dist/native-document.min.js +1 -0
- package/elements.js +2 -0
- package/index.js +11 -0
- package/package.json +16 -0
- package/readme.md +495 -0
- package/rollup.config.js +29 -0
- package/router.js +9 -0
- package/src/data/MemoryManager.js +60 -0
- package/src/data/Observable.js +162 -0
- package/src/data/ObservableChecker.js +24 -0
- package/src/data/ObservableItem.js +101 -0
- package/src/data/Store.js +74 -0
- package/src/elements/content-formatter.js +32 -0
- package/src/elements/control/for-each.js +110 -0
- package/src/elements/control/show-if.js +86 -0
- package/src/elements/control/switch.js +88 -0
- package/src/elements/description-list.js +5 -0
- package/src/elements/form.js +71 -0
- package/src/elements/html5-semantics.js +12 -0
- package/src/elements/img.js +45 -0
- package/src/elements/index.js +21 -0
- package/src/elements/interactive.js +7 -0
- package/src/elements/list.js +6 -0
- package/src/elements/medias.js +8 -0
- package/src/elements/meta-data.js +9 -0
- package/src/elements/table.js +14 -0
- package/src/errors/ArgTypesError.js +7 -0
- package/src/errors/NativeDocumentError.js +8 -0
- package/src/errors/RouterError.js +9 -0
- package/src/router/Route.js +102 -0
- package/src/router/RouteGroupHelper.js +52 -0
- package/src/router/Router.js +232 -0
- package/src/router/RouterComponent.js +37 -0
- package/src/router/link.js +27 -0
- package/src/router/modes/HashRouter.js +83 -0
- package/src/router/modes/HistoryRouter.js +66 -0
- package/src/router/modes/MemoryRouter.js +71 -0
- package/src/utils/args-types.js +100 -0
- package/src/utils/debug-manager.js +34 -0
- package/src/utils/helpers.js +37 -0
- package/src/utils/prototypes.js +16 -0
- package/src/utils/validator.js +96 -0
- package/src/wrappers/AttributesWrapper.js +94 -0
- package/src/wrappers/DocumentObserver.js +51 -0
- package/src/wrappers/HtmlElementEventsWrapper.js +77 -0
- package/src/wrappers/HtmlElementWrapper.js +174 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const Form = HtmlElementWrapper('form', function(el) {
|
|
5
|
+
|
|
6
|
+
el.submit = function(action) {
|
|
7
|
+
if(typeof action === 'function') {
|
|
8
|
+
el.on.submit((e) => {
|
|
9
|
+
e.preventDefault();
|
|
10
|
+
action(e);
|
|
11
|
+
});
|
|
12
|
+
return el;
|
|
13
|
+
}
|
|
14
|
+
this.setAttribute('action', action);
|
|
15
|
+
return el;
|
|
16
|
+
};
|
|
17
|
+
el.multipartFormData = function() {
|
|
18
|
+
this.setAttribute('enctype', 'multipart/form-data');
|
|
19
|
+
return el;
|
|
20
|
+
}
|
|
21
|
+
el.post = function(action) {
|
|
22
|
+
this.setAttribute('method', 'post');
|
|
23
|
+
this.setAttribute('action', action);
|
|
24
|
+
return el;
|
|
25
|
+
};
|
|
26
|
+
el.get = function(action) {
|
|
27
|
+
this.setAttribute('method', 'get');
|
|
28
|
+
this.setAttribute('action', action);
|
|
29
|
+
};
|
|
30
|
+
return el;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const Input = HtmlElementWrapper('input');
|
|
34
|
+
|
|
35
|
+
export const TextArea = HtmlElementWrapper('textarea');
|
|
36
|
+
export const TextInput = TextArea;
|
|
37
|
+
|
|
38
|
+
export const Select = HtmlElementWrapper('select');
|
|
39
|
+
export const FieldSet = HtmlElementWrapper('fieldset', );
|
|
40
|
+
export const Option = HtmlElementWrapper('option');
|
|
41
|
+
export const Legend = HtmlElementWrapper('legend');
|
|
42
|
+
export const Datalist = HtmlElementWrapper('datalist');
|
|
43
|
+
export const Output = HtmlElementWrapper('output');
|
|
44
|
+
export const Progress = HtmlElementWrapper('progress');
|
|
45
|
+
export const Meter = HtmlElementWrapper('meter');
|
|
46
|
+
|
|
47
|
+
export const ReadonlyInput = (attributes) => Input({ readonly: true, ...attributes });
|
|
48
|
+
export const HiddenInput = (attributes) => Input({type: 'hidden', ...attributes });
|
|
49
|
+
export const FileInput = (attributes) => Input({ type: 'file', ...attributes });
|
|
50
|
+
export const PasswordInput = (attributes) => Input({ type: 'password', ...attributes });
|
|
51
|
+
export const Checkbox = (attributes) => Input({ type: 'checkbox', ...attributes });
|
|
52
|
+
export const Radio = (attributes) => Input({ type: 'radio', ...attributes });
|
|
53
|
+
|
|
54
|
+
export const RangeInput = (attributes) => Input({ type: 'range', ...attributes });
|
|
55
|
+
export const ColorInput = (attributes) => Input({ type: 'color', ...attributes });
|
|
56
|
+
export const DateInput = (attributes) => Input({ type: 'date', ...attributes });
|
|
57
|
+
export const TimeInput = (attributes) => Input({ type: 'time', ...attributes });
|
|
58
|
+
export const DateTimeInput = (attributes) => Input({ type: 'datetime-local', ...attributes });
|
|
59
|
+
export const WeekInput = (attributes) => Input({ type: 'week', ...attributes });
|
|
60
|
+
export const MonthInput = (attributes) => Input({ type: 'month', ...attributes });
|
|
61
|
+
export const SearchInput = (attributes) => Input({ type: 'search', ...attributes });
|
|
62
|
+
export const TelInput = (attributes) => Input({ type: 'tel', ...attributes });
|
|
63
|
+
export const UrlInput = (attributes) => Input({ type: 'url', ...attributes });
|
|
64
|
+
export const EmailInput = (attributes) => Input({ type: 'email', ...attributes });
|
|
65
|
+
export const NumberInput = (attributes) => Input({ type: 'number', ...attributes });
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export const Button = HtmlElementWrapper('button');
|
|
69
|
+
export const SimpleButton = (child, attributes) => Button(child, { type: 'button', ...attributes });
|
|
70
|
+
export const SubmitButton = (child, attributes) => Button(child, { type: 'submit', ...attributes });
|
|
71
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
|
|
2
|
+
|
|
3
|
+
export const Main = HtmlElementWrapper('main');
|
|
4
|
+
export const Section = HtmlElementWrapper('section');
|
|
5
|
+
export const Article = HtmlElementWrapper('article');
|
|
6
|
+
export const Aside = HtmlElementWrapper('aside');
|
|
7
|
+
export const Nav = HtmlElementWrapper('nav');
|
|
8
|
+
export const Figure = HtmlElementWrapper('figure');
|
|
9
|
+
export const FigCaption = HtmlElementWrapper('figcaption');
|
|
10
|
+
|
|
11
|
+
export const Header = HtmlElementWrapper('header');
|
|
12
|
+
export const Footer = HtmlElementWrapper('footer');
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper"
|
|
2
|
+
import Validator from "../utils/validator";
|
|
3
|
+
import NativeDocumentError from "../errors/NativeDocumentError";
|
|
4
|
+
|
|
5
|
+
export const BaseImage = HtmlElementWrapper('img');
|
|
6
|
+
export const Img = function(src, attributes) {
|
|
7
|
+
return BaseImage({ src, ...attributes });
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {string} src
|
|
13
|
+
* @param {string|null} defaultImage
|
|
14
|
+
* @param {Object} attributes
|
|
15
|
+
* @param {?Function} callback
|
|
16
|
+
* @returns {Image}
|
|
17
|
+
*/
|
|
18
|
+
export const AsyncImg = function(src, defaultImage, attributes, callback) {
|
|
19
|
+
const image = Img(defaultImage || src, attributes);
|
|
20
|
+
const img = new Image();
|
|
21
|
+
img.onload = () => {
|
|
22
|
+
Validator.isFunction(callback) && callback(null, image);
|
|
23
|
+
image.src = src;
|
|
24
|
+
};
|
|
25
|
+
img.onerror = () => {
|
|
26
|
+
Validator.isFunction(callback) && callback(new NativeDocumentError('Image not found'));
|
|
27
|
+
};
|
|
28
|
+
if(Validator.isObservable(src)) {
|
|
29
|
+
src.subscribe(newSrc => {
|
|
30
|
+
img.src = newSrc;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
img.src = src;
|
|
34
|
+
return image;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param {string} src
|
|
40
|
+
* @param {Object} attributes
|
|
41
|
+
* @returns {Image}
|
|
42
|
+
*/
|
|
43
|
+
export const LazyImg = function(src, attributes) {
|
|
44
|
+
return Img(src, { ...attributes, loading: 'lazy' });
|
|
45
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
|
|
2
|
+
|
|
3
|
+
export * from './control/for-each';
|
|
4
|
+
export * from './control/show-if';
|
|
5
|
+
export * from './control/switch';
|
|
6
|
+
export * from './content-formatter';
|
|
7
|
+
export * from './description-list';
|
|
8
|
+
export * from './form';
|
|
9
|
+
export * from './html5-semantics';
|
|
10
|
+
export * from './img';
|
|
11
|
+
export * from './interactive';
|
|
12
|
+
export * from './list';
|
|
13
|
+
export * from './medias';
|
|
14
|
+
export * from './meta-data';
|
|
15
|
+
export * from './table';
|
|
16
|
+
|
|
17
|
+
export const Fragment = HtmlElementWrapper('');
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const Details = HtmlElementWrapper('details');
|
|
5
|
+
export const Summary = HtmlElementWrapper('summary');
|
|
6
|
+
export const Dialog = HtmlElementWrapper('dialog');
|
|
7
|
+
export const Menu = HtmlElementWrapper('menu');
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
|
|
2
|
+
|
|
3
|
+
export const Audio = HtmlElementWrapper('audio');
|
|
4
|
+
export const Video = HtmlElementWrapper('video');
|
|
5
|
+
export const Source = HtmlElementWrapper('source');
|
|
6
|
+
export const Track = HtmlElementWrapper('track');
|
|
7
|
+
export const Canvas = HtmlElementWrapper('canvas');
|
|
8
|
+
export const Svg = HtmlElementWrapper('svg');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
|
|
2
|
+
|
|
3
|
+
export const Time = HtmlElementWrapper('time');
|
|
4
|
+
export const Data = HtmlElementWrapper('data');
|
|
5
|
+
export const Address = HtmlElementWrapper('address');
|
|
6
|
+
export const Kbd = HtmlElementWrapper('kbd');
|
|
7
|
+
export const Samp = HtmlElementWrapper('samp');
|
|
8
|
+
export const Var = HtmlElementWrapper('var');
|
|
9
|
+
export const Wbr = HtmlElementWrapper('wbr');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import HtmlElementWrapper from "../wrappers/HtmlElementWrapper";
|
|
2
|
+
|
|
3
|
+
export const Caption = HtmlElementWrapper('caption');
|
|
4
|
+
export const Table = HtmlElementWrapper('table');
|
|
5
|
+
export const THead = HtmlElementWrapper('thead');
|
|
6
|
+
export const TFoot = HtmlElementWrapper('tfoot');
|
|
7
|
+
export const TBody = HtmlElementWrapper('tbody');
|
|
8
|
+
export const Tr = HtmlElementWrapper('tr');
|
|
9
|
+
export const TRow = Tr;
|
|
10
|
+
export const Th = HtmlElementWrapper('th');
|
|
11
|
+
export const THeadCell = Th;
|
|
12
|
+
export const TFootCell = Th;
|
|
13
|
+
export const Td = HtmlElementWrapper('td');
|
|
14
|
+
export const TBodyCell = Td;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {trim} from "../utils/helpers.js";
|
|
2
|
+
|
|
3
|
+
export const RouteParamPatterns = {
|
|
4
|
+
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {string} $path
|
|
10
|
+
* @param {Function} $component
|
|
11
|
+
* @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }}$options
|
|
12
|
+
* @class
|
|
13
|
+
*/
|
|
14
|
+
export function Route($path, $component, $options = {}) {
|
|
15
|
+
|
|
16
|
+
$path = '/'+trim($path, '/');
|
|
17
|
+
|
|
18
|
+
let $pattern = null;
|
|
19
|
+
let $name = $options.name || null;
|
|
20
|
+
|
|
21
|
+
const $middlewares = $options.middlewares || [];
|
|
22
|
+
const $shouldRebuild = $options.shouldRebuild || false;
|
|
23
|
+
const $paramsValidators = $options.with || {};
|
|
24
|
+
|
|
25
|
+
const $params = {};
|
|
26
|
+
const $paramsNames = [];
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const paramsExtractor = (description) => {
|
|
30
|
+
if(!description) return null;
|
|
31
|
+
const [name, type] = description.split(':');
|
|
32
|
+
|
|
33
|
+
let pattern = $paramsValidators[name];
|
|
34
|
+
if(!pattern && type) {
|
|
35
|
+
pattern = RouteParamPatterns[type];
|
|
36
|
+
}
|
|
37
|
+
if(!pattern) {
|
|
38
|
+
pattern = '[^/]+';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pattern = pattern.replace('(', '(?:');
|
|
42
|
+
|
|
43
|
+
return { name, pattern: `(${pattern})` };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getPattern = () => {
|
|
47
|
+
if($pattern) {
|
|
48
|
+
return $pattern;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const patternDescription = $path.replace(/\{(.*?)}/ig, (block, definition) => {
|
|
52
|
+
const description = paramsExtractor(definition);
|
|
53
|
+
if(!description || !description.pattern) return block;
|
|
54
|
+
$params[description.name] = description.pattern;
|
|
55
|
+
$paramsNames.push(description.name);
|
|
56
|
+
return description.pattern;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
$pattern = new RegExp('^'+patternDescription+'$');
|
|
60
|
+
return $pattern;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
this.name = () => $name;
|
|
64
|
+
this.component = () => $component;
|
|
65
|
+
this.middlewares = () => $middlewares;
|
|
66
|
+
this.shouldRebuild = () => $shouldRebuild;
|
|
67
|
+
this.path = () => $path;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* @param {string} path
|
|
72
|
+
*/
|
|
73
|
+
this.match = function(path) {
|
|
74
|
+
path = '/'+trim(path, '/');
|
|
75
|
+
const match = getPattern().exec(path);
|
|
76
|
+
if(!match) return false;
|
|
77
|
+
const params = {};
|
|
78
|
+
|
|
79
|
+
getPattern().exec(path).forEach((value, index) => {
|
|
80
|
+
if(index < 1) return;
|
|
81
|
+
const name = $paramsNames[index - 1];
|
|
82
|
+
params[name] = value;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return params;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* @param {{params: ?Object, query: ?Object, basePath: ?string}} configs
|
|
89
|
+
*/
|
|
90
|
+
this.url = function(configs) {
|
|
91
|
+
const path = $path.replace(/\{(.*?)}/ig, (block, definition) => {
|
|
92
|
+
const description = paramsExtractor(definition);
|
|
93
|
+
if(configs.params && configs.params[description.name]) {
|
|
94
|
+
return configs.params[description.name];
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Missing parameter '${description.name}'`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const queryString = (typeof configs.query === 'object') ? (new URLSearchParams(configs.query)).toString() : null;
|
|
100
|
+
return (configs.basePath ? configs.basePath : '') + (queryString ? `${path}?${queryString}` : path);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {trim} from "../utils/helpers.js";
|
|
2
|
+
|
|
3
|
+
export const RouteGroupHelper = {
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
|
|
7
|
+
* @param {string} path
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
fullPath: ($groupTree, path) => {
|
|
11
|
+
const fullPath = [];
|
|
12
|
+
$groupTree.forEach(group => {
|
|
13
|
+
fullPath.push(trim(group.suffix, '/'));
|
|
14
|
+
});
|
|
15
|
+
fullPath.push(trim(path, '/'));
|
|
16
|
+
return fullPath.join('/');
|
|
17
|
+
},
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
|
|
21
|
+
* @param {Function[]} middlewares
|
|
22
|
+
* @returns {Function[]}
|
|
23
|
+
*/
|
|
24
|
+
fullMiddlewares: ($groupTree, middlewares) => {
|
|
25
|
+
const fullMiddlewares = [];
|
|
26
|
+
$groupTree.forEach(group => {
|
|
27
|
+
if(group.options.middlewares) {
|
|
28
|
+
fullMiddlewares.push(...group.options.middlewares);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if(middlewares) {
|
|
32
|
+
fullMiddlewares.push(...middlewares);
|
|
33
|
+
}
|
|
34
|
+
return fullMiddlewares;
|
|
35
|
+
},
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param {{suffix: string, options: {middlewares: Function[], name: string}}[]} $groupTree
|
|
39
|
+
* @param {string} name
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
fullName: ($groupTree, name) => {
|
|
43
|
+
const fullName = [];
|
|
44
|
+
$groupTree.forEach(group => {
|
|
45
|
+
if(group.options?.name) {
|
|
46
|
+
fullName.push(group.options.name);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
name && fullName.push(name);
|
|
50
|
+
return fullName.join('.');
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {Route} from "./Route.js";
|
|
2
|
+
import Validator from "../utils/validator.js";
|
|
3
|
+
import RouterError from "../errors/RouterError.js";
|
|
4
|
+
import {RouteGroupHelper} from "./RouteGroupHelper.js";
|
|
5
|
+
import {trim} from "../utils/helpers.js";
|
|
6
|
+
import HashRouter from "./modes/HashRouter.js";
|
|
7
|
+
import HistoryRouter from "./modes/HistoryRouter.js";
|
|
8
|
+
import MemoryRouter from "./modes/MemoryRouter.js";
|
|
9
|
+
import DebugManager from "../utils/debug-manager.js";
|
|
10
|
+
import {RouterComponent} from "./RouterComponent.js";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_ROUTER_NAME = 'default';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param {{mode: 'memory'|'history'|'hash'}} $options
|
|
17
|
+
* @constructor
|
|
18
|
+
*/
|
|
19
|
+
export default function Router($options = {}) {
|
|
20
|
+
|
|
21
|
+
/** @type {Route[]} */
|
|
22
|
+
const $routes = [];
|
|
23
|
+
/** @type {{[string]: Route}} */
|
|
24
|
+
const $routesByName = {};
|
|
25
|
+
const $groupTree = [];
|
|
26
|
+
const $listeners = [];
|
|
27
|
+
const $currentState = { route: null, params: null, query: null, path: null, hash: null };
|
|
28
|
+
|
|
29
|
+
if($options.mode === 'hash') {
|
|
30
|
+
HashRouter.apply(this, []);
|
|
31
|
+
} else if($options.mode === 'history') {
|
|
32
|
+
HistoryRouter.apply(this, []);
|
|
33
|
+
} else if($options.mode === 'memory') {
|
|
34
|
+
MemoryRouter.apply(this, []);
|
|
35
|
+
} else {
|
|
36
|
+
throw new RouterError('Invalid router mode '+$options.mode);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const trigger = function(request, next) {
|
|
40
|
+
for(const listener of $listeners) {
|
|
41
|
+
try {
|
|
42
|
+
listener(request);
|
|
43
|
+
next && next(request);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
DebugManager.warn('Route Listener', 'Error in listener:', e);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.routes = () => [...$routes];
|
|
51
|
+
this.currentState = () => ({ ...$currentState });
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
*
|
|
55
|
+
* @param {string} path
|
|
56
|
+
* @param {Function} component
|
|
57
|
+
* @param {{name:?string, middlewares:Function[], shouldRebuild:Boolean, with: Object }} options
|
|
58
|
+
* @returns {this}
|
|
59
|
+
*/
|
|
60
|
+
this.add = function(path, component, options) {
|
|
61
|
+
const route = new Route(RouteGroupHelper.fullPath($groupTree, path), component, {
|
|
62
|
+
...options,
|
|
63
|
+
middlewares: RouteGroupHelper.fullMiddlewares($groupTree, options?.middlewares || []),
|
|
64
|
+
name: options?.name ? RouteGroupHelper.fullName($groupTree, options.name) : null,
|
|
65
|
+
});
|
|
66
|
+
$routes.push(route);
|
|
67
|
+
if(route.name()) {
|
|
68
|
+
$routesByName[route.name()] = route;
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
* @param {string} suffix
|
|
76
|
+
* @param {{ middlewares: Function[], name: string}} options
|
|
77
|
+
* @param {Function} callback
|
|
78
|
+
* @returns {this}
|
|
79
|
+
*/
|
|
80
|
+
this.group = function(suffix, options, callback) {
|
|
81
|
+
if(!Validator.isFunction(callback)) {
|
|
82
|
+
throw new RouterError('Callback must be a function');
|
|
83
|
+
}
|
|
84
|
+
$groupTree.push({suffix, options});
|
|
85
|
+
callback();
|
|
86
|
+
$groupTree.pop();
|
|
87
|
+
return this;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
*
|
|
92
|
+
* @param {string} name
|
|
93
|
+
* @param {Object}params
|
|
94
|
+
* @param {Object} query
|
|
95
|
+
* @returns {*}
|
|
96
|
+
*/
|
|
97
|
+
this.generateUrl = function(name, params = {}, query = {}) {
|
|
98
|
+
const route = $routesByName[name];
|
|
99
|
+
if(!route) {
|
|
100
|
+
throw new RouterError(`Route not found for name: ${name}`);
|
|
101
|
+
}
|
|
102
|
+
return route.url({ params, query });
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
*
|
|
107
|
+
* @param {string|{name:string,params?:Object, query?:Object }} target
|
|
108
|
+
* @returns {{route:Route, params:Object, query:Object, path:string}}
|
|
109
|
+
*/
|
|
110
|
+
this.resolve = function(target) {
|
|
111
|
+
if(Validator.isJson(target)) {
|
|
112
|
+
const route = $routesByName[target.name];
|
|
113
|
+
if(!route) {
|
|
114
|
+
throw new RouterError(`Route not found for name: ${target.name}`);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
route,
|
|
118
|
+
params: target.params,
|
|
119
|
+
query: target.query,
|
|
120
|
+
path: route.url({ ...target })
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const [urlPath, urlQuery] = target.split('?');
|
|
125
|
+
const path = '/'+trim(urlPath, '/');
|
|
126
|
+
let routeFound = null, params;
|
|
127
|
+
|
|
128
|
+
for(const route of $routes) {
|
|
129
|
+
params = route.match(path);
|
|
130
|
+
if(params) {
|
|
131
|
+
routeFound = route;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if(!routeFound) {
|
|
136
|
+
throw new RouterError(`Route not found for url: ${urlPath}`);
|
|
137
|
+
}
|
|
138
|
+
const queryParams = {};
|
|
139
|
+
if(urlQuery) {
|
|
140
|
+
const queries = new URLSearchParams(urlQuery).entries();
|
|
141
|
+
for (const [key, value] of queries) {
|
|
142
|
+
queryParams[key] = value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { route: routeFound, params, query: queryParams, path: target };
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
*
|
|
151
|
+
* @param {Function} listener
|
|
152
|
+
* @returns {(function(): void)|*}
|
|
153
|
+
*/
|
|
154
|
+
this.subscribe = function(listener) {
|
|
155
|
+
if(!Validator.isFunction(listener)) {
|
|
156
|
+
throw new RouterError('Listener must be a function');
|
|
157
|
+
}
|
|
158
|
+
$listeners.push(listener);
|
|
159
|
+
return () => {
|
|
160
|
+
$listeners.splice($listeners.indexOf(listener), 1);
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
*
|
|
166
|
+
* @param {Route} route
|
|
167
|
+
* @param {Object} params
|
|
168
|
+
* @param {Object} query
|
|
169
|
+
* @param {string} path
|
|
170
|
+
*/
|
|
171
|
+
this.handleRouteChange = function(route, params, query, path) {
|
|
172
|
+
$currentState.route = route;
|
|
173
|
+
$currentState.params = params;
|
|
174
|
+
$currentState.query = query;
|
|
175
|
+
$currentState.path = path;
|
|
176
|
+
|
|
177
|
+
console.log($currentState.query)
|
|
178
|
+
const middlewares = [...route.middlewares(), trigger];
|
|
179
|
+
let currentIndex = 0;
|
|
180
|
+
const request = { ...$currentState };
|
|
181
|
+
|
|
182
|
+
const next = (editableRequest) => {
|
|
183
|
+
currentIndex++;
|
|
184
|
+
if(currentIndex >= middlewares.length) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
return middlewares[currentIndex](editableRequest || request, next);
|
|
188
|
+
};
|
|
189
|
+
return middlewares[currentIndex](request, next);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
Router.routers = {};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
*
|
|
198
|
+
* @param {{mode: 'memory'|'history'|'hash', name:string, entry: string}} options
|
|
199
|
+
* @param {Function} callback
|
|
200
|
+
* @param {Element} container
|
|
201
|
+
*/
|
|
202
|
+
Router.create = function(options, callback) {
|
|
203
|
+
if(!Validator.isFunction(callback)) {
|
|
204
|
+
DebugManager.error('Router', 'Callback must be a function', e);
|
|
205
|
+
throw new RouterError('Callback must be a function');
|
|
206
|
+
}
|
|
207
|
+
const router = new Router(options);
|
|
208
|
+
Router.routers[options.name || DEFAULT_ROUTER_NAME] = router;
|
|
209
|
+
callback(router);
|
|
210
|
+
|
|
211
|
+
router.init(options.entry);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
mount: (container) => {
|
|
215
|
+
if(Validator.isString(container)) {
|
|
216
|
+
const mountContainer = document.querySelector(container);
|
|
217
|
+
if(!mountContainer) {
|
|
218
|
+
throw new RouterError(`Container not found for selector: ${container}`);
|
|
219
|
+
}
|
|
220
|
+
container = mountContainer;
|
|
221
|
+
} else if(!Validator.isElement(container)) {
|
|
222
|
+
throw new RouterError('Container must be a string or an Element');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
RouterComponent(router, container);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
Router.get = function(name) {
|
|
231
|
+
return Router.routers[name || DEFAULT_ROUTER_NAME];
|
|
232
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {Router} router
|
|
4
|
+
* @param {?HTMLElement} container
|
|
5
|
+
*/
|
|
6
|
+
export function RouterComponent(router, container) {
|
|
7
|
+
|
|
8
|
+
const $cache = new Map();
|
|
9
|
+
|
|
10
|
+
const updateContainer = function(node) {
|
|
11
|
+
container.innerHTML = '';
|
|
12
|
+
container.appendChild(node);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const handleCurrentRouterState = function(state) {
|
|
16
|
+
if(!state.route) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const { route, params, query, path } = state;
|
|
20
|
+
if($cache.has(path)) {
|
|
21
|
+
const cacheNode = $cache.get(path);
|
|
22
|
+
console.log(cacheNode);
|
|
23
|
+
updateContainer(cacheNode);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const Component = route.component()
|
|
27
|
+
console.log({ params, query })
|
|
28
|
+
const node = Component({ params, query });
|
|
29
|
+
$cache.set(path, node);
|
|
30
|
+
updateContainer(node);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
router.subscribe(handleCurrentRouterState);
|
|
34
|
+
|
|
35
|
+
handleCurrentRouterState(router.currentState());
|
|
36
|
+
return container;
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Validator from "../utils/validator.js";
|
|
2
|
+
import {Link as NativeLink} from "../../elements.js";
|
|
3
|
+
import Router from "./Router.js";
|
|
4
|
+
import RouterError from "../errors/RouterError.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function Link(attributes, children){
|
|
8
|
+
const target = attributes.to || attributes.href;
|
|
9
|
+
if(Validator.isString(target)) {
|
|
10
|
+
const router = Router.get();
|
|
11
|
+
return NativeLink({ ...attributes, href: target}, children).nd.on.prevent.click(() => {
|
|
12
|
+
router.push(target);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const router = Router.get(target.router);
|
|
16
|
+
if(!router) {
|
|
17
|
+
throw new RouterError('Router not found "'+target.router+'" for link "'+target.name+'"');
|
|
18
|
+
}
|
|
19
|
+
const url = router.generateUrl(target.name, target.params, target.query);
|
|
20
|
+
return NativeLink({ ...attributes, href: url }, children).nd.on.prevent.click(() => {
|
|
21
|
+
router.push(url);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Link.blank = function(attributes, children){
|
|
26
|
+
return NativeLink({ ...attributes, target: '_blank'}, children);
|
|
27
|
+
};
|