vsn 0.1.60 → 0.1.63
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/demo/demo.html +14 -13
- package/demo/vsn.js +2 -2
- package/demo/xhr.html +46 -3
- package/dist/AST/ClassNode.d.ts +22 -0
- package/dist/AST/ClassNode.js +248 -0
- package/dist/AST/ClassNode.js.map +1 -0
- package/dist/AST/FunctionCallNode.js +48 -10
- package/dist/AST/FunctionCallNode.js.map +1 -1
- package/dist/AST/FunctionNode.d.ts +2 -1
- package/dist/AST/FunctionNode.js +16 -4
- package/dist/AST/FunctionNode.js.map +1 -1
- package/dist/AST/OnNode.d.ts +9 -0
- package/dist/{attributes/ClassConstructor.js → AST/OnNode.js} +25 -40
- package/dist/AST/OnNode.js.map +1 -0
- package/dist/AST.d.ts +48 -46
- package/dist/AST.js +68 -46
- package/dist/AST.js.map +1 -1
- package/dist/DOM.js +55 -36
- package/dist/DOM.js.map +1 -1
- package/dist/Registry.d.ts +4 -3
- package/dist/Registry.js +8 -11
- package/dist/Registry.js.map +1 -1
- package/dist/Scope.d.ts +3 -0
- package/dist/Scope.js +15 -1
- package/dist/Scope.js.map +1 -1
- package/dist/Tag.d.ts +2 -0
- package/dist/Tag.js +12 -0
- package/dist/Tag.js.map +1 -1
- package/dist/attributes/ControllerAttribute.d.ts +9 -2
- package/dist/attributes/ControllerAttribute.js +78 -2
- package/dist/attributes/ControllerAttribute.js.map +1 -1
- package/dist/attributes/ListItem.js +1 -1
- package/dist/attributes/ListItem.js.map +1 -1
- package/dist/attributes/ModelAttribute.d.ts +2 -2
- package/dist/attributes/ModelAttribute.js +2 -2
- package/dist/attributes/ModelAttribute.js.map +1 -1
- package/dist/attributes/ScopeAttribute.js +6 -2
- package/dist/attributes/ScopeAttribute.js.map +1 -1
- package/dist/attributes/SetAttribute.js +9 -6
- package/dist/attributes/SetAttribute.js.map +1 -1
- package/dist/vsn.js +3 -3
- package/dist/vsn.js.map +1 -1
- package/package.json +1 -1
- package/src/AST/ClassNode.ts +117 -0
- package/src/AST/FunctionCallNode.ts +24 -1
- package/src/AST/FunctionNode.ts +9 -5
- package/src/AST/OnNode.ts +18 -0
- package/src/AST.ts +20 -0
- package/src/DOM.ts +13 -3
- package/src/Registry.ts +11 -9
- package/src/Scope.ts +9 -0
- package/src/Tag.ts +11 -0
- package/src/attributes/ControllerAttribute.ts +30 -2
- package/src/attributes/ListItem.ts +1 -1
- package/src/attributes/ModelAttribute.ts +2 -2
- package/src/attributes/ScopeAttribute.ts +3 -0
- package/src/attributes/SetAttribute.ts +6 -4
- package/src/vsn.ts +3 -3
- package/test/DOM.spec.ts +3 -3
- package/test/attributes/ListItem.spec.ts +3 -3
- package/test/attributes/SetAttribute.spec.ts +3 -3
- package/dist/attributes/ClassConstructor.d.ts +0 -10
- package/dist/attributes/ClassConstructor.js.map +0 -1
- package/dist/vsn.min.js +0 -2
- package/src/attributes/ClassConstructor.ts +0 -32
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {Scope} from "../Scope";
|
|
2
|
+
import {DOM} from "../DOM";
|
|
3
|
+
import {Tag} from "../Tag";
|
|
4
|
+
import {Token, TokenType, Tree, TreeNode} from "../AST";
|
|
5
|
+
import {Node} from "./Node";
|
|
6
|
+
import {BlockNode} from "./BlockNode";
|
|
7
|
+
import {Registry} from "../Registry";
|
|
8
|
+
import {OnNode} from "./OnNode";
|
|
9
|
+
|
|
10
|
+
export class ClassNode extends Node implements TreeNode {
|
|
11
|
+
public static readonly classes: {[name: string]: ClassNode} = {};
|
|
12
|
+
protected requiresPrep: boolean = true;
|
|
13
|
+
public readonly classScope: Scope = new Scope();
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
public readonly name: string,
|
|
17
|
+
public readonly block: BlockNode
|
|
18
|
+
) {
|
|
19
|
+
super();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async prepare(scope: Scope, dom: DOM, tag: Tag = null): Promise<void> {
|
|
23
|
+
if (ClassNode.classes[this.name]) return; // Don't re-prepare same classes
|
|
24
|
+
ClassNode.classes[this.name] = this;
|
|
25
|
+
await this.block.prepare(this.classScope, dom, tag);
|
|
26
|
+
Registry.class(this);
|
|
27
|
+
const hasConstructor = this.classScope.has('construct');
|
|
28
|
+
|
|
29
|
+
for (const element of Array.from(dom.querySelectorAll(`.${this.name}`))) {
|
|
30
|
+
const tag: Tag = await dom.getTagForElement(element as HTMLElement, true);
|
|
31
|
+
if (tag) {
|
|
32
|
+
await this.prepareTag(tag, dom, hasConstructor);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async prepareTag(tag: Tag, dom: DOM, hasConstructor: boolean | null = null) {
|
|
38
|
+
if (hasConstructor === null)
|
|
39
|
+
hasConstructor = this.classScope.has('construct');
|
|
40
|
+
|
|
41
|
+
tag.createScope(true);
|
|
42
|
+
await this.block.prepare(tag.scope, dom, tag);
|
|
43
|
+
if (hasConstructor) {
|
|
44
|
+
const fnc = this.classScope.get('construct');
|
|
45
|
+
(await fnc.evaluate(tag.scope, dom, tag))();
|
|
46
|
+
}
|
|
47
|
+
/*
|
|
48
|
+
for (const key of this.classScope.keys) {
|
|
49
|
+
if (this.classScope.get(key) instanceof OnNode) {
|
|
50
|
+
const on = this.classScope.get(key) as OnNode;
|
|
51
|
+
tag.addEventHandler(on.name, [], await on.getFunction(tag.scope, dom, tag), on);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
*/
|
|
55
|
+
tag.preppedClasses.push(this.name);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async tearDownTag(tag: Tag, dom: DOM, hasDeconstructor: boolean | null = null) {
|
|
59
|
+
if (hasDeconstructor === null)
|
|
60
|
+
hasDeconstructor = this.classScope.has('deconstruct');
|
|
61
|
+
|
|
62
|
+
if (hasDeconstructor) {
|
|
63
|
+
const fnc = this.classScope.get('deconstruct');
|
|
64
|
+
(await fnc.evaluate(tag.scope, dom, tag))();
|
|
65
|
+
}
|
|
66
|
+
for (const key of this.classScope.keys) {
|
|
67
|
+
if (this.classScope.get(key) instanceof OnNode) {
|
|
68
|
+
const on = this.classScope.get(key) as OnNode;
|
|
69
|
+
tag.removeContextEventHandlers(on);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
tag.preppedClasses.splice(tag.preppedClasses.indexOf(this.name), 1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public static parse(lastNode, token, tokens: Token[]): ClassNode {
|
|
80
|
+
tokens.shift(); // skip 'class'
|
|
81
|
+
const nameParts: string[] = [];
|
|
82
|
+
for (const t of tokens) {
|
|
83
|
+
if (t.type === TokenType.L_BRACE) break;
|
|
84
|
+
nameParts.push(t.value);
|
|
85
|
+
}
|
|
86
|
+
const name = nameParts.join('');
|
|
87
|
+
tokens.splice(0, nameParts.length);
|
|
88
|
+
const block = Tree.processTokens(Tree.getNextStatementTokens(tokens, true, true));
|
|
89
|
+
return new ClassNode(name, block);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public static async checkForClassChanges(element: HTMLElement, dom: DOM, tag: Tag = null) {
|
|
93
|
+
const classes: string[] = Array.from(element.classList);
|
|
94
|
+
let addedClasses: string[] = classes.filter(c => Registry.instance.classes.has(c));
|
|
95
|
+
let removedClasses: string[] = [];
|
|
96
|
+
if (tag) {
|
|
97
|
+
addedClasses = addedClasses.filter(c => !tag.preppedClasses.includes(c));
|
|
98
|
+
removedClasses = tag.preppedClasses.filter(c => !classes.includes(c));
|
|
99
|
+
} else {
|
|
100
|
+
tag = await dom.getTagForElement(element, true);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const addedClass of addedClasses) {
|
|
104
|
+
const classNode: ClassNode = Registry.instance.classes.getSynchronous(addedClass);
|
|
105
|
+
if (classNode) {
|
|
106
|
+
await classNode.prepareTag(tag, dom);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const removedClass of removedClasses) {
|
|
111
|
+
const classNode: ClassNode = Registry.instance.classes.getSynchronous(removedClass);
|
|
112
|
+
if (classNode) {
|
|
113
|
+
await classNode.tearDownTag(tag, dom);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -6,6 +6,7 @@ import {Node} from "./Node";
|
|
|
6
6
|
import {FunctionArgumentNode} from "./FunctionArgumentNode";
|
|
7
7
|
import {ScopeMemberNode} from "./ScopeMemberNode";
|
|
8
8
|
import {FunctionNode} from "./FunctionNode";
|
|
9
|
+
import {Registry} from "../Registry";
|
|
9
10
|
|
|
10
11
|
export class FunctionCallNode<T = any> extends Node implements TreeNode {
|
|
11
12
|
constructor(
|
|
@@ -27,9 +28,31 @@ export class FunctionCallNode<T = any> extends Node implements TreeNode {
|
|
|
27
28
|
if (this.fnc instanceof ScopeMemberNode) {
|
|
28
29
|
functionScope = await this.fnc.scope.evaluate(scope, dom, tag);
|
|
29
30
|
}
|
|
31
|
+
|
|
30
32
|
const values = await this.args.evaluate(scope, dom, tag);
|
|
31
33
|
const func = await this.fnc.evaluate(scope, dom, tag);
|
|
32
|
-
if (func
|
|
34
|
+
if (!func) {
|
|
35
|
+
const functionName = await (this.fnc as any).name.evaluate(scope, dom, tag);
|
|
36
|
+
const returnValues = [];
|
|
37
|
+
let calls = 0;
|
|
38
|
+
for (const className of tag.preppedClasses) {
|
|
39
|
+
const cls = Registry.instance.classes.getSynchronous(className);
|
|
40
|
+
if (cls) {
|
|
41
|
+
if (cls.classScope.has(functionName)) {
|
|
42
|
+
const fnc = cls.classScope.get(functionName);
|
|
43
|
+
returnValues.push((await fnc.evaluate(functionScope, dom, tag))(...values));
|
|
44
|
+
calls++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (calls === 1) {
|
|
49
|
+
return returnValues[0];
|
|
50
|
+
} else if (calls === 0) {
|
|
51
|
+
throw new Error(`Function ${functionName} not found`);
|
|
52
|
+
} else {
|
|
53
|
+
return returnValues;
|
|
54
|
+
}
|
|
55
|
+
} else if (func instanceof FunctionNode) {
|
|
33
56
|
return (await func.evaluate(functionScope, dom, tag) as any)(...values);
|
|
34
57
|
} else {
|
|
35
58
|
return func.call(functionScope.wrapped || functionScope, ...values);
|
package/src/AST/FunctionNode.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Scope} from "../Scope";
|
|
1
|
+
import {FunctionScope, Scope} from "../Scope";
|
|
2
2
|
import {DOM} from "../DOM";
|
|
3
3
|
import {Tag} from "../Tag";
|
|
4
4
|
import {Token, Tree, TreeNode} from "../AST";
|
|
@@ -28,8 +28,12 @@ export class FunctionNode extends Node implements TreeNode {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
public async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
|
|
31
|
+
return await this.getFunction(scope, dom, tag);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async getFunction(scope: Scope, dom: DOM, tag: Tag = null) {
|
|
31
35
|
return async (...args) => {
|
|
32
|
-
const functionScope = new
|
|
36
|
+
const functionScope = new FunctionScope(scope);
|
|
33
37
|
for (const arg of this.args) {
|
|
34
38
|
functionScope.set(arg, args.shift());
|
|
35
39
|
}
|
|
@@ -37,7 +41,7 @@ export class FunctionNode extends Node implements TreeNode {
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
public static parse(lastNode, token, tokens: Token[]): FunctionNode {
|
|
44
|
+
public static parse<T = FunctionNode>(lastNode, token, tokens: Token[], cls: any = FunctionNode): FunctionNode {
|
|
41
45
|
tokens.shift(); // skip 'func'
|
|
42
46
|
const name = tokens.shift();
|
|
43
47
|
const argTokens = Tree.getBlockTokens(tokens);
|
|
@@ -45,7 +49,7 @@ export class FunctionNode extends Node implements TreeNode {
|
|
|
45
49
|
for (const t of argTokens) {
|
|
46
50
|
funcArgs.push(t[0].value);
|
|
47
51
|
}
|
|
48
|
-
const block = Tree.processTokens(Tree.
|
|
49
|
-
return new
|
|
52
|
+
const block = Tree.processTokens(Tree.getNextStatementTokens(tokens, true, true));
|
|
53
|
+
return new cls(name.value, funcArgs, block);
|
|
50
54
|
}
|
|
51
55
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {Token, TreeNode} from "../AST";
|
|
2
|
+
import {FunctionNode} from "./FunctionNode";
|
|
3
|
+
import {Scope} from "../Scope";
|
|
4
|
+
import {DOM} from "../DOM";
|
|
5
|
+
import {Tag} from "../Tag";
|
|
6
|
+
|
|
7
|
+
export class OnNode extends FunctionNode implements TreeNode {
|
|
8
|
+
public async prepare(scope: Scope, dom: DOM, tag: Tag = null): Promise<void> {
|
|
9
|
+
if (tag) {
|
|
10
|
+
tag.addEventHandler(this.name, [], await this.getFunction(scope, dom, tag), this);
|
|
11
|
+
}
|
|
12
|
+
await super.prepare(scope, dom, tag);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public static parse(lastNode, token, tokens: Token[]): OnNode {
|
|
16
|
+
return super.parse<OnNode>(lastNode, token, tokens, OnNode);
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/AST.ts
CHANGED
|
@@ -27,6 +27,8 @@ import {NotNode} from "./AST/NotNode";
|
|
|
27
27
|
import {XHRNode} from "./AST/XHRNode";
|
|
28
28
|
import {StringFormatNode} from "./AST/StringFormatNode";
|
|
29
29
|
import {FunctionNode} from "./AST/FunctionNode";
|
|
30
|
+
import {ClassNode} from "./AST/ClassNode";
|
|
31
|
+
import {OnNode} from "./AST/OnNode";
|
|
30
32
|
|
|
31
33
|
function lower(str: string): string {
|
|
32
34
|
return str ? str.toLowerCase() : null;
|
|
@@ -67,6 +69,8 @@ export enum TokenType {
|
|
|
67
69
|
ELSE_IF,
|
|
68
70
|
ELSE,
|
|
69
71
|
FUNC,
|
|
72
|
+
ON,
|
|
73
|
+
CLASS,
|
|
70
74
|
NAME,
|
|
71
75
|
L_BRACE,
|
|
72
76
|
R_BRACE,
|
|
@@ -200,6 +204,14 @@ const TOKEN_PATTERNS: TokenPattern[] = [
|
|
|
200
204
|
type: TokenType.FUNC,
|
|
201
205
|
pattern: /^func\s/
|
|
202
206
|
},
|
|
207
|
+
{
|
|
208
|
+
type: TokenType.ON,
|
|
209
|
+
pattern: /^on\s/
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: TokenType.CLASS,
|
|
213
|
+
pattern: /^class\s/
|
|
214
|
+
},
|
|
203
215
|
{
|
|
204
216
|
type: TokenType.ELEMENT_ATTRIBUTE,
|
|
205
217
|
pattern: /^\.?@[-_a-zA-Z0-9]*/
|
|
@@ -492,6 +504,14 @@ export class Tree {
|
|
|
492
504
|
node = FunctionNode.parse(node, token, tokens);
|
|
493
505
|
blockNodes.push(node);
|
|
494
506
|
node = null;
|
|
507
|
+
} else if (token.type === TokenType.ON) {
|
|
508
|
+
node = OnNode.parse(node, token, tokens);
|
|
509
|
+
blockNodes.push(node);
|
|
510
|
+
node = null;
|
|
511
|
+
} else if (token.type === TokenType.CLASS) {
|
|
512
|
+
node = ClassNode.parse(node, token, tokens);
|
|
513
|
+
blockNodes.push(node);
|
|
514
|
+
node = null;
|
|
495
515
|
} else if (StringFormatNode.match(tokens)) {
|
|
496
516
|
node = StringFormatNode.parse(node, tokens[0], tokens);
|
|
497
517
|
} else if (token.type === TokenType.STRING_LITERAL) {
|
package/src/DOM.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {WrappedWindow} from "./DOM/WrappedWindow";
|
|
|
7
7
|
import {WrappedDocument} from "./DOM/WrappedDocument";
|
|
8
8
|
import {Scope} from "./Scope";
|
|
9
9
|
import {EventDispatcher} from "./EventDispatcher";
|
|
10
|
+
import {Registry} from "./Registry";
|
|
11
|
+
import {ClassNode} from "./AST/ClassNode";
|
|
10
12
|
|
|
11
13
|
export class DOM extends EventDispatcher {
|
|
12
14
|
protected static _instance: DOM;
|
|
@@ -115,11 +117,16 @@ export class DOM extends EventDispatcher {
|
|
|
115
117
|
|
|
116
118
|
public async mutation(mutations: MutationRecord[]) {
|
|
117
119
|
for (const mutation of mutations) {
|
|
118
|
-
|
|
120
|
+
let tag: Tag = await this.getTagForElement(mutation.target as HTMLElement);
|
|
119
121
|
if (tag) {
|
|
120
122
|
tag.mutate(mutation);
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
// Check for class changes
|
|
126
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
|
127
|
+
await ClassNode.checkForClassChanges(mutation.target as HTMLElement, this, tag);
|
|
128
|
+
}
|
|
129
|
+
|
|
123
130
|
for (const ele of Array.from(mutation.removedNodes)) {
|
|
124
131
|
const toRemove: Tag = await this.getTagForElement(ele as HTMLElement);
|
|
125
132
|
if (toRemove) {
|
|
@@ -191,12 +198,15 @@ export class DOM extends EventDispatcher {
|
|
|
191
198
|
this.queued.splice(this.queued.indexOf(tag.element), 1);
|
|
192
199
|
}
|
|
193
200
|
|
|
194
|
-
|
|
201
|
+
for (const tag of newTags) {
|
|
202
|
+
this.observer.observe(tag.element, {
|
|
195
203
|
attributes: true,
|
|
196
204
|
characterData: true,
|
|
197
205
|
childList: true,
|
|
198
206
|
subtree: true
|
|
199
|
-
})
|
|
207
|
+
});
|
|
208
|
+
await ClassNode.checkForClassChanges(tag.element, this, tag);
|
|
209
|
+
}
|
|
200
210
|
|
|
201
211
|
this.dispatch('built');
|
|
202
212
|
}
|
package/src/Registry.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {EventDispatcher} from "./EventDispatcher";
|
|
2
2
|
import {IDeferred, IPromise, SimplePromise} from "./SimplePromise";
|
|
3
|
+
import {ClassNode} from "./AST/ClassNode";
|
|
3
4
|
|
|
4
5
|
export function register(store: string, key: string = null, setup: () => void = null) {
|
|
5
6
|
return function(target: any, _key: string = null) {
|
|
@@ -23,6 +24,7 @@ export class RegistryStore extends EventDispatcher {
|
|
|
23
24
|
|
|
24
25
|
register(key: string, item: any) {
|
|
25
26
|
this.store[key] = item;
|
|
27
|
+
this.dispatch('register', key, item);
|
|
26
28
|
this.dispatch(`registered:${key}`, item);
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -48,11 +50,15 @@ export class RegistryStore extends EventDispatcher {
|
|
|
48
50
|
getSynchronous(key: string) {
|
|
49
51
|
return this.store[key];
|
|
50
52
|
}
|
|
53
|
+
|
|
54
|
+
has(key: string) {
|
|
55
|
+
return !!this.store[key];
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
export class Registry extends EventDispatcher {
|
|
54
60
|
protected static _instance: Registry;
|
|
55
|
-
public readonly
|
|
61
|
+
public readonly controllers: RegistryStore;
|
|
56
62
|
public readonly classes: RegistryStore;
|
|
57
63
|
public readonly models: RegistryStore;
|
|
58
64
|
public readonly templates: RegistryStore;
|
|
@@ -63,7 +69,7 @@ export class Registry extends EventDispatcher {
|
|
|
63
69
|
|
|
64
70
|
constructor() {
|
|
65
71
|
super();
|
|
66
|
-
this.
|
|
72
|
+
this.controllers = new RegistryStore();
|
|
67
73
|
this.classes = new RegistryStore();
|
|
68
74
|
this.models = new RegistryStore();
|
|
69
75
|
this.templates = new RegistryStore();
|
|
@@ -73,16 +79,12 @@ export class Registry extends EventDispatcher {
|
|
|
73
79
|
this.attributes = new RegistryStore();
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
public static
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
public static class(key: string = null, setup = null) {
|
|
81
|
-
return register('classes', key, setup);
|
|
82
|
+
public static class(cls: ClassNode) {
|
|
83
|
+
Registry.instance.classes.register(cls.name, cls);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
public static controller(key: string = null, setup = null) {
|
|
85
|
-
return register('
|
|
87
|
+
return register('controllers', key, setup);
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
public static model(key: string = null, setup = null) {
|
package/src/Scope.ts
CHANGED
|
@@ -227,3 +227,12 @@ export class Scope extends EventDispatcher {
|
|
|
227
227
|
return scope;
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
export class FunctionScope extends Scope {
|
|
232
|
+
set(key: string, value: any) {
|
|
233
|
+
if (this.parentScope.has(key) || ['$', '@'].indexOf(key[0]) > -1)
|
|
234
|
+
this.parentScope.set(key, value);
|
|
235
|
+
else
|
|
236
|
+
super.set(key, value);
|
|
237
|
+
}
|
|
238
|
+
}
|
package/src/Tag.ts
CHANGED
|
@@ -27,6 +27,7 @@ export class Tag extends DOMObject {
|
|
|
27
27
|
public readonly rawAttributes: { [key: string]: string; };
|
|
28
28
|
public readonly parsedAttributes: { [key: string]: string[]; };
|
|
29
29
|
public readonly deferredAttributes: Attribute[] = [];
|
|
30
|
+
public readonly preppedClasses: string[] = [];
|
|
30
31
|
protected _state: TagState;
|
|
31
32
|
protected attributes: Attribute[];
|
|
32
33
|
protected _nonDeferredAttributes: Attribute[] = [];
|
|
@@ -598,6 +599,16 @@ export class Tag extends DOMObject {
|
|
|
598
599
|
}
|
|
599
600
|
}
|
|
600
601
|
|
|
602
|
+
public removeContextEventHandlers(context: any) {
|
|
603
|
+
for (const eventType of Object.keys(this.onEventHandlers)) {
|
|
604
|
+
for (const handler of this.onEventHandlers[eventType]) {
|
|
605
|
+
if (handler.context === context) {
|
|
606
|
+
this.removeEventHandler(eventType, handler.handler, context);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
601
612
|
public createScope(force: boolean = false): Scope {
|
|
602
613
|
// Standard attribute requires a unique scope
|
|
603
614
|
// @todo: Does this cause any issues with attribute bindings on the parent scope prior to having its own scope? hmm...
|
|
@@ -1,4 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Scope} from "../Scope";
|
|
2
|
+
import {Attribute} from "../Attribute";
|
|
3
|
+
import {Registry} from "../Registry";
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
@Registry.attribute('vsn-controller')
|
|
6
|
+
export class ControllerAttribute extends Attribute {
|
|
7
|
+
public static readonly canDefer: boolean = false;
|
|
8
|
+
public static readonly scoped: boolean = true;
|
|
9
|
+
protected attributeKey: string;
|
|
10
|
+
protected className: string;
|
|
11
|
+
protected defaultClassName: string;
|
|
12
|
+
|
|
13
|
+
public async setup() {
|
|
14
|
+
const parentScope: Scope = this.tag.parentTag.scope;
|
|
15
|
+
if (!parentScope)
|
|
16
|
+
return;
|
|
17
|
+
|
|
18
|
+
this.attributeKey = this.getAttributeBinding();
|
|
19
|
+
this.className = this.getAttributeValue(this.defaultClassName);
|
|
20
|
+
|
|
21
|
+
const cls = await Registry.instance.controllers.get(this.className);
|
|
22
|
+
this.instantiateClass(cls);
|
|
23
|
+
|
|
24
|
+
if (this.attributeKey && parentScope)
|
|
25
|
+
parentScope.set(this.attributeKey, this.tag.scope);
|
|
26
|
+
await super.setup();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected instantiateClass(cls) {
|
|
30
|
+
this.tag.wrap(cls);
|
|
31
|
+
}
|
|
4
32
|
}
|
|
@@ -21,7 +21,7 @@ export class ListItem extends Attribute {
|
|
|
21
21
|
|
|
22
22
|
this.tag.scope.set(this.listItemName, this.tag.scope);
|
|
23
23
|
const modelName: string = (await this.getList()).listItemModel;
|
|
24
|
-
const cls = await Registry.instance.
|
|
24
|
+
const cls = await Registry.instance.controllers.get(modelName);
|
|
25
25
|
this.instantiateModel(cls);
|
|
26
26
|
await super.setup();
|
|
27
27
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {ControllerAttribute} from "./ControllerAttribute";
|
|
2
2
|
|
|
3
|
-
export class ModelAttribute extends
|
|
3
|
+
export class ModelAttribute extends ControllerAttribute {
|
|
4
4
|
public static readonly canDefer: boolean = false;
|
|
5
5
|
}
|
|
@@ -28,6 +28,9 @@ export class ScopeAttribute extends Attribute {
|
|
|
28
28
|
this.tag.scope.set(key, value.data[key]);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
const binding = this.getAttributeBinding();
|
|
32
|
+
if (binding)
|
|
33
|
+
this.tag.scope.parentScope.set(binding, this.tag.scope);
|
|
31
34
|
await super.extract();
|
|
32
35
|
}
|
|
33
36
|
}
|
|
@@ -36,10 +36,12 @@ export class SetAttribute extends Attribute {
|
|
|
36
36
|
|
|
37
37
|
public async extract() {
|
|
38
38
|
let value = this.getAttributeValue(null);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
for (const m of this.getAttributeModifiers()) {
|
|
40
|
+
const t = Registry.instance.types.getSynchronous(m);
|
|
41
|
+
if (t) {
|
|
42
|
+
this.boundScope.setType(this.key, m);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
43
45
|
}
|
|
44
46
|
this.boundScope.set(this.key, value);
|
|
45
47
|
}
|
package/src/vsn.ts
CHANGED
|
@@ -25,9 +25,9 @@ export class Vision extends EventDispatcher {
|
|
|
25
25
|
} else {
|
|
26
26
|
console.warn('No dom, running in CLI mode.');
|
|
27
27
|
}
|
|
28
|
-
this.registry.
|
|
29
|
-
this.registry.
|
|
30
|
-
this.registry.
|
|
28
|
+
this.registry.controllers.register('Object', Object);
|
|
29
|
+
this.registry.controllers.register('WrappedArray', WrappedArray);
|
|
30
|
+
this.registry.controllers.register('Data', DynamicScopeData);
|
|
31
31
|
|
|
32
32
|
if (VisionHelper.window) {
|
|
33
33
|
window['Vision'] = Vision;
|
package/test/DOM.spec.ts
CHANGED
|
@@ -2,7 +2,7 @@ import "../src/vsn";
|
|
|
2
2
|
import {DOM} from "../src/DOM";
|
|
3
3
|
import {Registry} from "../src/Registry";
|
|
4
4
|
|
|
5
|
-
@Registry.
|
|
5
|
+
@Registry.controller('TestController')
|
|
6
6
|
class TestController {}
|
|
7
7
|
|
|
8
8
|
describe('DOM', () => {
|
|
@@ -20,9 +20,9 @@ describe('DOM', () => {
|
|
|
20
20
|
|
|
21
21
|
it("should use scopes correctly", (done) => {
|
|
22
22
|
document.body.innerHTML = `
|
|
23
|
-
<div id="parent" vsn-set:asd="123
|
|
23
|
+
<div id="parent" vsn-set:asd|integer="123">
|
|
24
24
|
<div id="testing" vsn-controller:test="TestController" vsn-set:asd="234|integer">
|
|
25
|
-
<div vsn-set:asd="345
|
|
25
|
+
<div vsn-set:asd|integer="345"></div>
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
`;
|
|
@@ -5,14 +5,14 @@ import {ListItem} from "../../src/attributes/ListItem";
|
|
|
5
5
|
import {Registry} from "../../src/Registry";
|
|
6
6
|
import {List} from "../../src/attributes/List";
|
|
7
7
|
|
|
8
|
-
@Registry.
|
|
8
|
+
@Registry.controller('ListItemController')
|
|
9
9
|
class ListItemController{
|
|
10
10
|
items: ListItemSpecTestItem[] = [];
|
|
11
11
|
|
|
12
12
|
do() {}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
@Registry.
|
|
15
|
+
@Registry.controller('ListItemSpecTestItem')
|
|
16
16
|
class ListItemSpecTestItem {
|
|
17
17
|
test: number = null;
|
|
18
18
|
|
|
@@ -96,7 +96,7 @@ describe('ListItem', () => {
|
|
|
96
96
|
|
|
97
97
|
it("vsn-list-item should work with vsn-set", (done) => {
|
|
98
98
|
document.body.innerHTML = `
|
|
99
|
-
<ul vsn-list:list id="test"><li vsn-list-item:item="ListItemSpecTestItem" id="test-item" vsn-set:item.testing="1
|
|
99
|
+
<ul vsn-list:list id="test"><li vsn-list-item:item="ListItemSpecTestItem" id="test-item" vsn-set:item.testing|integer="1"></li></ul>
|
|
100
100
|
`;
|
|
101
101
|
|
|
102
102
|
const dom = new DOM(document);
|
|
@@ -37,9 +37,9 @@ describe('Bind', () => {
|
|
|
37
37
|
|
|
38
38
|
it("vsn-set to work with a typed value", (done) => {
|
|
39
39
|
document.body.innerHTML = `
|
|
40
|
-
<span id="test-int" vsn-set:int="142.3
|
|
41
|
-
<span id="test-float" vsn-set:float="142.3
|
|
42
|
-
<span id="test-bool" vsn-set:bool="false
|
|
40
|
+
<span id="test-int" vsn-set:int|integer="142.3">testing</span>
|
|
41
|
+
<span id="test-float" vsn-set:float|float="142.3">testing</span>
|
|
42
|
+
<span id="test-bool" vsn-set:bool|boolean="false">testing</span>
|
|
43
43
|
`;
|
|
44
44
|
const dom = new DOM(document);
|
|
45
45
|
dom.once('built', async () => {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Attribute } from "../Attribute";
|
|
2
|
-
export declare class ClassConstructor extends Attribute {
|
|
3
|
-
static readonly canDefer: boolean;
|
|
4
|
-
static readonly scoped: boolean;
|
|
5
|
-
protected attributeKey: string;
|
|
6
|
-
protected className: string;
|
|
7
|
-
protected defaultClassName: string;
|
|
8
|
-
setup(): Promise<void>;
|
|
9
|
-
protected instantiateClass(cls: any): void;
|
|
10
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ClassConstructor.js","sourceRoot":"","sources":["../../src/attributes/ClassConstructor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,0CAAuC;AACvC,wCAAqC;AAGrC;IAAsC,oCAAS;IAA/C;;IA0BA,CAAC;IAnBgB,gCAAK,GAAlB;;;;;;wBACU,WAAW,GAAU,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC;wBACpD,IAAI,CAAC,WAAW;4BACZ,sBAAO;wBAEX,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;wBAC/C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;wBAEnD,qBAAM,mBAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAA;;wBAAzD,GAAG,GAAG,SAAmD;wBAC/D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;wBAE3B,IAAI,IAAI,CAAC,YAAY,IAAI,WAAW;4BAChC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;wBACvD,qBAAM,iBAAM,KAAK,WAAE,EAAA;;wBAAnB,SAAmB,CAAC;;;;;KACvB;IAES,2CAAgB,GAA1B,UAA2B,GAAG;QAC1B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAxBsB,yBAAQ,GAAY,KAAK,CAAC;IAC1B,uBAAM,GAAY,IAAI,CAAC;IAFrC,gBAAgB;QAD5B,mBAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC;OACxB,gBAAgB,CA0B5B;IAAD,uBAAC;CAAA,AA1BD,CAAsC,qBAAS,GA0B9C;AA1BY,4CAAgB"}
|