vsn 0.1.26 → 0.1.27

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 (191) hide show
  1. package/dist/AST.js +2197 -0
  2. package/dist/AST.js.map +1 -0
  3. package/dist/Attribute.d.ts +1 -1
  4. package/dist/Attribute.js +187 -0
  5. package/dist/Attribute.js.map +1 -0
  6. package/dist/Bencmark.js +179 -0
  7. package/dist/Bencmark.js.map +1 -0
  8. package/dist/Configuration.d.ts +1 -1
  9. package/dist/Configuration.js +64 -0
  10. package/dist/Configuration.js.map +1 -0
  11. package/dist/Controller.d.ts +1 -1
  12. package/dist/Controller.js +54 -0
  13. package/dist/Controller.js.map +1 -0
  14. package/dist/DOM/DOMObject.d.ts +1 -1
  15. package/dist/DOM/DOMObject.js +47 -0
  16. package/dist/DOM/DOMObject.js.map +1 -0
  17. package/dist/DOM/WrappedDocument.js +34 -0
  18. package/dist/DOM/WrappedDocument.js.map +1 -0
  19. package/dist/DOM/WrappedWindow.js +45 -0
  20. package/dist/DOM/WrappedWindow.js.map +1 -0
  21. package/dist/DOM.d.ts +1 -1
  22. package/dist/DOM.js +547 -0
  23. package/dist/DOM.js.map +1 -0
  24. package/dist/EventDispatcher.d.ts +26 -0
  25. package/dist/EventDispatcher.js +117 -0
  26. package/dist/EventDispatcher.js.map +1 -0
  27. package/dist/Formats.js +44 -0
  28. package/dist/Formats.js.map +1 -0
  29. package/dist/MessageList.d.ts +14 -0
  30. package/dist/MessageList.js +88 -0
  31. package/dist/MessageList.js.map +1 -0
  32. package/dist/Model/Collection.d.ts +5 -0
  33. package/dist/Model/Collection.js +37 -0
  34. package/dist/Model/Collection.js.map +1 -0
  35. package/dist/Model/DataModel.d.ts +6 -0
  36. package/dist/Model/DataModel.js +54 -0
  37. package/dist/Model/DataModel.js.map +1 -0
  38. package/dist/Model/ModelAbstract.d.ts +20 -0
  39. package/dist/Model/ModelAbstract.js +122 -0
  40. package/dist/Model/ModelAbstract.js.map +1 -0
  41. package/dist/Model/fields/BooleanField.d.ts +5 -0
  42. package/dist/Model/fields/BooleanField.js +43 -0
  43. package/dist/Model/fields/BooleanField.js.map +1 -0
  44. package/dist/Model/fields/EmailField.d.ts +5 -0
  45. package/dist/Model/fields/EmailField.js +36 -0
  46. package/dist/Model/fields/EmailField.js.map +1 -0
  47. package/dist/Model/fields/Field.d.ts +13 -0
  48. package/dist/Model/fields/Field.js +79 -0
  49. package/dist/Model/fields/Field.js.map +1 -0
  50. package/dist/Model/fields/FloatField.d.ts +5 -0
  51. package/dist/Model/fields/FloatField.js +47 -0
  52. package/dist/Model/fields/FloatField.js.map +1 -0
  53. package/dist/Model/fields/PositiveNumberField.d.ts +5 -0
  54. package/dist/Model/fields/PositiveNumberField.js +51 -0
  55. package/dist/Model/fields/PositiveNumberField.js.map +1 -0
  56. package/dist/Model/fields/StringField.d.ts +5 -0
  57. package/dist/Model/fields/StringField.js +43 -0
  58. package/dist/Model/fields/StringField.js.map +1 -0
  59. package/dist/Model.d.ts +11 -0
  60. package/dist/Model.js +72 -0
  61. package/dist/Model.js.map +1 -0
  62. package/dist/Query.js +66 -0
  63. package/dist/Query.js.map +1 -0
  64. package/dist/Registry.d.ts +2 -2
  65. package/dist/Registry.js +128 -0
  66. package/dist/Registry.js.map +1 -0
  67. package/dist/Scope.d.ts +7 -10
  68. package/dist/Scope.js +462 -0
  69. package/dist/Scope.js.map +1 -0
  70. package/dist/SimplePromise.d.ts +42 -0
  71. package/dist/SimplePromise.js +217 -0
  72. package/dist/SimplePromise.js.map +1 -0
  73. package/dist/Tag/List.js +85 -0
  74. package/dist/Tag/List.js.map +1 -0
  75. package/dist/Tag.js +770 -0
  76. package/dist/Tag.js.map +1 -0
  77. package/dist/Types.js +48 -0
  78. package/dist/Types.js.map +1 -0
  79. package/dist/attributes/AddClassIf.js +93 -0
  80. package/dist/attributes/AddClassIf.js.map +1 -0
  81. package/dist/attributes/Bind.js +272 -0
  82. package/dist/attributes/Bind.js.map +1 -0
  83. package/dist/attributes/ClassConstructor.js +104 -0
  84. package/dist/attributes/ClassConstructor.js.map +1 -0
  85. package/dist/attributes/ClickRemoveClass.js +114 -0
  86. package/dist/attributes/ClickRemoveClass.js.map +1 -0
  87. package/dist/attributes/ClickToggleClass.js +114 -0
  88. package/dist/attributes/ClickToggleClass.js.map +1 -0
  89. package/dist/attributes/ControllerAttribute.js +28 -0
  90. package/dist/attributes/ControllerAttribute.js.map +1 -0
  91. package/dist/attributes/DisableIf.js +94 -0
  92. package/dist/attributes/DisableIf.js.map +1 -0
  93. package/dist/attributes/Exec.js +108 -0
  94. package/dist/attributes/Exec.js.map +1 -0
  95. package/dist/attributes/Format.js +99 -0
  96. package/dist/attributes/Format.js.map +1 -0
  97. package/dist/attributes/If.js +159 -0
  98. package/dist/attributes/If.js.map +1 -0
  99. package/dist/attributes/JSONAttribute.js +118 -0
  100. package/dist/attributes/JSONAttribute.js.map +1 -0
  101. package/dist/attributes/KeyAbstract.js +117 -0
  102. package/dist/attributes/KeyAbstract.js.map +1 -0
  103. package/dist/attributes/KeyDown.js +88 -0
  104. package/dist/attributes/KeyDown.js.map +1 -0
  105. package/dist/attributes/KeyUp.js +88 -0
  106. package/dist/attributes/KeyUp.js.map +1 -0
  107. package/dist/attributes/List.js +282 -0
  108. package/dist/attributes/List.js.map +1 -0
  109. package/dist/attributes/ListItem.js +138 -0
  110. package/dist/attributes/ListItem.js.map +1 -0
  111. package/dist/attributes/ListItemModel.js +39 -0
  112. package/dist/attributes/ListItemModel.js.map +1 -0
  113. package/dist/attributes/ModelAttribute.js +29 -0
  114. package/dist/attributes/ModelAttribute.js.map +1 -0
  115. package/dist/attributes/Name.js +88 -0
  116. package/dist/attributes/Name.js.map +1 -0
  117. package/dist/attributes/On.js +135 -0
  118. package/dist/attributes/On.js.map +1 -0
  119. package/dist/attributes/Radio.js +174 -0
  120. package/dist/attributes/Radio.js.map +1 -0
  121. package/dist/attributes/Referenced.js +38 -0
  122. package/dist/attributes/Referenced.js.map +1 -0
  123. package/dist/attributes/RootAttribute.js +91 -0
  124. package/dist/attributes/RootAttribute.js.map +1 -0
  125. package/dist/attributes/ScopeAttribute.js +40 -0
  126. package/dist/attributes/ScopeAttribute.js.map +1 -0
  127. package/dist/attributes/ScopeChange.js +130 -0
  128. package/dist/attributes/ScopeChange.js.map +1 -0
  129. package/dist/attributes/SetAttribute.js +133 -0
  130. package/dist/attributes/SetAttribute.js.map +1 -0
  131. package/dist/attributes/StandardAttribute.js +186 -0
  132. package/dist/attributes/StandardAttribute.js.map +1 -0
  133. package/dist/attributes/StyleAttribute.js +183 -0
  134. package/dist/attributes/StyleAttribute.js.map +1 -0
  135. package/dist/attributes/Template.js +39 -0
  136. package/dist/attributes/Template.js.map +1 -0
  137. package/dist/attributes/TypeAttribute.js +104 -0
  138. package/dist/attributes/TypeAttribute.js.map +1 -0
  139. package/dist/attributes/_imports.js +60 -0
  140. package/dist/attributes/_imports.js.map +1 -0
  141. package/dist/helpers/DOMHelper.js +81 -0
  142. package/dist/helpers/DOMHelper.js.map +1 -0
  143. package/dist/helpers/ElementHelper.js +25 -0
  144. package/dist/helpers/ElementHelper.js.map +1 -0
  145. package/dist/helpers/VisionHelper.js +71 -0
  146. package/dist/helpers/VisionHelper.js.map +1 -0
  147. package/dist/helpers/decorators.js +38 -0
  148. package/dist/helpers/decorators.js.map +1 -0
  149. package/dist/vsn.d.ts +2 -1
  150. package/dist/vsn.js +166 -0
  151. package/dist/vsn.js.map +1 -0
  152. package/package.json +2 -6
  153. package/src/AST.ts +2 -2
  154. package/src/Attribute.ts +2 -2
  155. package/src/Configuration.ts +3 -3
  156. package/src/Controller.ts +1 -1
  157. package/src/DOM/DOMObject.ts +1 -1
  158. package/src/DOM.ts +3 -3
  159. package/src/EventDispatcher.ts +117 -0
  160. package/src/Formats.ts +2 -2
  161. package/src/MessageList.ts +81 -0
  162. package/src/Model/Collection.ts +13 -0
  163. package/src/Model/DataModel.ts +29 -0
  164. package/src/Model/ModelAbstract.ts +114 -0
  165. package/src/Model/fields/BooleanField.ts +16 -0
  166. package/src/Model/fields/EmailField.ts +12 -0
  167. package/src/Model/fields/Field.ts +65 -0
  168. package/src/Model/fields/FloatField.ts +22 -0
  169. package/src/Model/fields/PositiveNumberField.ts +24 -0
  170. package/src/Model/fields/StringField.ts +16 -0
  171. package/src/Model.ts +57 -0
  172. package/src/Registry.ts +3 -3
  173. package/src/Scope.ts +33 -89
  174. package/src/SimplePromise.ts +219 -0
  175. package/src/Tag.ts +2 -2
  176. package/src/attributes/Bind.ts +1 -1
  177. package/src/attributes/List.ts +3 -3
  178. package/src/attributes/Radio.ts +1 -1
  179. package/src/attributes/ScopeChange.ts +1 -1
  180. package/src/attributes/StandardAttribute.ts +1 -1
  181. package/src/attributes/StyleAttribute.ts +1 -1
  182. package/src/vsn.ts +3 -2
  183. package/test/AST.spec.ts +2 -1
  184. package/test/MessageList.spec.ts +101 -0
  185. package/test/Model/DataModel.spec.ts +141 -0
  186. package/test/Model.spec.ts +306 -0
  187. package/test/Scope.spec.ts +2 -2
  188. package/test/SimplePromise.spec.ts +271 -0
  189. package/test/attributes/Bind.spec.ts +5 -5
  190. package/test/attributes/ListItem.spec.ts +1 -1
  191. package/dist/vsn.min.js +0 -1
