vsn 0.1.60 → 0.1.61

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vsn",
3
- "version": "0.1.60",
3
+ "version": "0.1.61",
4
4
  "description": "SEO Friendly Javascript/Typescript Framework",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,49 @@
1
+ import {Scope} from "../Scope";
2
+ import {DOM} from "../DOM";
3
+ import {Tag} from "../Tag";
4
+ import {BlockType, Token, TokenType, Tree, TreeNode} from "../AST";
5
+ import {Node} from "./Node";
6
+ import {BlockNode} from "./BlockNode";
7
+ import {Registry} from "../Registry";
8
+
9
+ export class ClassNode extends Node implements TreeNode {
10
+ public static readonly classes: {[name: string]: ClassNode} = {};
11
+ protected requiresPrep: boolean = true;
12
+ public readonly classScope: Scope = new Scope();
13
+
14
+ constructor(
15
+ public readonly name: string,
16
+ public readonly block: BlockNode
17
+ ) {
18
+ super();
19
+ }
20
+
21
+ public async prepare(scope: Scope, dom: DOM, tag: Tag = null): Promise<void> {
22
+ if (ClassNode.classes[this.name]) return; // Don't re-prepare same classes
23
+ ClassNode.classes[this.name] = this;
24
+ await this.block.prepare(this.classScope, dom, tag);
25
+ Registry.class(this);
26
+ }
27
+
28
+ public async prepareTag(tag: Tag, dom: DOM) {
29
+ tag.createScope(true);
30
+ await this.block.prepare(tag.scope, dom, tag);
31
+ }
32
+
33
+ public async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
34
+ return null;
35
+ }
36
+
37
+ public static parse(lastNode, token, tokens: Token[]): ClassNode {
38
+ tokens.shift(); // skip 'class'
39
+ const nameParts: string[] = [];
40
+ for (const t of tokens) {
41
+ if (t.type === TokenType.L_BRACE) break;
42
+ nameParts.push(t.value);
43
+ }
44
+ const name = nameParts.join('');
45
+ tokens.splice(0, nameParts.length);
46
+ const block = Tree.processTokens(Tree.getNextStatementTokens(tokens, true, true));
47
+ return new ClassNode(name, block);
48
+ }
49
+ }
@@ -6,6 +6,8 @@ import {Node} from "./Node";
6
6
  import {FunctionArgumentNode} from "./FunctionArgumentNode";
7
7
  import {ScopeMemberNode} from "./ScopeMemberNode";
8
8
  import {FunctionNode} from "./FunctionNode";
9
+ import {ClassNode} from "./ClassNode";
10
+ import {Registry} from "../Registry";
9
11
 
