vsn 0.1.71 → 0.1.74

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.
Files changed (54) hide show
  1. package/demo/demo.html +5 -1
  2. package/demo/vsn.js +2 -2
  3. package/demo/xhr.html +27 -5
  4. package/dist/AST/ArithmeticAssignmentNode.js +1 -1
  5. package/dist/AST/ArithmeticAssignmentNode.js.map +1 -1
  6. package/dist/AST/ClassNode.d.ts +24 -7
  7. package/dist/AST/ClassNode.js +220 -57
  8. package/dist/AST/ClassNode.js.map +1 -1
  9. package/dist/AST/ElementAttributeNode.js +13 -4
  10. package/dist/AST/ElementAttributeNode.js.map +1 -1
  11. package/dist/AST/FunctionCallNode.js +14 -3
  12. package/dist/AST/FunctionCallNode.js.map +1 -1
  13. package/dist/AST/Node.d.ts +4 -1
  14. package/dist/AST/Node.js.map +1 -1
  15. package/dist/AST/ScopeMemberNode.js.map +1 -1
  16. package/dist/AST.js +3 -1
  17. package/dist/AST.js.map +1 -1
  18. package/dist/DOM.d.ts +2 -0
  19. package/dist/DOM.js +16 -3
  20. package/dist/DOM.js.map +1 -1
  21. package/dist/EventDispatcher.d.ts +1 -0
  22. package/dist/EventDispatcher.js +16 -0
  23. package/dist/EventDispatcher.js.map +1 -1
  24. package/dist/Registry.js +1 -1
  25. package/dist/Registry.js.map +1 -1
  26. package/dist/Tag/List.d.ts +1 -1
  27. package/dist/Tag/List.js +4 -14
  28. package/dist/Tag/List.js.map +1 -1
  29. package/dist/Tag.d.ts +0 -1
  30. package/dist/Tag.js +0 -1
  31. package/dist/Tag.js.map +1 -1
  32. package/dist/version.d.ts +1 -0
  33. package/dist/version.js +5 -0
  34. package/dist/version.js.map +1 -0
  35. package/dist/vsn.js +2 -1
  36. package/dist/vsn.js.map +1 -1
  37. package/dist/vsn.min.js +2 -2
  38. package/package.json +3 -2
  39. package/src/AST/ArithmeticAssignmentNode.ts +1 -1
  40. package/src/AST/ClassNode.ts +138 -33
  41. package/src/AST/ElementAttributeNode.ts +9 -4
  42. package/src/AST/FunctionCallNode.ts +11 -2
  43. package/src/AST/Node.ts +5 -1
  44. package/src/AST/ScopeMemberNode.ts +0 -1
  45. package/src/AST.ts +3 -1
  46. package/src/DOM.ts +15 -3
  47. package/src/EventDispatcher.ts +8 -0
  48. package/src/Registry.ts +1 -1
  49. package/src/Tag/List.ts +5 -13
  50. package/src/Tag.ts +0 -1
  51. package/src/version.ts +1 -0
  52. package/src/vsn.ts +2 -1
  53. package/test/AST/ClassNode.spec.ts +66 -11
  54. package/test/attributes/ListItem.spec.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vsn",
3
- "version": "0.1.71",
3
+ "version": "0.1.74",
4
4
  "description": "SEO Friendly Javascript/Typescript Framework",
5
5
  "keywords": [
6
6
  "framework",
@@ -13,7 +13,8 @@
13
13
  ],
14
14
  "main": "./dist/vsn.js",
