-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathindex.html
1132 lines (955 loc) · 49.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="ObjectModel : Strong Dynamically Typed Object Modeling for JavaScript">
<meta name="author" content="Sylvain Pollet-Villard">
<title>Object Model</title>
<link rel="icon" type="image/png" href="site/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="site/favicon-16x16.png" sizes="16x16" />
<link rel="shortcut icon" href="site/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="site/style/main.css"/>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
<script defer src="site/lib/prism.js"></script>
<script defer src="site/js/main-compiled.js"></script>
<script defer src="dist/object-model.js"></script>
</head>
<body>
<button id="menu-button" class="lines-button arrow arrow-left" type="button" role="button" aria-label="Toggle Navigation">
<span class="lines"></span>
</button>
<nav id="menu">
<a class="title download-link" href="#download">
<h2>Download</h2>
</a>
<hr>
<a class="title github-link" href="https://github.com/sylvainpolletvillard/ObjectModel" target="_blank">
<h2>View on Github</h2>
</a>
<hr>
<div>
<a href="#introduction" class="title">Introduction</a>
<ul>
<li><a href="#introduction">What is it</a></li>
<li><a href="#video-demo">Video demo</a></li>
<li><a href="#features">Features</a></li>
</ul>
</div>
<hr>
<div>
<a href="#doc-basic-model" class="title">Documentation</a>
<ul>
<li><a href="#doc-basic-model">Basic models</a></li>
<li><a href="#doc-object-model">Object models</a></li>
<li><a href="#doc-es6-classes">Using ES6 class</a></li>
<li><a href="#doc-optional-properties">Optional properties</a></li>
<li><a href="#doc-multiple-types">Multiple types</a></li>
<li><a href="#doc-value-checking">Value checking and enumerations</a></li>
<li><a href="#doc-null-safe">Null-safe object traversal</a></li>
<li><a href="#doc-default-values">Default values</a></li>
<li><a href="#doc-composition">Composition</a></li>
<li><a href="#doc-extensions">Inheritance by extensions</a></li>
<li><a href="#doc-multiple-inheritance">Multiple inheritance</a></li>
<li><a href="#doc-assertions">Assertions for custom tests</a></li>
<li><a href="#doc-private-and-constants">Private and constant properties</a></li>
<li><a href="#doc-array-model">Array models</a></li>
<li><a href="#doc-function-model">Function models</a></li>
<li><a href="#doc-custom-collectors">Custom error collectors</a></li>
<li><a href="#api">Full API</a></li>
<li><a href="#common-models">Commonly used models</a></li>
<li><a href="#common-questions">Common questions</a></li>
</ul>
</div>
<hr>
</nav>
<div id="page">
<header class="header">
<img src="site/res/logo.png" alt="Object Model" width="800" height="150">
<hr>
<p class="description">Strong Dynamically Typed Object Modeling for JavaScript</p>
</header>
<section id="introduction" class="grid">
<div class="description">
<h2 id="what-is-it">What is this library ?</h2>
<p>A recurring criticism of JavaScript is that it is a weakly typed language. New languages that compile to JavaScript have been invented, such as <a href="https://www.typescriptlang.org" target="_blank">TypeScript</a> by Microsoft. We also have static analysis tools like <a href="https://flowtype.org" target="_blank">Flow</a> by Facebook. These solutions bring <mark>static</mark> typing, which means it only validate your code at build time, not runtime. <mark>Once compiled in JavaScript and run in the browser, there is no longer any guarantee that the variables you are working with have the intended types.</mark></p>
<p><mark>Static typing is insufficient to prevent most of the real and practical bugs caused by type errors</mark>. Indeed, JavaScript applications often involve <mark>unreliable data sources</mark>: user inputs, web services, server-side rendering, browser built-ins, external dependencies, CDN... Static typing can not check the validity of this content since it is retrieved at runtime. For the same reason, developers usually face more type errors with this kind of data compared to their own code that is under their control and can benefit from type inference with their IDE.</p>
<p>This is why Object Model is about <strong>strong dynamic type checking</strong>: it aims to get strong validation constraints for your variables at runtime. Whenever a property is modified, the whole object is validated against its definition. This allows you to identify a problem much more quickly thanks to the generated exceptions. Object Model is also very easy to master: no new language to learn, no new tools, no compilation step, just a minimalist and intuitive API in a plain old JS micro-library.</p>
<p>Validating at runtime also brings other benefits : you can define your own types, and use them in complex model definitions with custom assertions and more specific tests that can even change depending on your application state.</p>
<p>Actually it goes much further than just type safety. Go on and see for yourself.</p>
</div>
<div id="video-demo">
<iframe width="640" height="360" src="https://www.youtube.com/embed/zmojfyNH_EE?rel=0&showinfo=0" frameborder="0" allowfullscreen></iframe>
</div>
</section>
<hr>
<section id="features-and-download" class="grid">
<div id="features">
<h2 >What's inside the box ?</h2>
<p>Many features, hopefully neither too much nor too few:</p>
<ul>
<li>Typed object structures</li>
<li>Typed arrays</li>
<li>Typed functions</li>
<li>Union types</li>
<li>Enumerations</li>
<li>Custom assertions</li>
<li>Optional properties</li>
<li>Default values</li>
<li>Null-safe object traversal</li>
<li>Easy composition and inheritance through model extensions</li>
<li>Constants and private properties based on name conventions</li>
<li>Explicit error messages</li>
<li>Custom error collectors</li>
<li>all in <strong>8 kB minified, 3.4 kB gzipped</strong></li>
</ul>
</div>
<div id="download">
<h2>Download</h2>
<p><strong>Note: You are currently on v2 documentation. The latest documentation is available <a href="../../index.html">here</a>.</strong></p>
<h3>Latest 2.x version: v<span class="version">2.6.4</span></h3>
<ul>
<li>From <a href="https://www.npmjs.com/package/objectmodel" target="_blank">NPM</a> : <code>npm install objectmodel@2</code></li>
<li>Minified bundle (<strong class="size-gzip">3.3 KB</strong> gzipped) : <a href="dist/object-model.min.js">object-model.min.js</a></li>
<li>Source files :
<a class="link-zip" href="https://github.com/sylvainpolletvillard/ObjectModel/archive/v2.6.4.zip">object-model-2.6.4.zip</a>
</li>
</ul>
<h3>Changelog and previous releases</h3>
<p>Checkout the <a href="https://github.com/sylvainpolletvillard/ObjectModel/releases" target="_blank">Github Releases</a></p>
</div>
<div>
<h2>GitHub repository</h2>
<p>You can report bugs and contribute to the project on the <a href="https://github.com/sylvainpolletvillard/ObjectModel" target="_blank">GitHub repository</a>.</p>
</div>
</section>
<hr>
<section id="doc-basic-model" class="grid doc-code-code">
<div class="doc">
<h2>Basic models</h2>
<p><code>Model</code> is the library namespace and the constructor function for Models. It takes as an argument a <i>model definition</i>, and returns a <i>model</i>. Basic models validate the argument passed against the model definition, and return the validated value.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var NumberModel = Model(Number);
// 'new' keyword is optional for models and model instances</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var x = NumberModel("42");</code>
<code class="language-none exception">TypeError: expecting Number, got String "42"</code></pre>
</div>
</section>
<hr>
<section id="doc-object-model" class="grid doc-code-code">
<div class="doc">
<h2>Object models</h2>
<p>Object models validate nested object properties against a definition tree. You may consider them as definitions of classes with advanced validation options. They provide automatic validation at initial and future assignments of the properties of the instance objects.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Order = new Model({ // or Model.Object
product: {
name: String,
quantity: Number,
},
orderDate: Date
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var myOrder = new Order({
product: { name: "Apple Pie", quantity: 1 },
orderDate: new Date()
});
myOrder.product.quantity = 2; // no exceptions thrown
myOrder.product.quantity = false; //try to assign a Boolean</code>
<code class="language-none exception">TypeError: expecting product.quantity to be Number, got Boolean false</code></pre>
</div>
</section>
<hr>
<section id="doc-es6-classes" class="grid doc-code-code">
<div class="doc">
<h2>Usage with ES6 classes</h2>
<p>If you can use ES6 on your project, it is very easy to define a model for your classes:</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">class Character extends Model({ lastName: String, firstName: String }){
get fullName(){ return `${this.firstName} ${this.lastName}`; }
}</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var rick = new Character({ lastName: "Sanchez", firstName: "Rick" });
rick.lastName = 132;</code>
<code class="language-none exception">TypeError: expecting lastName to be String, got Number 132</code>
<code class="language-javascript">console.log(rick.fullName); // "Rick Sanchez"</code></pre>
</div>
</section>
<hr>
<section id="doc-optional-properties" class="grid doc-code-code">
<div class="doc">
<h2>Optional properties</h2>
<p>By default, model properties are mandatory. That means all properties defined are required on instance declaration, otherwise an exception will be raised. But you can specify a property to be optional by using the bracket notation, borrowed from the JSDoc specification:</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var User = Model({
email: String, // mandatory
name: [String] // optional
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var stan = User({ email: "stan@smith.com" }); // no exceptions
var roger = User({ name: "Roger" }); // email is mandatory</code>
<code class="language-none exception">TypeError: expecting email to be String, got undefined</code></pre>
</div>
</section>
<hr>
<section id="doc-multiple-types" class="grid doc-code-code">
<div class="doc">
<h2>Multiple types</h2>
<p>Several valid types can be specified for one property, aka union types. So optional properties are actually union types between the original type and the values <code>undefined</code> and <code>null</code>. To declare an optional union type, add <code>undefined</code> to the list.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Animation = new Model({
// can be a Number or a String
delay: [Number, String],
// optional property which can be a Boolean or a String
easing: [Boolean, String, undefined]
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var opening = new Animation({ delay: 300 }); // easing is optional
opening.delay = "fast"; // String is a valid type
opening.delay = null;</code>
<code class="language-none exception">TypeError: expecting delay to be Number or String, got null</code>
<code class="language-javascript">opening.easing = true; // Boolean is a valid type
opening.easing = 1;</code>
<code class="language-none exception">TypeError: expecting easing to be Boolean or String or undefined, got Number 1</code></pre>
</div>
</section>
<hr>
<section id="doc-value-checking" class="grid doc-code">
<div class="doc">
<h2>Value checking and enumerations</h2>
<p>Instead of types, values can be specified for model properties. The property value must match the model one. If a regular expression is passed, the value must match it. Use brackets notation for value enumerations. Note that values and types can be mixed for one property.</p>
</div>
<div class="panel">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Shirt = new Model({
// the only acceptable value is "clothes"
category: "clothes",
// valid values: 38, 42, "S", "M", "L", "XL", "XXL"...
size: [Number, "M", /^X{0,2}[SL]$/],
// valid values: "black", "#FF0000", undefined...
color: ["black","white", new RegExp("^#([A-F0-9]{6})$"), undefined]
});</code></pre>
</div>
</section>
<hr>
<section id="doc-null-safe" class="grid doc-code-code">
<div class="doc">
<h2>Null-safe object traversal</h2>
<p>When you want to traverse nested objects, you always have to worry about the null pointer exception. Some languages such as Groovy have a safe navigation operator represented by <code>?.</code> to safely navigate through potential null references. In JavaScript, there is no such solution so you have to manually check for <code>undefined/null</code> values at each level of the object. But within an Object Model, declared properties are null-safe for traversal: every instance complete its structure with undefined properties according to the model definition.</p>
</div>
<div class="panel panel1">
<span class="legend">Model and instanciation</span>
<pre><code class="language-javascript">var Config = new Model({
local: {
time: {
format: ["12h","24h", undefined]
}
}
});
var config = { local: undefined }; // object duck typed
var model_config = Config(config); // object model</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Traversal</span>
<pre><code class="language-javascript">if(config.local.time.format === "12h"){ hour %= 12; }</code>
<code class="language-none exception">TypeError: Cannot read property 'time' of undefined</code>
<code class="language-javascript">// so to prevent this exception, we have to check this way:
if(config != null
&& config.local != null
&& config.local.time != null
&& config.local.time.format === "12h"){
hour %= 12;
}
// with object models, no worries :)
if(model_config.local.time.format === "12h"){ hour %= 12; }
// model_config.local.time.format returns undefined</code></pre>
</div>
</section>
<hr>
<section id="doc-default-values" class="grid doc-code-code">
<div class="doc">
<h2>Default values assignment</h2>
<p>You can set a default value for any model with <code>model.defaultTo(value)</code>. This default value will be used if the argument passed to the model constructor is undefined.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var N = Model(Number).defaultTo(1)</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">N(5) + N() === 6</code></pre>
</div>
<div class="doc">
<p>To specify default values for some properties of your object models, put them in the model prototype. You can also use the <code>defaults</code> method as a shorthand for setting all the default values at once. If these are not defined at object instanciation, their default value will be assigned.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var FileInfo = Model({
name: String,
size: [Number],
creationDate: [Date],
writable: Boolean
}).defaults({
name: "Untitled file",
size: 0,
writable: true
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var file = new FileInfo({ writable: false });</code>
<code class="language-javascript">file.name; // name is mandatory but a default value was passed</code>
<code class="language-none log">"Untitled file"</code>
<code class="language-javascript">file.size; // size is optional, but the default value still apply</code>
<code class="language-none log">0</code>
<code class="language-javascript">file.creationDate; // no default value was passed for this property</code>
<code class="language-none log">undefined</code>
<code class="language-javascript">file.writable; // passed value overrides default value</code>
<code class="language-none log">false</code>
<code class="language-javascript">Object.keys(file);</code>
<code class="language-none log">["name","size","creationDate","writable"]</code></pre>
</div>
</section>
<hr>
<section id="doc-composition" class="grid doc-code-code">
<div class="doc">
<h2>Composition with models as types</h2>
<p>Models declared can also be used for type checking, so you can compose structures of models. Note that the property values do not necessarily need to be model instances to be considered valid: only the definition of the associated model must be respected. This is called <a href="http://en.wikipedia.org/wiki/Duck_typing" target="_blank">duck typing</a>, which can be summarized as <i>"If it looks like a duck and quacks like a duck, then it's a duck"</i>.</p>
<p>When a model definition is recognized, the value is automatically replaced by an instance of the corresponding model. This naive approach is very time saving and allows you, for example, to parse composed models from JSON in one step. If there is somehow an ambiguity (such as two possible valid models within an union type), the value is kept unchanged and a warning console message will inform you how to solve this ambiguity.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Person = Model({
name: String,
age: [Number]
});
var Lovers = Model({
husband: Person,
wife: Person
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var joe = { name: "Joe", age: 42 };
var ann = new Person({
name: joe.name + "'s wife",
age: joe.age - 5
});
var couple = Lovers({
husband: joe, // object duck typed
wife: ann // object model
});
couple.husband instanceof Person === true // object has been casted to Person</code></pre>
</div>
</section>
<hr>
<section id="doc-extensions" class="grid doc-code-code">
<div class="doc">
<h2>Inheritance by extension</h2>
<p>Extensions create new models based on existing model definitions. You can declare new properties or override previous ones. Therefore, extending object models is an easy way to reproduce subtyping and class inheritance patterns.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Person = Model({
name: String,
female: Boolean
});
var Mother = Person.extend({
female: true,
child: Person
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var joe = new Person({ name: "Joe", female: false });
var ann = new Person({ name: "Joe's wife", female: true });
var joanna = new Person({ name: "Joe's daughter", female: true });
ann.child = joanna;
ann = Mother(ann); // cast ann to Mother model
Mother.test(ann) && Person.test(ann)</code>
<code class="language-none log">true</code>
<code class="language-javascript">joe = Mother(joe); // try to cast joe to Mother model</code>
<code class="language-none exception">TypeError: expecting female to be true, got Boolean false
expecting child to be { name: String, female: Boolean }, got undefined</code></pre>
</div>
<p>Note that the <code>extend</code> function can receive any number of additional models and/or definitions as arguments. They will be merged using this strategy:</p>
<ul>
<li><mark>for object models</mark>, the resulting definition is the deep (recursive) merge of the definition objects
<br>i.e. <code>Mother</code> model in the example above becomes <code>Model({ name: String, female: true, child: Person })</code></li>
<li><mark>for other models</mark>, the resulting definition is the union of the definitions
<br>i.e. <code>Model.Array(Number).extend(Model.Array(String))</code> becomes <code>Model.Array([Number, String]) ;</code></li>
<li>the <mark>assertions</mark> of every model are retained and concatenated ;</li>
<li>the <mark>prototypes</mark> of every model are merged ;</li>
<li>the resulting <mark>constructor</mark> and <mark>error collector</mark> are the ones from the initially extended model.</li>
</ul>
</section>
<hr>
<section id="doc-multiple-inheritance" class="grid doc-code-code">
<div class="doc">
<h2>Multiple inheritance</h2>
<p>But it goes further: you can do multiple inheritance and mix any number of parent models definitions and assertions. If some properties have the same name, those of the last object overrides the others.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Client = Person.extend(User, Order, { store: String });
Client.prototype.sendConfirmationMail = function(){
return this.email + ": Dear " + this.name
+ ", thank you for ordering "
+ this.product.quantity + " " + this.product.name
+ " on " + this.store;
};
Object.keys(Client.definition);</code>
<code class="language-none log">["name", "female", "email", "product", "orderDate", "store"]</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var joe = new Client({
name: "Joe",
female: false,
email: "joe@email.net",
product: { name: "diapers", quantity: 100 },
orderDate: new Date(),
store: "daddy.net"
});
joe.sendConfirmationMail();</code>
<code class="language-none log">joe@email.net: Dear Joe, thank you for ordering 100 diapers on daddy.net</code></pre>
</div>
</section>
<hr>
<section id="doc-assertions" class="grid doc-code-code">
<div class="doc">
<h2>Assertions for custom validation tests</h2>
<p>You can add to your models any number of assertions that are custom test functions applied on model instances. All assertions are called every time the model is changed, and must all return <code>true</code> to validate. Exceptions thrown by assertions are catched and considered as assertion failures.</p>
<p>For example, we can get an Integer model by adding <code>Number.isInteger</code> as an assertion to a basic <code>Number</code> model.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var PositiveInteger = Model(Number)
.assert(Number.isInteger)
.assert(function(n){ return n >= 0 }, "should be greater or equal to zero")
function isPrime(n) {
for (var i=2, m=Math.sqrt(n); i <= m ; i++){
if(n%i === 0) return false;
}
return n > 1;
}
var PrimeNumber = PositiveInteger.extend().assert(isPrime);
// extend to not add isPrime assertion to the Integer model
</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">PositiveInteger(-1);</code>
<code class="language-none exception">TypeError: assertion should be greater or equal to zero returned false for value -1</code>
<code class="language-javascript">PositiveInteger(Math.sqrt(2));</code>
<code class="language-none exception">TypeError: assertion isInteger returned false for value 1.4142135623730951</code>
<code class="language-javascript">PrimeNumber(83);</code>
<code class="language-none log">83</code>
<code class="language-javascript">PrimeNumber(87);</code>
<code class="language-none exception">TypeError: assertion isPrime returned false for value 87</code></pre>
</div>
<div class="doc">
<p>Assertions are inherited from <code>Model.prototype</code> so you can add global assertions on all models. The second argument of the <code>assert</code> method is an optional message shown when assertion fails. It can be a String or a function returning a String.</p>
</div>
<div class="panel panel1">
<span class="legend">Configuration</span>
<pre><code class="language-javascript">Model.Object.prototype.assert(function noUndeclaredProperties(obj) {
var modelDefinition = this.definition;
var extraKeys = Object.keys(obj).filter(function(key){
return modelDefinition.hasOwnProperty(key) === false;
});
if(extraKeys.length === 0) return true; // validate assertion
else return extraKeys; // pass information to the error message
}, function(extraKeys){
return "These properties are not defined in the model: " + extraKeys.join(', ');
})</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Example</span>
<pre><code class="language-javascript">var Order = Model({ price: Number });
var expectedOrder = Order({ price: 42.50 });
var receivedOrder = Order({
price: 42.50,
taxes: 8.33,
deliveryCost: 15.00
});</code>
<code class="language-none exception">TypeError: These properties are not defined in the model: taxes, deliveryCost</code></pre>
</div>
</section>
<hr>
<section id="doc-private-and-constants" class="grid doc-code-code">
<div class="doc">
<h2>Private and constant properties</h2>
<p>Some variable naming conventions are commonly used in JavaScript. For example, a leading underscore is used to specify a <i>_private</i> variable which
should not be used outside its class methods. Also, constants are often in <i>ALL_CAPS</i>. Model definitions follow these conventions by making
<i>_underscored</i> properties not enumerable and <i>CAPITALIZED</i> properties not writable.</p>
<p>You can modify or remove these conventions by overriding <code>Model.conventionForPrivate</code> and <code>Model.conventionForConstant</code> functions.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Circle = Model({
radius: Number, // public
_diameter: [Number], // private
UNIT: ["px","cm","mm"], // constant
_ID: [Number], // private and constant
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var c = new Circle({ radius: 120, UNIT: "px", _ID: 1 });
c.radius = 100;
c.UNIT = "cm";</code>
<code class="language-none exception">TypeError: cannot redefine constant UNIT</code>
<code class="language-javascript">Object.keys(c); // private variables are not enumerated</code>
<code class="language-none log">["radius", "UNIT"]</code></pre>
</div>
</section>
<hr>
<section id="doc-array-model" class="grid doc-code-code">
<div class="doc">
<h2>Array models</h2>
<p>Array models validate the array elements types. All the validation options for object models are also available for array model elements: type/value checking, optional properties, union types, enumerations, assertions...</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Family = Model({
father: Father,
mother: Mother,
children: Model.Array(Person), // array of Persons
grandparents: [Model.Array([Mother, Father])]
// optional array of Mothers or Fathers
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var joefamily = new Family({
father: joe,
mother: ann,
children: [joanna, "dog"]
});</code>
<code class="language-none exception">TypeError: expecting Array[1] to be { name: String, female: Boolean }, got String "dog"</code>
</pre>
</div>
<div class="doc">
<p>The validation is done on initial array elements passed to the model, then on new elements added or modified afterwards.</p>
<p class="note">Note that for <a href="http://kangax.github.io/compat-table/es6/#test-Proxy" target="_blank">browsers which do not support ES6 proxies</a>, if you expand the array by storing an element at an index greater than the current array length (which is commonly considered as a bad practice), the new element and model assertions may not be validated. Use a mutator method like <code>push</code> or <code>splice</code> instead.</p>
</div>
<div class="panel panel1">
<pre><code class="language-javascript">var Cards = Model.Array([Number, "J","Q","K"]);
var Hand = Cards.extend("Joker").assert(function(cards){
return cards.length === 2;
}, "should have two cards");
// Hand is an array of 2 Numbers, J, Q, K or Joker</code></pre>
</div>
<div class="panel panel2">
<pre><code class="language-javascript">var myHand = Hand( [7, "Joker"] );
myHand[2] = "K"; // may not trigger validation this way
myHand.push("K"); // use this instead</code>
<code class="language-none exception">TypeError: assertion "should have two cards" returned false for value [7, "Joker", "K"]</code></pre>
</div>
</section>
<hr>
<section id="doc-function-model" class="grid doc-code-code">
<div class="doc">
<h2>Function models</h2>
<p>Function models provide validation on input (arguments) and output (return value). All the validation options for Object models are also available for Function models. The arguments passed to <code>Model.Function</code> are the types of the arguments the function will receive. The <code>defaults</code> method of Function models let you specify default values for missing arguments. Finally, the <code> return</code> method is used to specify the type of the function return value.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Calculator = Model.Function(Number, ["+","-","*","/"], Number)
.defaults(0, "+", 1)
.return(Number);</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var calc = new Calculator(function(a, operator, b){
return eval(a + operator + b);
});
calc(3, "+"); // use default value for 3rd argument</code>
<code class="language-none log">4</code>
<code class="language-javascript">calc(6, "*", null);</code>
<code class="language-none exception">TypeError: expecting arguments[2] to be Number, got null</code></pre>
</div>
<div class="doc">
<p>Under classical JavaScript OOP programming, methods are declared in the constructor's <code>prototype</code>. You can do the same with instances of function models. Another option is to declare function models in the model definition, then use the <code>defaults</code> method to set the implementation in the prototype as well. <a href="#doc-default-values">See the 'Default values' section</a>. The difference is that all the properties in the model definition are required for an object to be considered suitable for the model. In the following example, an object must have a function <code>sayMyName</code> to be valid as a Person, while the function <code>greet</code> is not mandatory.</p>
</div>
<div class="panel panel1">
<span class="legend">Model</span>
<pre><code class="language-javascript">var Person = Model({
name: String,
age: Number,
// function without arguments returning a String
sayMyName: Model.Function().return(String)
}).defaults({
sayMyName: function(){
return "my name is " + this.name;
}
});
// takes one argument of type Person, returns a String
Person.prototype.greet = Model.Function(Person).return(String)(function(otherguy){
return "Hello "+ otherguy.name + ", " + this.sayMyName();
});</code></pre>
</div>
<div class="panel panel2">
<span class="legend">Instance</span>
<pre><code class="language-javascript">var joe = new Person({ name: "Joe", age: 28 });
var ann = new Person({ name: "Ann", age: 23 });
joe.sayMyName();</code>
<code class="language-none log">my name is Joe</code>
<code class="language-javascript">joe.greet(ann);</code>
<code class="language-none log">Hello Ann, my name is Joe</code>
<code class="language-javascript">joe.greet("dog");</code>
<code class="language-none exception">TypeError: expecting arguments[0] to be {
name: String,
age: Number,
sayMyName: Function() => String
}, got String "dog"</code></pre>
</div>
</section>
<hr>
<section id="doc-custom-collectors" class="grid doc-code-code">
<h2>Custom error collectors</h2>
<div class="doc">
<p>By default, validation errors are collected every time a model instance is created or modified, and thrown as <code>TypeError</code> exceptions with a message describing all the errors found. It it possible to change this behaviour and add your own error collectors. For example, you may want to notify the user that an error occurred, or send the information to your server for error tracking on production.</p>
</div>
<div class="doc">
<p>Error collectors are callback functions called with an array of all the errors collected during the last model inspection. Every error is an object with these properties:</p>
<ul>
<li><code>message</code>: a message describing the error</li>
<li>[<code>expected</code>]: the expected type definition or assertion</li>
<li>[<code>received</code>]: the received value, to compare to the expected</li>
<li>[<code>path</code>]: the path where the error occurred in an object model definition</li>
</ul>
</div>
<div class="doc">
<h3>Global error collector</h3>
<p>This is how you define an error collector globally for all models.</p>
</div>
<div class="panel panel1">
<pre><code class="language-javascript">Model.prototype.errorCollector = function(errors){
console.log("Global error collector caught these errors:");
errors.forEach(function(error){ console.dir(error); });
};
var Student = Model({
name: String,
course: [ "math","english","history" ],
grade: Number
}).assert(function(stu){ return stu.grade >= 60 }, "should at least get 60 to validate semester")
new Student({ name: "Joanna", course: "sleep", grade: 0 });</code></pre>
</div>
<div class="panel panel2">
<pre><code class="language-none log">Global error collector caught these errors:
{
message: 'expecting course to be "math" or "english" or "history", got String "sleep"'
path: "course"
expected: ["math","english","history"]
received: "sleep"
}
{
message: "assertion should at least get 60 to validate semester returned false for value { name: "Joanna", course: "sleep", grade: 0 }",
path: null,
expected: function(stu){ return stu.grade >= 60 },
received: { name: "Joanna", course: "sleep", grade: 0 }
}</code></pre>
</div>
<div class="doc">
<h3>Model error collector</h3>
<p>This is how you define an error collector specifically by model</p>
</div>
<div class="panel panel1">
<pre><code class="language-javascript">Student.errorCollector = function(errors){
console.log("Student model error collector caught these errors:");
errors.forEach(function(error){ console.dir(error); });
};
new Student({ name: "Joanna", course: "math", grade: 50 });</code></pre>
</div>
<div class="panel panel2">
<pre><code class="language-none log">Student model collector caught these errors:
{
message: "assertion should at least get 60 to validate semester returned false for value { name: "Joanna", course: "math", grade: 50 }",
path: null,
expected: function(stu){ return stu.grade >= 60 },
received: { name: "Joanna", course: "math", grade: 50 }
}</code></pre>
</div>
<div class="doc">
<h3>Single-use error collector</h3>
<p>And this is how you define an error collector to be used only once with <code>validate(obj, myErrorCollector)</code></p>
</div>
<div class="panel panel1">
<pre><code class="language-javascript">Student.validate({
name: "Joanna",
course: "cheating",
grade: 90
}, function(errors){
console.log("This specific error collector caught these errors:");
errors.forEach(function(error){ console.dir(error); });
});</code></pre>
</div>
<div class="panel panel2">
<pre><code class="language-none log">This specific error collector caught these errors:
{
message: 'expecting course to be "math" or "english" or "history", got String "cheating"'
path: "course"
expected: ["math","english","history","science"]
received: "cheating"
}</code></pre>
</div>
</section>
<hr>
<section id="api">
<h2>Full API</h2>
<dl>
<dt><a href="#doc-basic-model">Model</a> <code>Model(definition)</code></dt>
<dd>Constructor for basic models. If a litteral object is passed, return an object model.</dd>
<dt><a href="#doc-object-model">Model.Object</a> <code>Model.Object(definition)</code></dt>
<dd>Constructor for object models</dd>
<dt><a href="#doc-array-model">Model.Array</a> <code>Model.Array(itemDefinition)</code></dt>
<dd>Constructor for array models</dd>
<dt><a href="#doc-function-model">Model.Function</a> <code>Model.Function(definitionArgument1, definitionArgument2, ...)</code></dt>
<dd>Constructor for function models</dd>
<dt><a href="#doc-private-and-constants">Model.conventionForConstant</a> <code>function(variableName)</code></dt>
<dd>Internal function used to identify a constant property based on naming convention. You can override it to suit your needs.</dd>
<dt><a href="#doc-private-and-constants">Model.conventionForPrivate</a> <code>function(variableName)</code></dt>
<dd>Internal function used to identify a non-enumerable property based on naming convention. You can override it to suit your needs.</dd>
</dl>
<h3>Model methods and properties</h3>
<dl>
<dt>definition <code>model.definition</code></dt>
<dd>Returns the model definition</dd>
<dt>assertions <code>model.assertions</code></dt>
<dd>Returns the list of model assertions</dd>
<dt>default <code>model.default</code></dt>
<dd>Returns the default value if defined</dd>
<dt>errorCollector <code>model.errorCollector = function(errors){ ... }</code></dt>
<dd>Function called when validation errors are detected</dd>
<dt><a href="#doc-extensions">extend</a> <code>model.extend(...otherDefinitions)</code></dt>
<dd>Returns a new model based on the initial model definition merged with other definitions passed.
Assertions are preserved too.</dd>
<dt><a href="#doc-assertions">assert</a> <code>model.assert(assertion, [description])</code></dt>
<dd>Add a test function to the model that must return <code>true</code> to validate the instance.</dd>
<dt><a href="#doc-default-values">defaultTo</a> <code>model.defaultTo(defaultValue)</code></dt>
<dd>Shorthand to set the default value of the model</dd>
<dt><a href="#doc-composition">test</a> <code>model.test(value)</code></dt>
<dd>Returns <code>true</code> if the value passed validates the model definition.
Works with <a href="#doc-composition">duck typing</a>.</dd>
<dt>validate <code>model.validate(instance, [errorCollector])</code></dt>
<dd>Manually triggers the validation of a model instance over its definition.
It is automatically done whenever an instance property changes.
<br>
Note that contrary to the <code>test</code> method, the object is not cast to its suitable
model (a.k.a <a href="#doc-composition">duck typing</a>) before being validated.
</dd>
</dl>
<h3>Object models</h3>
<dl>
<dt>defaults <code>objectModel.defaults(defaultValuesObject)</code></dt>
<dd>Merge the object passed within the model prototype to set defaults values for some model properties.</dd>
</dl>
<h3>Function models</h3>
<dl>
<dt>defaults <code>functionModel.defaults(defaultValueArgument1, defaultValueArgument2, ...)</code></dt>
<dd>Set default values for the function arguments.</dd>
<dt>return <code>functionModel.return(returnValueDefinition)</code></dt>
<dd>Set the definition of the return value. Each call to the function must return a validated value, otherwise an exception will be raised.</dd>
</dl>
</section>
<hr>
<section id="common-models">
<h2>Commonly used models</h2>
<p>Here are some models that you may find useful. <strong>These are not included in the library</strong>, so pick what you need or <a href="examples/common.js">get them all from here</a></p>
<details>
<summary data-source-trigger>Click here to show the source code of common models examples</summary>
<pre><code class="language-javascript" data-source="examples/common.js"></code></pre>
</details>
</section>
<hr>
<section id="common-questions">
<h2>Common questions</h2>
<details>
<summary id="browser-support">Which browsers are supported ?</summary>
<p>This library has been <a href="./test/index.html" target="_blank">unit tested</a> on latest versions of Chrome, Firefox and Edge, on Internet Explorer 11, Opera 20, and Safari 5.1. It should work on any browser implementing ECMAScript 5 specifications. If you find a fairly recent browser on which the tests do not pass, please post a Github issue and I will try to add support for it.</p>
<p>Support for IE < 11 had to be dropped in v2 because it required many hacks and was holding back other browsers. The latest version supporting IE9-10 is <a href="https://github.com/sylvainpolletvillard/ObjectModel/tree/master/dist" target="_blank">v1.2</a>.</p>
</details>
<details>
<summary>What is the impact on performance ?</summary>
<p>To get dynamic type validation, Object models have to override properties setters so that each assignment passes through a function that acts as a proxy. This has a cost, especially on old browsers. Therefore, it is not advisable to use object models in performance-critical parts of your applications. In particular, Array models and circular references in models have the most impact on performance. But in general, the loss of time does not exceed a few milliseconds and is quite negligible.</p>
</details>
<details>
<summary>How do I declare a constructor function to be called on instanciation before validating my model ?</summary>
<div class="grid doc-code">
<div class="doc">
<p>The recommended way is to use a factory function to instanciate your models. You can declare as many different factories as needed, which makes this pattern both simple and flexible.</p>
</div>
<div class="panel">
<pre><code class="language-javascript">var User = Model.Object({
firstName: String,
lastName: String,
fullName: String
});
User.create = function(properties){
properties.fullName = properties.firstName + " " + properties.lastName;
return new User(properties);
};
var joe = User.create({ firstName: "Joe", lastName: "Dalton" });</code></pre>
</div>
</div>
</details>
<details>
<summary>How can I get the model from an instance ?</summary>
<div class="grid doc-code">
<div class="doc">
<p>With the <code>constructor</code> property. If this property is used in your model, you can also retrieve it with <code>Object.getPrototypeOf(instance).constructor</code>. This is useful for retrieving the type of a property for example.</p>
</div>
<div class="panel">
<pre><code class="language-javascript">var User = Model({ name: String }),
joe = User({ name: "Joe" });
var modelOfJoe = joe.constructor // or Object.getPrototypeOf(joe).constructor;
// modelOfJoe === User
// modelOfJoe.definition.name === String</code></pre>
</div>
</div>
</details>
<details>
<summary>How should I deal with circular references in my model definitions ?</summary>
<div class="grid doc-code">
<div class="doc">
<p>You can't refer to a model or instance that is not yet defined, so you have to update the definition afterwards:</p>
</div>
<div class="panel">
<pre><code class="language-javascript">var Honey = Model({