10
12
  export class FunctionCallNode<T = any> extends Node implements TreeNode {
11
13
  constructor(
@@ -27,9 +29,23 @@ export class FunctionCallNode<T = any> extends Node implements TreeNode {
27
29
  if (this.fnc instanceof ScopeMemberNode) {
28
30
  functionScope = await this.fnc.scope.evaluate(scope, dom, tag);
29
31
  }
32
+
30
33
  const values = await this.args.evaluate(scope, dom, tag);
31
34
  const func = await this.fnc.evaluate(scope, dom, tag);
32
- if (func instanceof FunctionNode) {
35
+ if (!func) {
36
+ const functionName = await (this.fnc as any).name.evaluate(scope, dom, tag);
37
+ const classes: ClassNode[] = [];
38
+ for (const className of Array.from(tag.element.classList)) {
39
+ const cls = Registry.instance.classes.getSynchronous(className);
40
+ if (cls) classes.push(cls);
41
+ }
42
+ for (const cls of classes) {
43
+ if (cls.classScope.has(functionName)) {
44
+ const fnc = cls.classScope.get(functionName);
45
+ return (await fnc.evaluate(functionScope, dom, tag))(...values);
46
+ }
47
+ }
48
+ } else if (func instanceof FunctionNode) {
33
49
  return (await func.evaluate(functionScope, dom, tag) as any)(...values);
34
50
  } else {
35
51
  return func.call(functionScope.wrapped || functionScope, ...values);
@@ -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";
@@ -29,7 +29,7 @@ export class FunctionNode extends Node implements TreeNode {
29
29
 
30
30
  public async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
31
31
  return async (...args) => {
32
- const functionScope = new Scope(scope);
32
+ const functionScope = new FunctionScope(scope);
33
33
  for (const arg of this.args) {
34
34
  functionScope.set(arg, args.shift());
35
35
  }
@@ -45,7 +45,7 @@ export class FunctionNode extends Node implements TreeNode {
45
45
  for (const t of argTokens) {
46
46
  funcArgs.push(t[0].value);
47
47
  }
48
- const block = Tree.processTokens(Tree.getBlockTokens(tokens, null)[0]);
48
+ const block = Tree.processTokens(Tree.getNextStatementTokens(tokens, true, true));
49
49
  return new FunctionNode(name.value, funcArgs, block);
50
50
  }
51
51
  }
package/src/AST.ts CHANGED
@@ -27,6 +27,7 @@ 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";
30
31
 
31
32
  function lower(str: string): string {
32
33
  return str ? str.toLowerCase() : null;
@@ -67,6 +68,7 @@ export enum TokenType {
67
68
  ELSE_IF,
68
69
  ELSE,
69
70
  FUNC,
71
+ CLASS,
70
72
  NAME,
71
73
  L_BRACE,
72
74
  R_BRACE,
@@ -200,6 +202,10 @@ const TOKEN_PATTERNS: TokenPattern[] = [
200
202
  type: TokenType.FUNC,
201
203
  pattern: /^func\s/
202
204
  },
205
+ {
206
+ type: TokenType.CLASS,
207
+ pattern: /^class\s/
208
+ },
203
209
  {
204
210
  type: TokenType.ELEMENT_ATTRIBUTE,
205
211
  pattern: /^\.?@[-_a-zA-Z0-9]*/
@@ -492,6 +498,10 @@ export class Tree {
492
498
  node = FunctionNode.parse(node, token, tokens);
493
499
  blockNodes.push(node);
494
500
  node = null;
501
+ } else if (token.type === TokenType.CLASS) {
502
+ node = ClassNode.parse(node, token, tokens);
503
+ blockNodes.push(node);
504
+ node = null;
495
505
  } else if (StringFormatNode.match(tokens)) {
496
506
  node = StringFormatNode.parse(node, tokens[0], tokens);
497
507
  } 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;
@@ -26,6 +28,7 @@ export class DOM extends EventDispatcher {
26
28
  ) {
27
29
  super();
28
30
  this.observer = new MutationObserver(this.mutation.bind(this));
31
+ Registry.instance.classes.on('register', this.prepareDOMClass.bind(this));
29
32
  this.tags = [];
30
33
 
31
34
  this.window = new WrappedWindow(window);
@@ -56,6 +59,18 @@ export class DOM extends EventDispatcher {
56
59
  }
57
60
  }
58
61
 
62
+ public async prepareDOMClass(className: string, classObject: ClassNode) {
63
+ for (const element of Array.from(this.querySelectorAll(`.${className}`))) {
64
+ const tag: Tag = await this.getTagForElement(element as HTMLElement, true);
65
+ if (tag) {
66
+ await classObject.prepareTag(tag, this);
67
+ if (classObject.classScope.has('construct')) {
68
+ await tag.exec('construct()');
69
+ }
70
+ }
71
+ }
72
+ }
73
+
59
74
  public async getFromTag(tag: Tag, selector: string, create: boolean = false) {
60
75
  const nodes = this.querySelectorElement(tag.element, selector);
61
76
  return await this.getTagsForElements(Array.from(nodes) as Element[], create);
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
 
@@ -52,7 +54,7 @@ export class RegistryStore extends EventDispatcher {
52
54
 
53
55
  export class Registry extends EventDispatcher {
54
56
  protected static _instance: Registry;
55
- public readonly components: RegistryStore;
57
+ public readonly controllers: RegistryStore;
56
58
  public readonly classes: RegistryStore;
57
59
  public readonly models: RegistryStore;
58
60
  public readonly templates: RegistryStore;
@@ -63,7 +65,7 @@ export class Registry extends EventDispatcher {
63
65
 
64
66
  constructor() {
65
67
  super();
66
- this.components = new RegistryStore();
68
+ this.controllers = new RegistryStore();
67
69
  this.classes = new RegistryStore();
68
70
  this.models = new RegistryStore();
69
71
  this.templates = new RegistryStore();
@@ -73,16 +75,12 @@ export class Registry extends EventDispatcher {
73
75
  this.attributes = new RegistryStore();
74
76
  }
75
77
 
76
- public static component(key: string = null, setup = null) {
77
- return register('components', key, setup);
78
- }
79
-
80
- public static class(key: string = null, setup = null) {
81
- return register('classes', key, setup);
78
+ public static class(cls: ClassNode) {
79
+ Registry.instance.classes.register(cls.name, cls);
82
80
  }
83
81
 
84
82
  public static controller(key: string = null, setup = null) {
85
- return register('classes', key, setup);
83
+ return register('controllers', key, setup);
86
84
  }
87
85
 
88
86
  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
+ }
@@ -1,4 +1,32 @@
1
- import {ClassConstructor} from "./ClassConstructor";
1
+ import {Scope} from "../Scope";
2
+ import {Attribute} from "../Attribute";
3
+ import {Registry} from "../Registry";
2
4
 
3
- export class ControllerAttribute extends ClassConstructor {
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.classes.get(modelName);
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 {ClassConstructor} from "./ClassConstructor";
1
+ import {ControllerAttribute} from "./ControllerAttribute";
2
2
 
3
- export class ModelAttribute extends ClassConstructor {
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 typeIndex: number = value && value.indexOf('|') || -1;
40
- if (typeIndex > -1) {
41
- this.boundScope.setType(this.key, value.substr(typeIndex + 1));
42
- value = value.substr(0, typeIndex);
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.classes.register('Object', Object);
29
- this.registry.classes.register('WrappedArray', WrappedArray);
30
- this.registry.classes.register('Data', DynamicScopeData);
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.class('TestController')
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|integer">
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|integer"></div>
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.class('ListItemController')
8
+ @Registry.controller('ListItemController')
9
9
  class ListItemController{
10
10
  items: ListItemSpecTestItem[] = [];
11
11
 
12
12
  do() {}
13
13
  }
14
14
 
15
- @Registry.class('ListItemSpecTestItem')
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|integer"></li></ul>
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|integer">testing</span>
41
- <span id="test-float" vsn-set:float="142.3|float">testing</span>
42
- <span id="test-bool" vsn-set:bool="false|boolean">testing</span>
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,32 +0,0 @@
1
- import {Scope} from "../Scope";
2
- import {Attribute} from "../Attribute";
3
- import {Registry} from "../Registry";
4
-
5
- @Registry.attribute('vsn-controller')
6
- export class ClassConstructor 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.classes.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
- }
32
- }