package/src/Attribute.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {Tag} from "./Tag";
2
2
  import {VisionHelper} from "./helpers/VisionHelper";
3
- import {EventDispatcher} from "simple-ts-event-dispatcher";
3
+ import {EventDispatcher} from "./EventDispatcher";
4
4
 
5
5
  export enum AttributeState {
6
6
  Instantiated,
@@ -83,7 +83,7 @@ export abstract class Attribute extends EventDispatcher {
83
83
  private setState(state: AttributeState) {
84
84
  const previousState = this._state;
85
85
  this._state = state;
86
- this.trigger('state', {
86
+ this.dispatch('state', {
87
87
  state: state,
88
88
  previousState: previousState,
89
89
  attribute: this
@@ -1,5 +1,5 @@
1
- import {EventDispatcher} from "simple-ts-event-dispatcher";
2
1
  import {VisionHelper} from "./helpers/VisionHelper";
2
+ import {EventDispatcher} from "./EventDispatcher";
3
3
 
4
4
  export type ConfigurationValue = string | number | boolean | null | undefined;
5
5
 
@@ -19,11 +19,11 @@ export class Configuration extends EventDispatcher {
19
19
  public set(key: string, value: ConfigurationValue) {
20
20
  const prev: ConfigurationValue = this.data[key];
21
21
  this.data[key] = value;
22
- this.trigger(`change:${key}`, {
22
+ this.dispatch(`change:${key}`, {
23
23
  value: value,
24
24
  previous: prev
25
25
  });
26
- this.trigger('change', {
26
+ this.dispatch('change', {
27
27
  key: key,
28
28
  value: value,
29
29
  previous: prev
package/src/Controller.ts CHANGED
@@ -1,6 +1,6 @@
1
- import {EventDispatcher} from "simple-ts-event-dispatcher";
2
1
  import {Scope} from "./Scope";
3
2
  import {Tag} from "./Tag";
3
+ import {EventDispatcher} from "./EventDispatcher";
4
4
 
5
5
  export abstract class Controller extends EventDispatcher {
6
6
  protected _scope: Scope;
@@ -1,5 +1,5 @@
1
- import {EventDispatcher} from "simple-ts-event-dispatcher";
2
1
  import {Scope} from "../Scope";
2
+ import {EventDispatcher} from "../EventDispatcher";
3
3
 
4
4
  export abstract class DOMObject extends EventDispatcher {
5
5
  protected _scope: Scope;
package/src/DOM.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import {Tag} from "./Tag";
2
2
  import {ElementHelper} from "./helpers/ElementHelper";
3
- import {EventDispatcher} from "simple-ts-event-dispatcher";
4
3
  import {Configuration} from "./Configuration";
5
4
  import {Tree} from "./AST";
6
5
  import {TagList} from "./Tag/List";
@@ -9,6 +8,7 @@ import {VisionHelper} from "./helpers/VisionHelper";
9
8
  import {WrappedWindow} from "./DOM/WrappedWindow";
10
9
  import {WrappedDocument} from "./DOM/WrappedDocument";
11
10
  import {Scope} from "./Scope";
11
+ import {EventDispatcher} from "./EventDispatcher";
12
12
 
13
13
  export class DOM extends EventDispatcher {
14
14
  protected static _instance: DOM;
@@ -37,7 +37,7 @@ export class DOM extends EventDispatcher {
37
37
  this.buildFrom(rootElement, true);
38
38
  }
39
39
  this.evaluate();
40
- Configuration.instance.bind('change', this.evaluate.bind(this));
40
+ Configuration.instance.on('change', this.evaluate.bind(this));
41
41
  }
42
42
 
43
43
  public get root(): Tag {
@@ -232,7 +232,7 @@ export class DOM extends EventDispatcher {
232
232
  });
233
233
  if (VisionHelper.doBenchmark) benchmarkEnd('DOM', 'observeTags');
234
234
 
235
- this.trigger('built');
235
+ this.dispatch('built');
236
236
  }
237
237
 
238
238
  async getTagsForElements(elements: Element[], create: boolean = false) {
@@ -0,0 +1,117 @@
1
+
2
+ export interface EventCallbackList {
3
+ [index: string]: EventCallback[];
4
+ }
5
+
6
+
7
+ export class EventCallback {
8
+ public calls: number;
9
+ constructor(
10
+ public readonly fnc: any,
11
+ public readonly key: number,
12
+ public readonly once: boolean,
13
+ public readonly context?: any,
14
+ ) {
15
+ this.calls = 0;
16
+ }
17
+
18
+ call(...args: any[]): boolean {
19
+ if(this.once && this.calls > 0)
20
+ return false;
21
+
22
+ this.fnc.apply(this.context, ...args);
23
+ this.calls += 1;
24
+ return true;
25
+ }
26
+ }
27
+
28
+ export type EventDispatcherCallback = (...args: any[]) => any;
29
+
30
+
31
+ export class EventDispatcher {
32
+ private static sources: EventDispatcher[] = [];
33
+ private readonly _listeners: EventCallbackList;
34
+ private _lastKey: number;
35
+
36
+ constructor() {
37
+ this._lastKey = 0;
38
+ this._listeners = {};
39
+ EventDispatcher.sources.push(this);
40
+ }
41
+
42
+ deconstructor() {
43
+ this.dispatch('deconstruct', this);
44
+ EventDispatcher.sources.splice(EventDispatcher.sources.indexOf(this), 1);
45
+ }
46
+
47
+ on(event: string, fct: EventDispatcherCallback, context?: any, once?: boolean): number {
48
+ once = once || false;
49
+ this._lastKey++;
50
+ this._listeners[event] = this._listeners[event] || [];
51
+ this._listeners[event].push(new EventCallback(fct, this._lastKey, once, context));
52
+ return this._lastKey;
53
+ }
54
+
55
+ once(event: string, fct: EventDispatcherCallback, context?: any): number {
56
+ return this.on(event, fct, context, true);
57
+ }
58
+
59
+ off(event: string, key?: number): boolean {
60
+ if(event in this._listeners === false) return false;
61
+ if(key) {
62
+ for(const cb of this._listeners[event]) {
63
+ if(key == cb.key) {
64
+ this._listeners[event].splice(this._listeners[event].indexOf(cb), 1);
65
+ return true;
66
+ }
67
+ }
68
+ } else {
69
+ this._listeners[event] = [];
70
+ return true;
71
+ }
72
+ return false;
73
+ }
74
+
75
+ offWithContext(event: string, context: any): number {
76
+ if(!(event in this._listeners)) return 0;
77
+ let toRemove: EventCallback[] = [],
78
+ cnt = 0;
79
+
80
+ for(const cb of this._listeners[event]) {
81
+ if(context == cb.context) {
82
+ toRemove.push(cb);
83
+ }
84
+ }
85
+
86
+ for(const cb of toRemove) {
87
+ this._listeners[event].splice(this._listeners[event].indexOf(cb), 1);
88
+ cnt++;
89
+ }
90
+ return cnt;
91
+ }
92
+
93
+ getListener(event: string, key: number): EventCallback | undefined {
94
+ for(const cb of this._listeners[event]) {
95
+ if(key == cb.key)
96
+ return cb;
97
+ }
98
+ }
99
+
100
+ dispatch(event: string, ...args: any[]): void {
101
+ if(!(event in this._listeners)) return;
102
+
103
+ for(let i = 0; i < this._listeners[event].length; i++) {
104
+ const cb: EventCallback = this._listeners[event][i];
105
+
106
+ // We need to unbind callbacks before they're called to prevent
107
+ // infinite loops if the event is somehow triggered within the
108
+ // callback
109
+ if(cb.once) {
110
+ this.off(event, cb.key);
111
+ i--;
112
+ }
113
+
114
+ cb.call(args);
115
+ }
116
+ }
117
+ }
package/src/Formats.ts CHANGED
@@ -15,8 +15,8 @@ export class Formats {
15
15
  currency: currency
16
16
  });
17
17
  };
18
- Configuration.instance.bind('change:locale', setup);
19
- Configuration.instance.bind('change:currency', setup);
18
+ Configuration.instance.on('change:locale', setup);
19
+ Configuration.instance.on('change:currency', setup);
20
20
  setup();
21
21
  }
22
22
  value = `${value}`.replace(/[^0-9.]+/, '');
@@ -0,0 +1,81 @@
1
+ export interface IMessageHash {
2
+ [key: string]: string[] | null | undefined;
3
+ }
4
+
5
+ export default class MessageList {
6
+ [key: string]: any; // We're trying to mimic a basic object
7
+
8
+ private _cachedList: IMessageHash | undefined;
9
+
10
+ constructor(messages?: IMessageHash) {
11
+ this.reset();
12
+ if (messages)
13
+ this.merge(messages);
14
+ }
15
+
16
+ reset(): void {
17
+ // Reset the object
18
+ const keys: string[] = this.keys;
19
+
20
+ this._cachedList = undefined;
21
+
22
+ if (keys.length > 0) {
23
+ for (const field of keys) {
24
+ delete this[field];
25
+ }
26
+ }
27
+ }
28
+
29
+ add(field: string, errors: string[] | string, replace: boolean = false) {
30
+ this.merge({
31
+ [field]: typeof errors == 'string' ? [errors] : errors
32
+ }, replace);
33
+ }
34
+
35
+ merge(messages: IMessageHash | null | undefined, replace: boolean = false) {
36
+ if (!messages) return;
37
+
38
+ this._cachedList = undefined;
39
+
40
+ const keys: string[] = this.keys;
41
+ for (const field in messages) {
42
+ if (!messages.hasOwnProperty(field))
43
+ continue;
44
+ const message: string[] | null | undefined = messages[field];
45
+
46
+ if (message instanceof Array) {
47
+ if (!replace && keys.indexOf(field) > -1) {
48
+ message.map((x) => {
49
+ this[field].push(x);
50
+ });
51
+ } else if (message.length > 0) {
52
+ this[field] = message;
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ get list(): IMessageHash {
59
+ if (this._cachedList)
60
+ return this._cachedList;
61
+
62
+ const list: IMessageHash = {},
63
+ keys: string[] = this.keys;
64
+
65
+ for (const field of keys) {
66
+ list[field] = this[field];
67
+ }
68
+ this._cachedList = list;
69
+ return list;
70
+ }
71
+
72
+ get keys(): string[] {
73
+ const keys = Object.keys(this);
74
+ keys.splice(keys.indexOf('_cachedList'), 1);
75
+ return keys;
76
+ }
77
+
78
+ get length(): number {
79
+ return this.keys.length;
80
+ }
81
+ }
@@ -0,0 +1,13 @@
1
+ import {Model} from "../Model";
2
+ import {ModelData} from "./ModelAbstract";
3
+
4
+ export class Collection<T extends Model> extends Array<T> {
5
+ getData: () => ModelData[] = (): ModelData[] => {
6
+ // Returns an array of data from all of the models in the collection
7
+ const data: ModelData[] = [];
8
+ for(const item of this) {
9
+ data.push(item.getData())
10
+ }
11
+ return data;
12
+ };
13
+ }
@@ -0,0 +1,29 @@
1
+ import {ModelAbstract, ModelData} from "./ModelAbstract";
2
+
3
+ export class DataModel extends ModelAbstract {
4
+ constructor(data: ModelData | string[]) {
5
+ super();
6
+ if(data instanceof Array) {
7
+ this.__fields__ = data;
8
+ for (const field of data)
9
+ this.createField(field);
10
+ } else {
11
+ this.setData(data);
12
+ }
13
+ }
14
+
15
+ setData(data: ModelData) {
16
+ for(const field of Object.keys(data))
17
+ if(this.__fields__.indexOf(field) == -1) {
18
+ this.__fields__.push(field);
19
+ this.createField(field);
20
+ }
21
+ super.setData(data);
22
+ }
23
+
24
+ on(event: string, fct: (...args: any[]) => any, context?: any, once?: boolean): number {
25
+ if(event.indexOf('change:') == 0)
26
+ this.createField(event.substr(7));
27
+ return super.on(event, fct, context, once);
28
+ }
29
+ }
@@ -0,0 +1,114 @@
1
+ import {Field} from "./fields/Field";
2
+ import {EventDispatcher} from "../EventDispatcher";
3
+
4
+ export interface ModelData {
5
+ [key: string]: any;
6
+ }
7
+
8
+ export abstract class ModelAbstract extends EventDispatcher {
9
+ [key: string]: any;
10
+ __fields__: string[];
11
+ protected _lastData: any;
12
+
13
+ constructor() {
14
+ super();
15
+
16
+ // Models may have __fields__ from prototype
17
+ if(!this['__fields__'])
18
+ this.__fields__ = [];
19
+ }
20
+
21
+ createField(field: string, fieldType = Field, config?: any) {
22
+ config = config || {};
23
+ const instance = new fieldType(this, config.default, config),
24
+ propDesc = Object.getOwnPropertyDescriptor(this, field);
25
+ this['__'+field] = instance;
26
+
27
+ // property getter
28
+ const fieldGetter = function() {
29
+ return instance.value;
30
+ };
31
+ const getter = propDesc ? propDesc.get : fieldGetter,
32
+ fieldSetter = function(newVal: any) {
33
+ instance.value = newVal;
34
+ },
35
+ setter = propDesc ? propDesc.set : fieldSetter;
36
+
37
+ // Delete the original property
38
+ delete this[field];
39
+
40
+ // Create new property with getter and setter
41
+ Object.defineProperty(this, field, {
42
+ get: getter,
43
+ set: setter,
44
+ enumerable: true,
45
+ configurable: true
46
+ });
47
+
48
+ instance.on('change', (...args: any[]) => {
49
+ this.dispatch('change', field, ...args);
50
+ this.dispatch('change:' + field, ...args);
51
+ });
52
+ return instance;
53
+ }
54
+
55
+ setData(data: ModelData) {
56
+ const fields = this.getFields();
57
+ for (const key in data) {
58
+ if (fields.indexOf(key) > -1) {
59
+ this[key] = data[key];
60
+ }
61
+ }
62
+ }
63
+
64
+ getData(): ModelData {
65
+ const data: ModelData = {};
66
+ for (const key of this.getFields()) {
67
+ const field = this['__'+key];
68
+ if(this[key] == null || !field)
69
+ continue;
70
+
71
+ data[key] = field.getData();
72
+ }
73
+ return data;
74
+ }
75
+
76
+ getFields(): string[] {
77
+ return this.__fields__;
78
+ }
79
+
80
+ getField(field: string): Field {
81
+ return this['__'+field];
82
+ }
83
+
84
+ bindToFields(event:string, fields:string[], callback: (...args: any[]) => any) {
85
+ for(const field of fields) {
86
+ const _field = this['__'+ field];
87
+ if(_field)
88
+ _field.on(event, callback);
89
+
90
+ }
91
+ }
92
+
93
+ setLastData() {
94
+ this._lastData = this.getData();
95
+ }
96
+
97
+ /*
98
+ * Revert data to the last setData() call. Useful for forms that edit a
99
+ * list of items and then hit cancel rather than saving the list.
100
+ */
101
+ revert() {
102
+ this.setData(this._lastData);
103
+ }
104
+
105
+ isModified() {
106
+ const oData = this._lastData,
107
+ nData = this.getData();
108
+ for(const key of this.getFields()) {
109
+ if(nData[key] != oData[key])
110
+ return true;
111
+ }
112
+ return false;
113
+ }
114
+ }
@@ -0,0 +1,16 @@
1
+ import { Field } from "./Field";
2
+
3
+ export class BooleanField extends Field {
4
+ set value(data:any) {
5
+ const oldValue = this.value;
6
+ this._value = !!data;
7
+ this.dispatch('change', {
8
+ oldValue:oldValue,
9
+ value:data
10
+ });
11
+ }
12
+
13
+ get value() {
14
+ return this._value;
15
+ }
16
+ }
@@ -0,0 +1,12 @@
1
+ import { Field } from "./Field";
2
+
3
+ export class EmailField extends Field {
4
+ _emailRegex = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
5
+
6
+ validate() {
7
+ super.validate();
8
+ if(this._value != null && !this._emailRegex.test(this._value))
9
+ this._errors.push('Please enter a valid email address');
10
+ return this._errors;
11
+ }
12
+ }
@@ -0,0 +1,65 @@
1
+ import {EventDispatcher} from "../../EventDispatcher";
2
+
3
+ export function field(fieldType = Field, config: {} | null = {}) {
4
+ return function(target: any, key: string) {
5
+ if(target.__fields__ == undefined) {
6
+ target.__fields__ = [];
7
+ }
8
+
9
+ // Abstract models share __fields__
10
+ if(target.__fields__.indexOf(key) == -1)
11
+ target.__fields__.push(key);
12
+
13
+ const getter = function() {
14
+ return [fieldType, config];
15
+ };
16
+
17
+ Object.defineProperty(target, '__'+key+'__', {
18
+ get: getter,
19
+ set: v => {},
20
+ enumerable:false,
21
+ configurable: true
22
+ });
23
+ }
24
+ }
25
+
26
+ export class Field extends EventDispatcher {
27
+ model:any;
28
+ _value:any;
29
+ _errors:String[];
30
+ config:any;
31
+
32
+ constructor(model:any, value?:any, config?: Object) {
33
+ super();
34
+ this.model = model;
35
+ this.config = config;
36
+ this.value = value;
37
+ this._errors = [];
38
+ }
39
+
40
+ set value(v:any) {
41
+ const oldValue = this._value;
42
+ this._value = v;
43
+ this.dispatch('change', {
44
+ oldValue:oldValue,
45
+ value:v
46
+ });
47
+ }
48
+
49
+ get value() {
50
+ return this._value;
51
+ }
52
+
53
+ getData() {
54
+ return this.value;
55
+ }
56
+
57
+ validate() {
58
+ this._errors = [];
59
+
60
+ if(this.config['required'] == true && this._value == null)
61
+ this._errors.push('This field is required.');
62
+
63
+ return this._errors;
64
+ }
65
+ }
@@ -0,0 +1,22 @@
1
+ import { Field } from "./Field";
2
+
3
+ export class FloatField extends Field {
4
+ set value(data:any) {
5
+ const oldValue = this.value;
6
+ if(typeof(data) == 'string')
7
+ data = parseFloat(data);
8
+
9
+ if(typeof(data) == "number" && this.config.toFixed)
10
+ data = parseFloat(data.toFixed(this.config.toFixed));
11
+
12
+ this._value = data;
13
+ this.dispatch('change', {
14
+ oldValue:oldValue,
15
+ value:data
16
+ });
17
+ }
18
+
19
+ get value() {
20
+ return this._value;
21
+ }
22
+ }
@@ -0,0 +1,24 @@
1
+ import { Field } from "./Field";
2
+
3
+ export class PositiveIntegerField extends Field {
4
+ set value(data:any) {
5
+ const oldValue = this.value;
6
+ if(data == null) {
7
+ this._value = null;
8
+ return;
9
+ } else if(typeof(data) == 'string')
10
+ data = parseInt(data);
11
+
12
+ if(data <= 0)
13
+ data = 0;
14
+ this._value = data;
15
+ this.dispatch('change', {
16
+ oldValue:oldValue,
17
+ value:data
18
+ });
19
+ }
20
+
21
+ get value() {
22
+ return this._value;
23
+ }
24
+ }
@@ -0,0 +1,16 @@
1
+ import { Field } from "./Field";
2
+
3
+ export class StringField extends Field {
4
+ set value(data:any) {
5
+ var oldValue = this.value;
6
+ this._value = ''+data;
7
+ this.dispatch('change', {
8
+ oldValue:oldValue,
9
+ value:data
10
+ });
11
+ }
12
+
13
+ get value() {
14
+ return this._value;
15
+ }
16
+ }
package/src/Model.ts CHANGED
@@ -0,0 +1,57 @@
1
+
2
+ import {ModelAbstract, ModelData} from "./Model/ModelAbstract";
3
+ import MessageList from "./MessageList";
4
+
5
+
6
+ export class Model extends ModelAbstract {
7
+ _errors!: MessageList;
8
+ _hasErrors: boolean;
9
+
10
+ constructor(data: ModelData | null | undefined = null) {
11
+ super();
12
+ const fields = this.__fields__.splice(0, this.__fields__.length);
13
+ for(const field of fields) {
14
+ (function(_self, field) {
15
+ if(!_self['__'+field+'__'])
16
+ return;
17
+
18
+ _self.__fields__.push(field);
19
+ const _field = _self['__'+field+'__'],
20
+ fieldType = _field[0],
21
+ config = _field[1] || {};
22
+
23
+ _self.createField(field, fieldType, config);
24
+ })(this, field);
25
+ }
26
+
27
+ this._hasErrors = false;
28
+ if (data)
29
+ this.setData(data);
30
+ this._lastData = this.getData();
31
+ this._constructor();
32
+ }
33
+
34
+ _constructor() {}
35
+
36
+ validate(): MessageList {
37
+ this._hasErrors = false;
38
+ this._errors = new MessageList;
39
+ for(const field of this.getFields()) {
40
+ const errors = this['__'+field].validate();
41
+ if(errors.length > 0) {
42
+ this._errors.add(field, errors, true);
43
+ this._hasErrors = true;
44
+ }
45
+ }
46
+ return this._errors;
47
+ }
48
+
49
+ hasErrors(): boolean {
50
+ this.validate();
51
+ return this._hasErrors;
52
+ }
53
+
54
+ get errors(): MessageList {
55
+ return this._errors;
56
+ }
57
+ }