15
15
  "scripts": {
16
- "build": "rm -rf ./dist/ && tsc",
16
+ "build": "rm -rf ./dist/ && npm run-script version && tsc",
17
+ "version": "echo -n \"export const VERSION = '${npm_package_version}';\n\" > src/version.ts",
17
18
  "build_dev": "rm -rf ./dist/ && webpack --env BUILD=development BENCHMARK=1",
18
19
  "demo": "webpack --env BUILD=production BENCHMARK=1 && cp ./dist/vsn.min.js ./demo/vsn.js",
19
20
  "test": "karma start --single-run",
@@ -48,7 +48,7 @@ export class ArithmeticAssignmentNode extends Node implements TreeNode {
48
48
  }
49
49
  } else if ((this.left instanceof ElementAttributeNode || this.left instanceof ElementStyleNode) && this.left.elementRef) {
50
50
  const elements = await this.left.elementRef.evaluate(scope, dom, tag);
51
- if (this.left.elementRef.first) {
51
+ if (this.left.elementRef.first || elements instanceof DOMObject) {
52
52
  scopes.push(elements);
53
53
  } else {
54
54
  scopes = elements;
@@ -2,45 +2,89 @@ import {Scope} from "../Scope";
2
2
  import {DOM} from "../DOM";
3
3
  import {Tag} from "../Tag";
4
4
  import {Token, TokenType, Tree, TreeNode} from "../AST";
5
- import {Node} from "./Node";
5
+ import {INodeMeta, Node} from "./Node";
6
6
  import {BlockNode} from "./BlockNode";
7
7
  import {Registry} from "../Registry";
8
8
  import {OnNode} from "./OnNode";
9
9
  import {FunctionNode} from "./FunctionNode";
10
10
 
11
11
  export class ClassNode extends Node implements TreeNode {
12
+ public static readonly ClassesVariable = '_vsn_classes';
12
13
  public static readonly classes: {[name: string]: ClassNode} = {};
14
+ public static readonly classParents: {[name: string]: string[]} = {};
15
+ public static readonly classChildren: {[name: string]: string[]} = {}; // List of child class selectors for a given class selector
16
+ public static readonly preppedTags: {[name: string]: Tag[]} = {};
17
+
13
18
  protected requiresPrep: boolean = true;
14
19
  public readonly classScope: Scope = new Scope();
15
- protected _ready: boolean = false;
20
+ protected _fullSelector: string;
16
21
 
17
22
  constructor(
18
- public readonly name: string,
23
+ public readonly selector: string,
19
24
  public readonly block: BlockNode
20
25
  ) {
21
26
  super();
22
27
  }
23
28
 
29
+ public get fullSelector(): string {
30
+ return this._fullSelector;
31
+ }
32
+
24
33
  public updateMeta(meta?: any) {
25
34
  meta = meta || {};
26
35
  meta['ClassNode'] = this;
27
36
  return meta;
28
37
  }
29
38
 
30
- public async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta?: any): Promise<void> {
31
- meta = meta || {};
32
- meta['ClassNodePrepare'] = true;
33
- if (ClassNode.classes[this.name]) return; // Don't re-prepare same classes
34
- ClassNode.classes[this.name] = this;
35
- await this.block.prepare(this.classScope, dom, tag, meta);
36
- Registry.class(this);
37
-
38
- for (const element of Array.from(dom.querySelectorAll(`.${this.name}`))) {
39
- await ClassNode.checkForClassChanges(element as HTMLElement, dom, element[Tag.TaggedVariable] || null);
39
+ public async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta?: INodeMeta): Promise<void> {
40
+ meta = Object.assign({}, meta) || {};
41
+ const initial = !!meta['initial'];
42
+ let root: boolean = false;
43
+ meta['ClassNodePrepare'] = initial;
44
+
45
+ // Only prepare once during the initial prep, all subsequent prepares are on tag class blocks
46
+ if (initial) {
47
+ if (meta['ClassNodeSelector']) {
48
+ ClassNode.classChildren[meta['ClassNodeSelector'] as string].push(this.selector);
49
+ meta['ClassNodeSelector'] = `${meta['ClassNodeSelector']} ${this.selector}`;
50
+ } else {
51
+ root = true;
52
+ meta['ClassNodeSelector'] = this.selector;
53
+ }
54
+
55
+ this._fullSelector = meta['ClassNodeSelector'];
56
+ if (ClassNode.classes[this._fullSelector]) return; // Don't re-prepare same classes
57
+ ClassNode.classes[this._fullSelector] = this;
58
+ ClassNode.classChildren[this._fullSelector] = [];
59
+ ClassNode.preppedTags[this._fullSelector] = [];
60
+
61
+ if (ClassNode.classParents[this.selector] === undefined)
62
+ ClassNode.classParents[this.selector] = [];
63
+
64
+ ClassNode.classParents[this.selector].push(this._fullSelector);
65
+ await this.block.prepare(this.classScope, dom, tag, meta);
66
+ Registry.class(this);
67
+
68
+ if (root) {
69
+ await this.findClassElements(dom);
70
+ }
71
+ } else {
72
+ await this.block.prepare(this.classScope, dom, tag, meta);
40
73
  }
41
74
  }
42
75
 
43
- public async prepareTag(tag: Tag, dom: DOM, hasConstructor: boolean | null = null) {
76
+ public async findClassElements(dom) {
77
+ for (const element of Array.from(dom.querySelectorAll(this._fullSelector))) {
78
+ await ClassNode.addElementClass(this._fullSelector, element as HTMLElement, dom, element[Tag.TaggedVariable] || null);
79
+ }
80
+ for (const childSelector of ClassNode.classChildren[this._fullSelector]) {
81
+ const node = ClassNode.classes[`${this._fullSelector} ${childSelector}`];
82
+ if (!node) continue;
83
+ await node.findClassElements(dom);
84
+ }
85
+ }
86
+
87
+ public async constructTag(tag: Tag, dom: DOM, hasConstructor: boolean | null = null) {
44
88
  if (hasConstructor === null)
45
89
  hasConstructor = this.classScope.has('construct');
46
90
 
@@ -52,10 +96,12 @@ export class ClassNode extends Node implements TreeNode {
52
96
  const fnc = await fncCls.getFunction(tag.scope, dom, tag, false);
53
97
  await fnc();
54
98
  }
55
- tag.preppedClasses.push(this.name);
99
+ tag.dispatch(`${this.fullSelector}.construct`, tag.element.id);
100
+ ClassNode.preppedTags[this.fullSelector].push(tag);
101
+ ClassNode.addPreparedClassToElement(tag.element, this.fullSelector);
56
102
  }
57
103
 
58
- public async tearDownTag(tag: Tag, dom: DOM, hasDeconstructor: boolean | null = null) {
104
+ public async deconstructTag(tag: Tag, dom: DOM, hasDeconstructor: boolean | null = null) {
59
105
  if (hasDeconstructor === null)
60
106
  hasDeconstructor = this.classScope.has('deconstruct');
61
107
 
@@ -70,7 +116,9 @@ export class ClassNode extends Node implements TreeNode {
70
116
  tag.removeContextEventHandlers(on);
71
117
  }
72
118
  }
73
- tag.preppedClasses.splice(tag.preppedClasses.indexOf(this.name), 1);
119
+ tag.dispatch(`${this.fullSelector}.deconstruct`);
120
+ ClassNode.preppedTags[this.fullSelector].splice(ClassNode.preppedTags[this.fullSelector].indexOf(tag), 1);
121
+ await ClassNode.removePreparedClassFromElement(tag.element, this.fullSelector);
74
122
  }
75
123
 
76
124
  public async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
@@ -84,35 +132,92 @@ export class ClassNode extends Node implements TreeNode {
84
132
  if (t.type === TokenType.L_BRACE) break;
85
133
  nameParts.push(t.value);
86
134
  }
87
- const name = nameParts.join('').trim();
135
+ const selector = nameParts.join('').trim();
88
136
  tokens.splice(0, nameParts.length);
89
137
  const block = Tree.processTokens(Tree.getNextStatementTokens(tokens, true, true));
90
- return new ClassNode(name, block);
138
+ return new ClassNode(selector, block);
91
139
  }
92
140
 
93
141
  public static async checkForClassChanges(element: HTMLElement, dom: DOM, tag: Tag = null) {
94
- const classes: string[] = Array.from(element.classList);
95
- let addedClasses: string[] = classes.filter(c => Registry.instance.classes.has(c));
96
- let removedClasses: string[];
142
+ const localSelectors: string[] = [element.tagName.toLowerCase(), ...Array.from(element.classList).map(c => `.${c}`)];
143
+ const fullSelectors: string[] = [...ClassNode.getClassesForElement(element)];
144
+ if (element.id)
145
+ localSelectors.push(`#${element.id}`);
146
+
147
+ for (const selector in localSelectors) {
148
+ if (ClassNode.classParents[selector])
149
+ fullSelectors.push(...ClassNode.classParents[selector]);
150
+ }
97
151
 
98
152
  if (!tag) {
99
153
  tag = await dom.getTagForElement(element, true);
100
154
  }
101
- addedClasses = addedClasses.filter(c => !tag.preppedClasses.includes(c));
102
- removedClasses = tag.preppedClasses.filter(c => !classes.includes(c));
103
155
 
104
- for (const addedClass of addedClasses) {
105
- const classNode: ClassNode = Registry.instance.classes.getSynchronous(addedClass);
106
- if (classNode) {
107
- await classNode.prepareTag(tag, dom);
156
+ for (const selector of fullSelectors) {
157
+ const isPrepped = ClassNode.getClassesForElement(element).includes(selector);
158
+ const elements = Array.from(dom.querySelectorAll(selector));
159
+ const inElements = elements.includes(element);
160
+ let changed: boolean = false;
161
+
162
+ if (inElements && !isPrepped) {
163
+ await ClassNode.addElementClass(selector, element, dom, tag);
164
+ changed = true;
165
+ } else if (!inElements && isPrepped) {
166
+ await ClassNode.removeElementClass(selector, element, dom, tag);
167
+ changed = true;
108
168
  }
109
- }
110
169
 
111
- for (const removedClass of removedClasses) {
112
- const classNode: ClassNode = Registry.instance.classes.getSynchronous(removedClass);
113
- if (classNode) {
114
- await classNode.tearDownTag(tag, dom);
170
+ if (changed && ClassNode.classChildren[selector].length > 0) {
171
+ for (const childSelector of ClassNode.classChildren[selector]) {
172
+ for (const childElement of Array.from(dom.querySelectorAll(childSelector, tag)) as HTMLElement[]) {
173
+ await ClassNode.checkForClassChanges(childElement, dom, childElement[Tag.TaggedVariable] || null);
174
+ }
175
+ }
115
176
  }
177
+
178
+ }
179
+ }
180
+
181
+ public static getClassesForElement(element: HTMLElement): string[] {
182
+ if (!element[ClassNode.ClassesVariable])
183
+ element[ClassNode.ClassesVariable] = [];
184
+ return element[ClassNode.ClassesVariable];
185
+ }
186
+
187
+ public static addPreparedClassToElement(element: HTMLElement, selector: string) {
188
+ ClassNode.getClassesForElement(element).push(selector);
189
+ }
190
+
191
+ public static removePreparedClassFromElement(element: HTMLElement, selector: string) {
192
+ const classes = ClassNode.getClassesForElement(element);
193
+ classes.splice(classes.indexOf(selector), 1);
194
+ }
195
+
196
+ public static async addElementClass(selector: string, element: HTMLElement, dom: DOM, tag: Tag = null) {
197
+ const classes = ClassNode.getClassesForElement(element);
198
+ if (classes.includes(selector)) return;
199
+
200
+ if (!tag) {
201
+ tag = await dom.getTagForElement(element, true);
202
+ }
203
+
204
+ const classNode: ClassNode = Registry.instance.classes.getSynchronous(selector);
205
+ if (classNode) {
206
+ await classNode.constructTag(tag, dom);
207
+ }
208
+ }
209
+
210
+ public static async removeElementClass(selector: string, element: HTMLElement, dom: DOM, tag: Tag = null) {
211
+ const classes = ClassNode.getClassesForElement(element);
212
+ if (!classes.includes(selector)) return;
213
+
214
+ if (!tag) {
215
+ tag = await dom.getTagForElement(element, true);
216
+ }
217
+
218
+ const classNode: ClassNode = Registry.instance.classes.getSynchronous(selector);
219
+ if (classNode) {
220
+ await classNode.deconstructTag(tag, dom);
116
221
  }
117
222
  }
118
223
 
@@ -6,6 +6,7 @@ import {TreeNode} from "../AST";
6
6
  import {Node} from "./Node";
7
7
  import {ElementQueryNode} from "./ElementQueryNode";
8
8
  import {LiteralNode} from "./LiteralNode";
9
+ import {DOMObject} from "../DOM/DOMObject";
9
10
 
10
11
  export class ElementAttributeNode extends Node implements TreeNode {
11
12
  protected requiresPrep: boolean = true;
@@ -53,10 +54,14 @@ export class ElementAttributeNode extends Node implements TreeNode {
53
54
  async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta: any = null) {
54
55
  if (this.elementRef) {
55
56
  await this.elementRef.prepare(scope, dom, tag, meta);
56
- const tags: TagList = await this.elementRef.evaluate(scope, dom, tag, true);
57
- for (const t of tags)
58
- await t.watchAttribute(this.attributeName);
59
- } else if(tag) {
57
+ const tags: any = await this.elementRef.evaluate(scope, dom, tag, true);
58
+ if (tags instanceof TagList) {
59
+ for (const t of tags)
60
+ await t.watchAttribute(this.attributeName);
61
+ } else if (tags instanceof DOMObject) {
62
+ await (tags as DOMObject).watchAttribute(this.attributeName);
63
+ }
64
+ } else if (tag) {
60
65
  await tag.watchAttribute(this.attributeName);
61
66
  }
62
67
  }
@@ -8,6 +8,8 @@ import {ScopeMemberNode} from "./ScopeMemberNode";
8
8
  import {FunctionNode} from "./FunctionNode";
9
9
  import {Registry} from "../Registry";
10
10
  import {ElementQueryNode} from "./ElementQueryNode";
11
+ import {ClassNode} from "./ClassNode";
12
+ import {TagList} from "../Tag/List";
11
13
 
12
14
  export class FunctionCallNode<T = any> extends Node implements TreeNode {
13
15
  constructor(
@@ -31,7 +33,14 @@ export class FunctionCallNode<T = any> extends Node implements TreeNode {
31
33
  if (this.fnc instanceof ScopeMemberNode) {
32
34
  functionScope = await this.fnc.scope.evaluate(scope, dom, tag);
33
35
  if (this.fnc.scope instanceof ElementQueryNode) {
34
- tags = await this.fnc.scope.evaluate(scope, dom, tag) as Tag[];
36
+ const _tags = await this.fnc.scope.evaluate(scope, dom, tag);
37
+ if (_tags instanceof Array) {
38
+ tags = _tags;
39
+ } else if (_tags instanceof Tag) {
40
+ tags = [_tags];
41
+ } else {
42
+ throw new Error('Invalid element query result');
43
+ }
35
44
  } else {
36
45
  tags = [tag];
37
46
  }
@@ -46,7 +55,7 @@ export class FunctionCallNode<T = any> extends Node implements TreeNode {
46
55
  let calls = 0;
47
56
  for (const _tag of tags) {
48
57
  let tagNum = 0;
49
- for (const className of _tag.preppedClasses) {
58
+ for (const className of _tag.element[ClassNode.ClassesVariable]) {
50
59
  tagNum++;
51
60
  const cls = Registry.instance.classes.getSynchronous(className);
52
61
  if (cls) {
package/src/AST/Node.ts CHANGED
@@ -3,6 +3,10 @@ import {DOM} from "../DOM";
3
3
  import {Tag} from "../Tag";
4
4
  import {TreeNode} from "../AST";
5
5
 
6
+ export interface INodeMeta {
7
+ [key: string]: string | number | boolean | null;
8
+ }
9
+
6
10
  export abstract class Node implements TreeNode {
7
11
  protected requiresPrep: boolean = false;
8
12
  protected _isPreparationRequired: boolean;
@@ -28,7 +32,7 @@ export abstract class Node implements TreeNode {
28
32
  return false;
29
33
  }
30
34
 
31
- async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta: any = null): Promise<void> {
35
+ async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta: INodeMeta = null): Promise<void> {
32
36
  for (const node of this.getChildNodes()) {
33
37
  await node.prepare(scope, dom, tag, meta);
34
38
  }
@@ -26,7 +26,6 @@ export class ScopeMemberNode extends ScopeNodeAbstract implements TreeNode {
26
26
  async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
27
27
  let scopes = [];
28
28
  const values = [];
29
-
30
29
  if (this.scope instanceof ElementQueryNode) {
31
30
  const elements = await this.scope.evaluate(scope, dom, tag);
32
31
  if (this.scope.first) {
package/src/AST.ts CHANGED
@@ -420,7 +420,9 @@ export class Tree {
420
420
  async prepare(scope: Scope, dom: DOM, tag: Tag = null) {
421
421
  if (!this._root.isPreparationRequired())
422
422
  return;
423
- return await this._root.prepare(scope, dom, tag);
423
+ return await this._root.prepare(scope, dom, tag, {
424
+ initial: true
425
+ });
424
426
  }
425
427
 
426
428
  async bindToScopeChanges(scope, fnc, dom: DOM, tag: Tag = null) {
package/src/DOM.ts CHANGED
@@ -18,6 +18,7 @@ export enum EQuerySelectDirection {
18
18
  export class DOM extends EventDispatcher {
19
19
  protected static _instance: DOM;
20
20
  protected _root: Tag;
21
+ protected _ready: Promise<boolean>;
21
22
  protected tags: Tag[];
22
23
  protected observer: MutationObserver;
23
24
  protected evaluateTimeout: any;
@@ -32,6 +33,12 @@ export class DOM extends EventDispatcher {
32
33
  protected debug: boolean = false
33
34
  ) {
34
35
  super();
36
+ this._ready = new Promise((resolve) => {
37
+ this.once('built', () => {
38
+ resolve(true);
39
+ });
40
+ });
41
+
35
42
  this.observer = new MutationObserver(this.mutation.bind(this));
36
43
  this.tags = [];
37
44
 
@@ -49,6 +56,10 @@ export class DOM extends EventDispatcher {
49
56
  return this._root;
50
57
  }
51
58
 
59
+ public get ready(): Promise<boolean> {
60
+ return
61
+ }
62
+
52
63
  public async get(selector: string, create: boolean = false, tag: Tag = null, direction: EQuerySelectDirection = EQuerySelectDirection.DOWN): Promise<TagList> {
53
64
  switch (selector) {
54
65
  case 'window':
@@ -232,7 +243,7 @@ export class DOM extends EventDispatcher {
232
243
  const found: Element[] = [];
233
244
  for (const tag of this.tags)
234
245
  {
235
- if (elements.indexOf(tag.element) > -1) {
246
+ if (!found.includes(tag.element) && elements.indexOf(tag.element) > -1) {
236
247
  tags.push(tag);
237
248
  found.push(tag.element);
238
249
  }
@@ -242,8 +253,9 @@ export class DOM extends EventDispatcher {
242
253
  const notFound: Element[] = [...elements];
243
254
  for (let i = notFound.length; i >= 0; i--) {
244
255
  const element: Element = notFound[i];
245
- if (found.indexOf(element) > -1)
246
- notFound.pop();
256
+ if (found.indexOf(element) > -1) {
257
+ notFound.splice(i, 1);
258
+ }
247
259
  }
248
260
 
249
261
  for (const element of notFound) {
@@ -70,6 +70,14 @@ export class EventDispatcher {
70
70
  return this.on(event, fct, context, true);
71
71
  }
72
72
 
73
+ promise(event: string, ...args: any[]): Promise<any> {
74
+ return new Promise((resolve, reject) => {
75
+ this.once(event, (...args) => {
76
+ resolve(args);
77
+ }, null);
78
+ });
79
+ }
80
+
73
81
  off(event: string, key?: number): boolean {
74
82
  if(!(event in this._listeners)) return false;
75
83
  if(key) {
package/src/Registry.ts CHANGED
@@ -90,7 +90,7 @@ export class Registry extends EventDispatcher {
90
90
  }
91
91
 
92
92
  public static class(cls: ClassNode) {
93
- Registry.instance.classes.register(cls.name, cls);
93
+ Registry.instance.classes.register(cls.fullSelector, cls);
94
94
  }
95
95
 
96
96
  public static controller(key: string = null, setup = null) {
package/src/Tag/List.ts CHANGED
@@ -12,19 +12,6 @@ export class TagList extends Array<DOMObject> {
12
12
  return this[0].scope
13
13
  }
14
14
 
15
- on(event, cbOrSelector, cb) {
16
- if (typeof cbOrSelector === "function") {
17
- this.forEach(e => e.element.addEventListener(event, cbOrSelector))
18
- } else {
19
- this.forEach(elem => {
20
- elem.element.addEventListener(event, e => {
21
- if (e.target.matches(cbOrSelector)) cb(e)
22
- })
23
- })
24
- }
25
- return this
26
- }
27
-
28
15
  get elements(): HTMLElement[] {
29
16
  return this.map(e => e.element);
30
17
  }
@@ -37,6 +24,11 @@ export class TagList extends Array<DOMObject> {
37
24
  return this[this.length - 1];
38
25
  }
39
26
 
27
+ all(event: string): Promise<number[]> {
28
+ const promises = this.map(e => e.promise(event));
29
+ return Promise.all(promises);
30
+ }
31
+
40
32
  removeClass(className) {
41
33
  this.forEach(e => e.element.classList.remove(className))
42
34
  return this
package/src/Tag.ts CHANGED
@@ -26,7 +26,6 @@ export class Tag extends DOMObject {
26
26
  public readonly rawAttributes: { [key: string]: string; };
27
27
  public readonly parsedAttributes: { [key: string]: string[]; };
28
28
  public readonly deferredAttributes: Attribute[] = [];
29
- public readonly preppedClasses: string[] = [];
30
29
  protected _state: TagState;
31
30
  protected attributes: Attribute[];
32
31
  protected _nonDeferredAttributes: Attribute[] = [];
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const VERSION = '0.1.74';
package/src/vsn.ts CHANGED
@@ -8,6 +8,7 @@ import {Query} from "./Query";
8
8
  import {EventDispatcher} from "./EventDispatcher";
9
9
  import {DynamicScopeData} from "./Scope/DynamicScopeData";
10
10
  import {Controller} from "./Controller";
11
+ import {VERSION} from "./version";
11
12
 
12
13
  export class Vision extends EventDispatcher {
13
14
  protected static _instance: Vision;
@@ -59,7 +60,7 @@ export class Vision extends EventDispatcher {
59
60
  await this._dom.buildFrom(document, true);
60
61
  const now = (new Date()).getTime();
61
62
  const setupTime = now - startTime;
62
- console.info(`Took ${setupTime}ms to start up VisionJS. https://www.vsnjs.com/`, window ? `v${window['VSN_VERSION']}` : null);
63
+ console.info(`Took ${setupTime}ms to start up VisionJS. https://www.vsnjs.com/`, `v${VERSION}`);
63
64
  }
64
65
 
65
66
  public static get instance() {
@@ -1,29 +1,84 @@
1
1
  import {DOM} from "../../src/DOM";
2
+ import {ClassNode} from "../../src/AST/ClassNode";
3
+ import {Registry} from "../../src/Registry";
4
+ import {TagList} from "../../src/Tag/List";
5
+ import {Tag} from "../../src/Tag";
2
6
 
3
7
 
4
8
  describe('ClassNode', () => {
5
- it("properly define a simple class", (done) => {
9
+ it("properly combine nested classes", async () => {
6
10
  document.body.innerHTML = `
7
11
  <script type="text/vsn" vsn-script>
8
- class simple {
12
+ class .simple {
13
+ func construct() {}
14
+ class input {
15
+ func construct() {}
16
+ }
17
+ }
18
+ </script>
19
+ <div class="simple"><input /></div>
20
+ `;
21
+ const dom = new DOM(document);
22
+ await dom.ready;
23
+ await Registry.instance.classes.get('.simple input');
24
+ expect(ClassNode.classParents['input']).toBeInstanceOf(Array);
25
+ expect(ClassNode.classParents['input'].includes('.simple input')).toBe(true);
26
+ expect(ClassNode.classes['.simple input']).toBeInstanceOf(ClassNode);
27
+ });
28
+
29
+ it("properly define a simple class", async () => {
30
+ document.body.innerHTML = `
31
+ <script type="text/vsn" vsn-script>
32
+ class .simple-construct {
9
33
  func construct() {
10
- a|integer = "15";
34
+ a|integer = "15";
35
+ log('####### construct', a);
11
36
  }
12
37
 
13
38
  func test() {
14
39
  a += 1;
40
+ log('####### testing', a);
41
+ }
42
+ }
43
+ </script>
44
+ <div class="simple-construct" id="ele-1"></div>
45
+ <div class="simple-construct" id="ele-2"></div>
46
+ `;
47
+ const dom = new DOM(document);
48
+ await dom.ready;
49
+ await Registry.instance.classes.get('.simple-construct');
50
+ const t: TagList = await dom.exec('?(.simple-construct)');
51
+ expect(t).toBeInstanceOf(TagList);
52
+ expect(t.length).toBe(2);
53
+ await t.all('.simple-construct.construct');
54
+ console.log('####### hmm?', await dom.exec('?(.simple-construct).a'));
55
+ expect(await dom.exec('?(.simple-construct).a')).toEqual([15, 15]);
56
+ await dom.exec('?(.simple-construct).test()');
57
+ expect(await dom.exec('?(.simple-construct).a')).toEqual([16, 16]);
58
+ });
59
+
60
+ it("properly define a simple class with a parent", async () => {
61
+ document.body.innerHTML = `
62
+ <script type="text/vsn" vsn-script>
63
+ class .testing {
64
+ func construct() {
65
+ a|integer = 0;
66
+ }
67
+
68
+ class .test {
69
+ func construct() {
70
+ ?<(.testing).a += 1;
71
+ }
15
72
  }
16
73
  }
17
74
  </script>
18
- <div class="simple"></div>
19
- <div class="simple"></div>
75
+ <div class="testing"><span class="test"></span></div>
20
76
  `;
21
77
  const dom = new DOM(document);
22
- dom.once('built', async () => {
23
- expect(await dom.exec('?(.simple).a')).toEqual([15, 15]);
24
- await dom.exec('?(.simple).test()');
25
- expect(await dom.exec('?(.simple).a')).toEqual([16, 16]);
26
- done();
27
- });
78
+ await dom.ready;
79
+ await Registry.instance.classes.get('.testing .test');
80
+ const t = await dom.exec('?(.testing .test)[0]');
81
+ await t.promise('.testing .test.construct');
82
+ expect(await dom.exec('?(.testing)[0].a')).toEqual(1);
28
83
  });
29
84
  });
@@ -68,7 +68,6 @@ describe('ListItem', () => {
68
68
  </ul>
69
69
  </div>
70
70
  `;
71
- console.log('########### test');
72
71
  const dom = new DOM(document);
73
72
  dom.once('built', async () => {
74
73
  const list = await dom.getTagForElement(document.getElementById('test'));