##// END OF EJS Templates
fixed NlsBundle locale package loading...
cin -
r112:2ccfaae984e9 v1.4.4 default
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,58 +1,60
1 import { MapOf, PromiseOrValue } from "@implab/core-amd/interfaces";
1 import { MapOf, PromiseOrValue } from "@implab/core-amd/interfaces";
2 import { argumentNotEmptyString, isPromise, mixin } from "@implab/core-amd/safe";
2 import { argumentNotEmptyString, isPromise, mixin } from "@implab/core-amd/safe";
3
3
4 export type LocaleProvider<T> = () => PromiseOrValue<T | { default: T }>;
4 export type LocaleProvider<T> = () => PromiseOrValue<T | { default: T }>;
5
5
6 function when<T, T2>(value: PromiseOrValue<T>, cb: (v: T) => PromiseOrValue<T2>): PromiseOrValue<T2> {
6 function when<T, T2>(value: PromiseOrValue<T>, cb: (v: T) => PromiseOrValue<T2>): PromiseOrValue<T2> {
7 return isPromise(value) ?
7 return isPromise(value) ?
8 value.then(cb) :
8 value.then(cb) :
9 cb(value);
9 cb(value);
10 }
10 }
11
11
12 const chainObjects = <T extends object, T2 extends object>(o1: T, o2: T2) =>
12 const loadPackage = <T extends object>(localeData: LocaleProvider<Partial<T>> | undefined) =>
13 mixin(Object.create(o1) as T, o2);
13 localeData ?
14 when(localeData(), data => data && "default" in data ? data.default : data) :
15 undefined;
16
17 const chainObjects = <T extends object>(o1: T, o2: Partial<T> | undefined): T =>
18 o2 ? mixin(Object.create(o1) as T, o2) : o1;
19
14 export class NlsBundle<T extends object> {
20 export class NlsBundle<T extends object> {
15 private readonly _locales: MapOf<LocaleProvider<Partial<T>>>;
21 private readonly _locales: MapOf<LocaleProvider<Partial<T>>>;
16
22
17 private readonly _default: T;
23 private readonly _default: T;
18
24
19 private _cache: MapOf<PromiseOrValue<T>>;
25 private _cache: MapOf<PromiseOrValue<T>>;
20
26
21 constructor(defNls: T, locales?: MapOf<LocaleProvider<Partial<T>>>) {
27 constructor(defNls: T, locales?: MapOf<LocaleProvider<Partial<T>>>) {
22 this._default = defNls;
28 this._default = defNls;
23 this._locales = locales || {};
29 this._locales = locales || {};
24 this._cache = {};
30 this._cache = {};
25 }
31 }
26
32
27 getLocale(locale: string) {
33 getLocale(locale: string) {
28 argumentNotEmptyString(locale, "locale");
34 argumentNotEmptyString(locale, "locale");
29 const _loc = locale;
35 const _loc = locale;
30
36
31 // en-US => ["en", "en-US"]
37 // en-US => ["en", "en-US"]
32 const locales = _loc.split(/-|_/).map((x, i, a) => a.slice(0, i + 1).join("-"));
38 const locales = _loc.split(/-|_/).map((x, i, a) => a.slice(0, i + 1).join("-"));
33 return this._resolveLocale(locales);
39 return this._resolveLocale(locales);
34 }
40 }
35
41
36 _resolveLocale(locales: string[]): PromiseOrValue<T> {
42 _resolveLocale(locales: string[]): PromiseOrValue<T> {
37 if (!locales.length)
43 if (!locales.length)
38 return this._default;
44 return this._default;
39
45
40 const locale = locales.pop();
46 const locale = locales.pop();
41 if (!locale)
47 if (!locale)
42 throw new Error("The locale can't be empty");
48 throw new Error("The locale can't be empty");
43
49
44 if (this._cache[locale])
50 if (this._cache[locale])
45 return this._cache[locale];
51 return this._cache[locale];
46
52
47 const data = this._loadPackage(this._locales[locale]);
53 const data = loadPackage(this._locales[locale]);
48 const parent = this._resolveLocale(locales);
54 const parent = this._resolveLocale(locales);
49
55
50 return this._cache[locale] = when(data, x => {
56 return this._cache[locale] = when(data, x => {
51 return when(parent, y => this._cache[locale] = chainObjects(y, x));
57 return when(parent, y => this._cache[locale] = chainObjects(y, x));
52 });
58 });
53 }
59 }
54
55 _loadPackage(localeData: LocaleProvider<Partial<T>>) {
56 return when(localeData(), data => data && "default" in data ? data.default : data);
57 }
58 }
60 }
@@ -1,7 +1,11
1 import { FunctionRendition } from "./FunctionRendition";
2 import { getItemDom } from "./render";
3
1 /** Special functional component used to create a document fragment */
4 /** Special functional component used to create a document fragment */
2 export function DjxFragment({ children }: { children?: Node | Node[] }) {
5 export const DjxFragment = ({ children }: { children?: unknown | unknown[] }) =>
3 const fragment = document.createDocumentFragment();
6 new FunctionRendition(() => {
4 if (children)
7 const fragment = document.createDocumentFragment();
5 (children instanceof Array ? children : [children]).forEach(child => fragment.appendChild(child));
8 if (children)
6 return fragment;
9 (children instanceof Array ? children : [children]).map(getItemDom).forEach(child => fragment.appendChild(child));
7 } No newline at end of file
10 return fragment;
11 }); No newline at end of file
@@ -1,131 +1,135
1 import { djbase, djclass } from "../declare";
1 import { djbase, djclass } from "../declare";
2 import _WidgetBase = require("dijit/_WidgetBase");
2 import _WidgetBase = require("dijit/_WidgetBase");
3 import _AttachMixin = require("dijit/_AttachMixin");
3 import _AttachMixin = require("dijit/_AttachMixin");
4 import { Rendition, isNode, isElementNode } from "./traits";
4 import { isNode, isElementNode } from "./traits";
5 import registry = require("dijit/registry");
5 import registry = require("dijit/registry");
6 import on = require("dojo/on");
6 import on = require("dojo/on");
7 import { Scope } from "./Scope";
7 import { Scope } from "./Scope";
8 import { render } from "./render";
8 import { render } from "./render";
9 import { isNull } from "@implab/core-amd/safe";
9
10
10 // type Handle = dojo.Handle;
11 // type Handle = dojo.Handle;
11
12
12 export interface EventArgs {
13 export interface EventArgs {
13 bubbles?: boolean;
14 bubbles?: boolean;
14
15
15 cancelable?: boolean;
16 cancelable?: boolean;
16
17
17 composed?: boolean;
18 composed?: boolean;
18 }
19 }
19
20
20 // eslint-disable-next-line @typescript-eslint/no-unused-vars
21 // eslint-disable-next-line @typescript-eslint/no-unused-vars
21 export interface DjxWidgetBase<Attrs = object, Events extends { [name in keyof Events]: Event } = object> extends
22 export interface DjxWidgetBase<Attrs = object, Events extends { [name in keyof Events]: Event } = object> extends
22 _WidgetBase<Events> {
23 _WidgetBase<Events> {
23
24
24 /** This property is declared only for type inference to work, it is never assigned
25 /** This property is declared only for type inference to work, it is never assigned
25 * and should not be used.
26 * and should not be used.
26 */
27 */
27 readonly _eventMap: Events & GlobalEventHandlersEventMap;
28 readonly _eventMap: Events & GlobalEventHandlersEventMap;
28
29
29 /** The list of pairs of event and method names. When the widget is created all methods from
30 /** The list of pairs of event and method names. When the widget is created all methods from
30 * this list will be connected to corresponding events.
31 * this list will be connected to corresponding events.
31 *
32 *
32 * This property is maintained in the prototype
33 * This property is maintained in the prototype
33 */
34 */
34 _eventHandlers: Array<{
35 _eventHandlers: Array<{
35 eventName: string,
36 eventName: string,
36 handlerMethod: string;
37 handlerMethod: string;
37 }>;
38 }>;
38 }
39 }
39
40
40 type _super = {
41 type _super = {
41 startup(): void;
42 startup(): void;
42
43
43 destroy(preserveDom?: boolean): void;
44 destroy(preserveDom?: boolean): void;
44 };
45 };
45
46
46 @djclass
47 @djclass
47 // eslint-disable-next-line @typescript-eslint/no-unused-vars
48 // eslint-disable-next-line @typescript-eslint/no-unused-vars
48 export abstract class DjxWidgetBase<Attrs = object, Events = object> extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) {
49 export abstract class DjxWidgetBase<Attrs = object, Events = object> extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) {
49 private readonly _scope = new Scope();
50 private readonly _scope = new Scope();
50
51
51 buildRendering() {
52 buildRendering() {
52 const node = render(this.render(), this._scope);
53 const node = render(this.render(), this._scope);
53 if (!isElementNode(node))
54 if (!isElementNode(node))
54 throw new Error("The render method must return a single DOM element");
55 throw new Error("The render method must return a single DOM element");
55 this.domNode = node as HTMLElement;
56 this.domNode = node as HTMLElement;
56
57
57 super.buildRendering();
58 super.buildRendering();
58
59
59 // now we should get assigned data-dojo-attach-points
60 // now we should get assigned data-dojo-attach-points
60 // place the contents of the original srcNode to the containerNode
61 // place the contents of the original srcNode to the containerNode
61 const src = this.srcNodeRef;
62 const src = this.srcNodeRef;
62 const dest = this.containerNode;
63 const dest = this.containerNode;
63
64
64 // the donNode is constructed now we need to connect event handlers
65 // the donNode is constructed now we need to connect event handlers
65 this._connectEventHandlers();
66 this._connectEventHandlers();
66
67
67 if (src && dest) {
68 if (src && dest) {
68 while (src.firstChild)
69 while (src.firstChild)
69 dest.appendChild(src.firstChild);
70 dest.appendChild(src.firstChild);
70 }
71 }
71 }
72 }
72
73
73 abstract render(): Rendition;
74 abstract render(): JSX.Element;
74
75
75 private _connectEventHandlers() {
76 private _connectEventHandlers() {
76 if (this._eventHandlers)
77 if (this._eventHandlers)
77 this._eventHandlers.forEach(({ eventName, handlerMethod }) => {
78 this._eventHandlers.forEach(({ eventName, handlerMethod }) => {
78 const handler = this[handlerMethod as keyof this];
79 const handler = this[handlerMethod as keyof this];
79 if (typeof handler === "function")
80 if (typeof handler === "function")
80 on(this.domNode, eventName, handler.bind(this) as (...args: unknown[]) => unknown);
81 on(this.domNode, eventName, handler.bind(this) as (...args: unknown[]) => unknown);
81 });
82 });
82 }
83 }
83
84
84 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
85 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
85 baseNode: T,
86 baseNode: T,
86 getAttrFunc: (baseNode: T, attr: string) => string,
87 getAttrFunc: (baseNode: T, attr: string) => string | undefined,
87 // tslint:disable-next-line: ban-types
88 // tslint:disable-next-line: ban-types
88 attachFunc: (node: T, type: string, func?: (...args: unknown[]) => unknown) => dojo.Handle
89 attachFunc: (node: T, type: string, func?: (...args: unknown[]) => unknown) => dojo.Handle
89 ): boolean {
90 ): boolean {
90 if (isNode(baseNode)) {
91 if (isNode(baseNode)) {
91 const w = registry.byNode(baseNode);
92 const w = registry.byNode(baseNode);
92 if (w) {
93 if (w) {
93 // from dijit/_WidgetsInTemplateMixin
94 // from dijit/_WidgetsInTemplateMixin
94 this._processTemplateNode(w,
95 this._processTemplateNode(w,
95 (n, p) => String(n.get(p as keyof typeof n)), // callback to get a property of a widget
96 (n, p) => {
97 const v = n.get(p as keyof typeof n);
98 return isNull(v) ? undefined : String(v);
99 }, // callback to get a property of a widget
96 (widget, type, callback) => {
100 (widget, type, callback) => {
97 if (!callback)
101 if (!callback)
98 throw new Error("The callback must be specified");
102 throw new Error("The callback must be specified");
99
103
100 // callback to do data-dojo-attach-event to a widget
104 // callback to do data-dojo-attach-event to a widget
101 if (type in widget) {
105 if (type in widget) {
102 // back-compat, remove for 2.0
106 // back-compat, remove for 2.0
103 return widget.connect(widget, type, callback as EventListener);
107 return widget.connect(widget, type, callback as EventListener);
104 } else {
108 } else {
105 // 1.x may never hit this branch, but it's the default for 2.0
109 // 1.x may never hit this branch, but it's the default for 2.0
106 return widget.on(type as keyof GlobalEventHandlersEventMap, callback);
110 return widget.on(type as keyof GlobalEventHandlersEventMap, callback);
107 }
111 }
108
112
109 });
113 });
110 // don't process widgets internals
114 // don't process widgets internals
111 return false;
115 return false;
112 }
116 }
113 }
117 }
114 // eslint-disable-next-line @typescript-eslint/ban-types
118 // eslint-disable-next-line @typescript-eslint/ban-types
115 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc as (node: T, type: string, func?: Function) => dojo.Handle);
119 return super._processTemplateNode(baseNode, getAttrFunc as (baseNode: T, attr: string) => string, attachFunc as (node: T, type: string, func?: Function) => dojo.Handle);
116 }
120 }
117
121
118 /** Starts current widget and all its supporting widgets (placed outside
122 /** Starts current widget and all its supporting widgets (placed outside
119 * `containerNode`) and child widgets (placed inside `containerNode`)
123 * `containerNode`) and child widgets (placed inside `containerNode`)
120 */
124 */
121 startup() {
125 startup() {
122 // startup supporting widgets
126 // startup supporting widgets
123 registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup());
127 registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup());
124 super.startup();
128 super.startup();
125 }
129 }
126
130
127 destroy(preserveDom?: boolean) {
131 destroy(preserveDom?: boolean) {
128 this._scope.destroy();
132 this._scope.destroy();
129 super.destroy(preserveDom);
133 super.destroy(preserveDom);
130 }
134 }
131 }
135 }
@@ -1,70 +1,70
1 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, isMounted, startupWidgets } from "./traits";
1 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, isMounted, startupWidgets } from "./traits";
2
2
3 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
3 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
4 private _attrs = {};
4 private _attrs = {};
5
5
6 private _children: unknown[] = [];
6 private _children: unknown[] = [];
7
7
8 private _created = false;
8 private _created = false;
9
9
10 visitNext(v: unknown) {
10 visitNext(v: unknown) {
11 if (this._created)
11 if (this._created)
12 throw new Error("The Element is already created");
12 throw new Error("The Element is already created");
13
13
14 if (v === null || v === undefined || typeof v === "boolean")
14 if (v === null || v === undefined || typeof v === "boolean")
15 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
15 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
16 return;
16 return;
17
17
18 if (isPlainObject(v)) {
18 if (isPlainObject(v)) {
19 this._attrs = {... this._attrs, ...v};
19 this._attrs = {... this._attrs, ...v};
20 } else if (v instanceof Array) {
20 } else if (v instanceof Array) {
21 v.forEach(x => this.visitNext(x));
21 v.forEach(x => this.visitNext(x));
22 } else {
22 } else {
23 this._children.push(v);
23 this._children.push(v);
24 }
24 }
25 }
25 }
26
26
27 ensureCreated() {
27 ensureCreated() {
28 if (!this._created) {
28 if (!this._created) {
29 this._create(this._attrs, this._children);
29 this._create(this._attrs, this._children);
30 this._children = [];
30 this._children = [];
31 this._attrs = {};
31 this._attrs = {};
32 this._created = true;
32 this._created = true;
33 }
33 }
34 }
34 }
35
35
36 /** Is rendition was instantiated to the DOM node */
36 /** Is rendition was instantiated to the DOM node */
37 isCreated() {
37 isCreated() {
38 return this._created;
38 return this._created;
39 }
39 }
40
40
41 /** Creates DOM node if not created. No additional actions are taken. */
41 /** Creates DOM node if not created. No additional actions are taken. */
42 getDomNode() {
42 getDomNode() {
43 this.ensureCreated();
43 this.ensureCreated();
44 return this._getDomNode();
44 return this._getDomNode();
45 }
45 }
46
46
47 /** Creates DOM node if not created, places it to the specified position
47 /** Creates DOM node if not created, places it to the specified position
48 * and calls startup() method for all widgets contained by this node.
48 * and calls startup() method for all widgets contained by this node.
49 *
49 *
50 * @param {string | Node} refNode The reference node where the created
50 * @param {string | Node} refNode The reference node where the created
51 * DOM should be placed.
51 * DOM should be placed.
52 * @param {DojoNodePosition} position Optional parameter, specifies the
52 * @param {DojoNodePosition} position Optional parameter, specifies the
53 * position relative to refNode. Default is "last" (i.e. last child).
53 * position relative to refNode. Default is "last" (i.e. last child).
54 */
54 */
55 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
55 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
56 const domNode = this.getDomNode();
56 const domNode = this.getDomNode();
57
57
58 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.childNodes) : [domNode];
58 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.childNodes) : [domNode];
59
59
60 placeAt(domNode, refNode, position);
60 placeAt(domNode, refNode, position);
61
61
62 if (isMounted(startupPending[0]))
62 if (startupPending.length && isMounted(startupPending[0]))
63 startupPending.forEach(n => startupWidgets(n));
63 startupPending.forEach(n => startupWidgets(n));
64
64
65 }
65 }
66
66
67 protected abstract _create(attrs: object, children: unknown[]): void;
67 protected abstract _create(attrs: object, children: unknown[]): void;
68
68
69 protected abstract _getDomNode(): TNode;
69 protected abstract _getDomNode(): TNode;
70 }
70 }
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now