-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.htm
More file actions
1531 lines (1300 loc) · 67.2 KB
/
index.htm
File metadata and controls
1531 lines (1300 loc) · 67.2 KB
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
<html>
<head>
<title>The Tiny Library</title>
<link href="lib/prism.css" rel="stylesheet">
<link href="index.css" rel="stylesheet">
<script type="text/javascript" src="lib/jquery-3.1.0.js"></script>
<script type="text/javascript" src="lib/require.js"></script>
<script type="text/javascript" src="index.js"></script>
</head>
<body>
<div class="sidebar">
<div id="content-table" class="content-table">
</div>
</div>
<div id="content-block" class="content">
<div id="test-info" class="test-info">
CODE TEST
</div>
<h1>The Tiny Function Library</h1>
<h2><a class="mark" name="">Introduction</a></h2>
<p>This small library is meant to provide some handy functions which are missing in DOM manipulating frameworks
like
jQuery or others.</p>
<p>And it doesn't want to be another armed-to-teeth full featured framework (might also be hard-to-learn).</p>
<p>Its goal is providing a minimalist building foundation for modern web pages.</p>
<h3><a class="mark" name="import">import()</a></h3>
<p>By default, Tiny will not inject global functions and object methods used in below examples into global
namespace
(the <code>window</code> object).</p>
<p>To use those short function names, pleas call <code>tiny.import()</code> ahead of all calls.</p>
<pre><code class="language-javascript">
// register global function names for tiny methods
tiny.import();
</code></pre>
<p>A typical initialization code with Tiny sequence looks like this:</p>
<pre><code class="language-javascript">
tiny.import(); // import functions & methods
tiny.output('error'); // output errors only in production environment
_storage.keyPrefix = 'my_app'; // set storage key prefix
var lang = _storage('lang'); // get user language setting
_lang.strings = LANG_STRINGS; // bind translation data
_lang.set(lang); // load correspond language strings
app.start(); // start your app script
</code></pre>
<h3><a class="mark" name="show-help">help()</a></h3>
<p>Show a structural overview of <code>tiny</code> namespace. This function will also show a list of injected
global
functions and prototype methods.</p>
<pre><code class="language-javascript">
// show tiny definitions in console
tiny.help();
</code></pre>
<hr />
<h2><a class="mark" name="basic-functions">Core Functions</a></h2>
<h3><a class="mark" name="type">_type()</a></h3>
<p>Get detailed object type. This function is slow on Objects, don't use it in performance critical operations.</p>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
ASSERT('undefined', _type() == 'undefined');
ASSERT('null', _type(null) == 'null');
ASSERT('number', _type(123) == 'number');
ASSERT('string', _type('test') == 'string');
ASSERT('function', _type(function(){}) == 'function');
ASSERT('object', _type({test: true}) == 'object');
ASSERT('RegExp', _type(/\s\t\n/) == 'RegExp');
ASSERT('Date', _type(new Date()) == 'Date');
ASSERT('Array', _type([1,2,3]) == 'Array');
ASSERT('Arguments', _type(arguments) == 'Arguments');
ASSERT('Window', _type(window) == 'Window');
ASSERT('HTMLCollection', _type(document.forms) == 'HTMLCollection');
ASSERT('NodeList', _type(document.querySelectorAll('.run-code')) == 'NodeList');
</code></pre>
<h3><a class="mark" name="each">_each()</a></h3>
<p>A handy replacement to the <code>for()</code> and <code>for...in</code> loop.</p>
<p>The syntax looks like Array.forEach() method, but you can also use it on <code>Array</code>, <code>Object</code>,
<code>Function</code>, <code>String</code>, <code>Number</code> and some Array-like objects:</p>
<ul>
<li><code>Arguments</code> - the <code>arguments</code> object inside a function</li>
<li><code>Storage</code> - the <code>window.localStorage</code> object</li>
<li><code>HTMLCollection</code> - like <code>document.forms</code></li>
<li><code>NodeList</code> - result of <code>document.querySelectorAll</code></li>
<li><code>jQuery</code> - result of <code>$()</code> function</li>
</ul>
<p>And you can continue or break with a return value.</p>
<p>The number of steps was determined by the array length before the loop starts. It won't change no matter you
add
items to or remove items from the array.</p>
<pre><code class="language-javascript">
_each(object, [start,] callback [,this_arg]) : any
// object : Array, Object, String or Number to loop
// start : you can set the start index of the loop (Array-like only)
// callback : callback function for the loop
// this_arg : set the value of 'this' inside the scope
// extended method for Array and String
Array._each([start,] callback [,this_arg]) : any
String._each([start,] callback [,this_arg]) : any
function callback(value, index, array) {
return; // continue to next item in array_or_object
return any_value; // break out, and _each() will return any_value
}
</code></pre>
<h4>NOTES</h4>
<p>Always use <code>for()</code> on Arrays in performance critical operations.</p>
<p>Below is a test result on an Array:</p>
<pre><code class="language-javascript">
for() 109.114ms // best performance
_each() 792.093ms // implement with for() loop
Array.forEach() 2052.318ms
for...of 1501.836ms
for...in 9032.005ms
</code></pre>
<p>For looping through Object, Map or Set, if you can use <code>for..of</code> feature of ECMAScript 6 in your
project,
the performance can be 50 times faster. And this feature allows you using <code>continue</code> and <code>break</code>
to control code flow.</p>
<p>Below is a test result on an Object:</p>
<pre><code class="language-javascript">
Object.keys() + for() 1064.989ms // not sure why this is slow
Object.keys().forEach() 145.356ms // fast but no flow control
_each() 822.088ms // implement with for...in loop
for...in 802.236ms
for...of 213.247ms // best choice - just a little slower than forEach()
</code></pre>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
var arr = [];
// ==> Number
// value: 1 -> 6, index: 0 -> 5
_each(6, function (value, index) {
arr[index] = value;
});
ASSERT('_each() on number', arr.join() === '1,2,3,4,5,6');
// ==> String
// value: a -> c, index: 1 -> 2
'abc'._each(1, function (value) {
arr.push(value);
});
ASSERT('_each() on string', arr.join() === '1,2,3,4,5,6,b,c');
// ==> Array
_each(arr, function (value, index, array) {
if (value < 4) return; // continue
if (value > 5) return true; // break
array[index] = value * 10;
});
ASSERT('_each() on array', arr.join() === '1,2,3,40,50,6,b,c' );
// return a value
var chr = arr._each(function (value, index, array) {
if (typeof value !== 'number')
return value; // return first non-number value
});
ASSERT('Array._each() & return value', chr === 'b' );
// ==> Object
var list = { action: 'test', animal: 'duck' };
var my_string = '';
_each(list, 'action', function (value, label) {
my_string += 'This is a ' + value + '. ';
});
ASSERT('_each() on object', my_string === 'This is a test. This is a duck. ');
// ==> Elements
var elems = document.querySelectorAll('h3');
var count = 0;
_each(elems, function (elem, index) {
if(elem.tagName != 'H3') count += index;
});
ASSERT('_each() on NodeList', count === 0 );
// ==> Arguments
// this code block is called as func(elem, 'this', 'is', 'a', 'test');
count = 0;
_each(arguments, function (arg, index) {
if(arg == 'test') count = index;
});
ASSERT('_each() on arguments', count === 4 );
</code></pre>
<h4>ERROR TESTS</h4>
<pre class="collapse run-code"><code class="error-test language-javascript">
// A TypeError will be thrown on invalid parameter types
// Detailed information can be found in console
try{
_each([1, 2, 3], 'error test 2');
FAIL('_each() paramter 2 error');
}catch(err){
ASSERT('_each() paramter 2 error', err instanceof TypeError );
_info(err.message)
}
</code></pre>
<h3><a class="mark" name="extend">_extend()</a></h3>
<p>Extend an object with new properties. This function does not do deep extension.</p>
<p>And Javscript preserved keywords can not be used or overwritten.</p>
<pre class="usage"><code class="language-javascript">
_extend(target, extensions[, overwrite]) : object
// target : the target Object or Function you want to extend
// extensions : an Object contains extensions that will be applied to target
// overwrite : whether overwrite existing properties, default is true
</code></pre>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
var point = { x: 1, y: 2 };
var point_3d = _extend(point, {z: 3});
ASSERT('_extend()', FLAT(point_3d) === '{"x":1,"y":2,"z":3}' );
// x will be overwrite
var point_4d = _extend(point_3d, {x: 10, t: 4});
ASSERT('_extend() overwrite', FLAT(point_4d) === '{"x":10,"y":2,"z":3,"t":4}' );
// x & y will not be overwrite
point_4d = _extend(point_3d, {x: 100, y: 100}, false);
ASSERT('_extend() no overwrite', FLAT(point_4d) === '{"x":10,"y":2,"z":3,"t":4}' );
</code></pre>
<h4>ERROR TESTS</h4>
<pre class="collapse run-code"><code class="error-test language-javascript">
// A TypeError will be thrown on invalid parameter types
// Detailed information can be found in console
try{
_extend('error test 1');
FAIL('_extend() parameter 1 error');
}catch(err){
ASSERT('_extend() parameter 1 error', err instanceof TypeError );
}
try{
_extend({}, 'error test 2');
FAIL('_extend() parameter 2 error');
}catch(err){
ASSERT('_extend() parameter 2 error', err instanceof TypeError );
_info(err.message)
}
</code></pre>
<h3><a class="mark" name="hash">hash()</a></h3>
<p>A fast hash function for generating 32-bit hash number from a string.</p>
<p>Currently it's using the Murmur2 algorithm. This algorithm is designed for uniqueness not security. Don't
use it for password hashing.</p>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
ASSERT('1', tiny.hash('This is a test') === 466082811);
// you can apply an integer seed
ASSERT('1', tiny.hash('This is a test', 5330) === 3689166302);
// kown Murmur2 collisions
ASSERT('collision 1', tiny.hash('costarring') !== tiny.hash('liquid'));
</code></pre>
<hr />
<h2><a class="mark" name="console">Console Functions</a></h2>
<h3><a class="mark" name="console-log">_log() ... _error()</a></h3>
<p>Tiny provides several shorthands to console methods: <code>_log()</code>, <code>_group()</code>, <code>_timer()</code>,
<code>_info()</code>,
<code>_warn()</code> and <code>_error()</code>.</p>
<p>You might use these functions just like the original methods of <code>console</code>. These are not wrapper
functions,
and will not show the wrapper function name & location in the console output.</p>
<p>By default, output of <code>_log()</code> & <code>_dir()</code> is turned off. Don't bother end users
with those
logs.
</p>
<p>You can easily turn the console log on or off by calling <code>tiny.output('all')</code> in develop or
production
environments.
</p>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
// enable _log() output when you need them
tiny.output('all');
// use them as you would like the console.xxx equivalents
_log('Test for _log()', '----------------------------');
_info('Test for _info()', null );
_warn('Test for _warn()', 'Nothing will happen');
_error('Test for _error()', '----------------------------');
</code></pre>
<h3><a class="mark" name="inspect">_inspect()</a></h3>
<p>The <code>_inspect</code> function is a helper function for debug purpose.</p>
<p>It will return a formatted JSON string version of given object for inspection.</p>
<p>Functions and special objects might lose in this process.</p>
<pre class="usage"><code class="language-javascript">
_inspect(obj [, key_filter] [, log]) : string
</code></pre>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
var obj = {
id: 123,
view: { loaded: true,
date: new Date(1467190047725) },
method: function(){}
};
var result = _inspect( obj, ['method'], false );
var expected_result = JSON.stringify({
"id": 123,
"view": {
"loaded": true,
"date": "2016-06-29T08:47:27.725Z"
}
}, null, 4);
// the result should looks like expected_result
ASSERT('_inspect()', result == expected_result);
</code></pre>
<h3><a class="mark" name="group">_group()</a></h3>
<p>Wrapper for console.group()/groupCollapsed()/groupEnd().</p>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
// call with a name to start a group
_group('test group');
// log something
_info('test', 123)
// call again to end a group
_group();
// collapsed group - first parameter is false
_group(false, 'test collapsed group');
_info('test', 456)
_group();
</code></pre>
<h3><a class="mark" name="time">_timer()</a></h3>
<p>Timing function using window.performance.now(). It returns a value on end</p>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
// call with an id to record start time
_timer('test');
// call again to output to console - last timer name can be ommitted
_timer();
// start another
_timer('test delayed');
setTimeout(function(){
// call with false as second parameter to get the value
ASSERT('time', _timer('test delayed') > 199);
}, 200);
</code></pre>
<hr />
<h2><a class="mark" name="message">Message System</a></h2>
<p>This tiny library includes a basic pseudo message system. Messages are more like remote function calls.</p>
<p>In order to distinguish from the DOM Event system, it is named _message.</p>
<p>Though it's not real asynchronize message delivery, it should be enough for most use cases on browser
client.</p>
<h3><a class="mark" name="message-listen">_message.register()</a></h3>
<p>Register messages.</p>
<p>All messages must be registered before using. Duplicate registration will result an error.</p>
<pre><code class="language-javascript">
_message.register([namespace,] array_of_message_name_strings) : _message
</code></pre>
<h3><a class="mark" name="message-listen">_message.listen()</a></h3>
<p>listen to a message.</p>
<p>There is no unlisten() method, since you can easily drop a message in your message handler by checking
internal flag
(and you definitely will have an internal flag in such case).</p>
<pre><code class="language-javascript">
_message.listen(message_name, handler) : _message
function handler(msg_name, param_1, param_2 ...){ ... }
</code></pre>
<h3><a class="mark" name="message-post">_message.post()</a></h3>
<p>Post a message to listeners.</p>
<pre><code class="language-javascript">
_message.post(msg_name, param_1, param_2 ...) : _message
</code></pre>
<h3><a class="mark" name="message-post-delayed">_message.postDelayed()</a></h3>
<p>Post a message to listeners after a specified delay in milliseconds.</p>
<p>If the same message is triggered again, the delay time will be reset. This behavior is suit for implement
Type-n-Search
like features.</p>
<pre><code class="language-javascript">
_message.postDelayed(delay_time, msg_name, param_1, param_2 ...) : _message
</code></pre>
<p>All these methods will return the <code>_message</code> object. You can chain methods if required.</p>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
var list = [];
// define the message handler
function put_in_list(){
var args = Array.prototype.slice.call(arguments);
list = list.concat(args);
}
// register messages
_message.register('global-msg')
_message.register('list::add')
_message.register(
'list', [
'remove',
'error' ]
)
// listen to the message
_message
.listen('list::add', put_in_list)
.listen('list::error', function(){ FAIL('_message.post()') });
// post a message
_message.post('list::add', 'me', 123);
// check result
ASSERT( '_message.post()', FLAT(list) == '["me",123]' );
// post a delayed message
_message.postDelayed(50, 'list::add', 'you', 456);
// check result after 100 milliseconds
setTimeout(function(){
ASSERT( '_message.postDelayed()', FLAT(list) == '["me",123,"you",456]' );
}, 100);
</code></pre>
<h4>ERROR TESTS</h4>
<pre class="collapse run-code"><code class="error-test language-javascript">
// A TypeError will be thrown on invalid parameter types
// Detailed information can be found in console
try{
_message.register(1, []);
FAIL('_message.register() parameter 1 error');
}catch(err){
ASSERT('_message.register() parameter 1 error', err instanceof TypeError );
_info(err.message)
}
try{
_message.register('ns', 2);
FAIL('_message.register() parameter 2 error');
}catch(err){
ASSERT('_message.register() parameter 2 error', err instanceof TypeError );
_info(err.message)
}
try{
_message.listen(['error test', 1]);
FAIL('_message.listen() parameter 1 error');
}catch(err){
ASSERT('_message.listen() parameter 1 error', err instanceof TypeError );
_info(err.message)
}
try{
_message.listen('my_msg', 'error test');
FAIL('_message.listen() parameter 2 error');
}catch(err){
ASSERT('_message.listen() parameter 2 error', err instanceof TypeError );
_info(err.message)
}
try{
_message.post(['error test'], null );
FAIL('_message.post() parameter 1 error');
}catch(err){
ASSERT('_message.post() parameter 1 error', err instanceof TypeError );
_info(err.message)
}
try{
_message.postDelayed(['error test 1'], null , null );
FAIL('_message.postDelayed() parameter 1 error');
}catch(err){
ASSERT('_message.postDelayed() parameter 1 error', err instanceof TypeError );
_info(err.message)
}
try{
_message.postDelayed(100, ['error test'], null );
FAIL('_message.postDelayed() parameter 2 error');
}catch(err){
ASSERT('_message.postDelayed() parameter 2 error', err instanceof TypeError );
_info(err.message)
}
</code></pre>
<hr />
<h2><a class="mark" name="route">Router System</a></h2>
<p>Routes are used to indicate a stateful view by a persistent URL.</p>
<p>With them you can access or share content directly with an URL, go back & forth with the browser
buttons.</p>
<p>They should not be used for temporary view like a popup dialog or represent an action to execute.</p>
<p>This is why the router system of Tiny is this simple.</p>
<p>And for simplicity and compatibility, this router system uses <code>window.location.hash</code> (#string)
for routing,
instead of the fancy History API introduced in HTML5.</p>
<p>And Tiny provides a predefined View Manager for fast implement of views.</p>
<h3><a class="mark" name="route-watch">_route.watch()</a></h3>
<p>Watch a certain route.</p>
<p>There is no unwatch() method, since you can easily drop a call in your route handler by checking internal
flag (and
you definitely will have an internal flag in such case).</p>
<p>The <code>route</code> parameter can be a string or a RegExp object.</p>
<pre><code class="language-javascript">
_route.watch(route, handler) : _route
function handler(current_route, param_object){ ... }
</code></pre>
<p>This method will return <code>_route</code> object, you can chain methods if needed.</p>
<p>The <code>route</code> parameter can be:</p>
<ul>
<li><code>'/'</code> - match <code>''</code> or <code>'/'</code> route only. This is used to implement
index view.</li>
<li><code>'/module'</code> - match any route which starts with a '/module' section.</li>
<li><code>'module'</code> - match any route which contains a 'module' section. A section must start with
'/' or start
of line, and end with '/' or end of line.</li>
<li><code>'/search/{keyword}/p{page}'</code> - will match '/search/me/p3' and call handler with {keyword:
'me', page:
'3'}
</li>
<li><code>/item=(.*?),(.*?)/i</code> - use RegExp to match, matched parameters will be returned in an
Array.</li>
</ul>
<p>The <code>handler</code> function will be called when:</p>
<ul>
<li>Current route is a match.</li>
<li>The route was matched last time, but no match this time.</li>
</ul>
<p>This behavior can make view switching easier.</p>
<p>The <code>current_route</code> parameter of the callback function will be current hash string without
starting '#'.</p>
<p>And the <code>param_object</code> will be <code>true</code> or an <code>Object</code> contains paramters
mateched
in URL. Like:</p>
<pre><code class="language-javascript">
// route match
-> handler('current/route', true);
// route match and has parameters
-> handler('current/route/noname/123', {name: 'noname', id: '123'});
</code></pre>
<p>If watched route was matched last time but not this time, the <code>param_object</code> will be <code>false</code>.</p>
<pre><code class="language-javascript">
// route not match
-> handler('current/route', false);
</code></pre>
<h3><a class="mark" name="route-check">_route.check()</a></h3>
<p>Check a route string or window.location.hash for route matching. Returns true if match found.</p>
<p>If you want Tiny to check automatically on window.location.hash changes, please use <code>_route.on()</code>.</p>
<pre><code class="language-javascript">
_route.check([route_string]) : boolean
// route_string : if not given, window.location.hash will be checked
</code></pre>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
var data = [];
function save_data(route, params){
data.push(params);
}
_route
.watch('/', function(route, param){ data.push(param ? 1 : 0) })
.watch('/list', save_data)
.watch('list/id:{id}', save_data);
// invoke root rule only
_route.check('');
ASSERT('_route.check() root', FLAT(data) === '[1]');
// '/' no match, a false parameter will be sent
// and match '/list' without parameter
var result = _route.check('/list/a');
ASSERT('_route.check() true', result === true);
ASSERT('data', FLAT(data) === '[1,0,true]');
// no match, but '/list' matched previously, a false parameter will be sent
result = _route.check('/id:123');
ASSERT('_route.check() false', result === false);
ASSERT('data add false', FLAT(data) === '[1,0,true,false]');
// no match again, nothing should happen
result = _route.check('lis');
ASSERT('_route.check() false again', result === false);
ASSERT('data no change', FLAT(data) === '[1,0,true,false]');
// match '/list/id:{id}' and get parameters
result = _route.check('word/list/id:123');
ASSERT('_route.check() true again', result === true);
ASSERT('data change', FLAT(data) === '[1,0,true,false,{"id":"123"}]');
data = [];
// match '/list' and '/list/id:{id}'
result = _route.check('/list/id:456');
ASSERT('_route.check() must true again', result === true);
ASSERT('data change again', FLAT(data) === '[true,{"id":"456"}]');
data = [];
// no match becuase the section string is 'my_list', not 'list'
// and previously matched routes will receive false
result = _route.check('my_list/id:789');
ASSERT('_route.check() shoule be false', result === false);
ASSERT('data add 2 false', FLAT(data) === '[false,false]');
</code></pre>
<h3><a class="mark" name="route-on">_route.on/off()</a></h3>
<p>Start or stop monitoring window.onhashchange event and check current window.location.hash immediately.</p>
<pre><code class="language-javascript">
_route.on() : _route
_route.off() : _route
</code></pre>
<h3><a class="mark" name="route-append">_route.append()</a></h3>
<p>Append sections to route string.</p>
<pre><code class="language-javascript">
_route.append(string_or_array[, trigger]) : _route
// string_or_array : a string or an array of items to append
// trigger : whether trigger route change event, default is true
</code></pre>
<h3><a class="mark" name="route-remove">_route.remove()</a></h3>
<p>Remove route string from first occurance of given string.</p>
<p>Slashes matters:</p>
<ul>
<li><code>'key'</code> - /my_key_chain/car_key/key:1' -> '/my_'.</li>
<li><code>'/key'</code> -'/my_key_chain/car_key/key:1' -> '/my_key_chain/car_key'.</li>
<li><code>'key/'</code> -'/my_key_chain/car_key/key:1' -> '/my_key_chain/car_'.</li>
<li><code>'/key/'</code> -'/my_key_chain/key/id:1' -> '/my_key_chain'.</li>
</ul>
<pre><code class="language-javascript">
_route.remove(section[, trigger]) : _route
// section : from which the route string will be removed
</code></pre>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
var data = [];
function save_data(route, params){
data.push(params);
}
_route
.watch('/', function(){ data.push('/') })
.watch('/books', save_data)
.watch('books/id:{id}', save_data)
.watch('books/{id}/{page}', save_data);
// set a test route - no event triggered
_route.set('/books', false);
ASSERT('_route.set() no match', FLAT(data) === '[]');
// start monitoring window.location.hash changes
_route.on();
// got a match immediately
ASSERT('_route.on() match', FLAT(data) === '[true]');
// add something to current route
_route.append('id:123');
ASSERT('_route.get()', _route.get() === '/books/id:123');
// match '/books' and '/books/id:{id}' with the parameter
ASSERT('_route.append()', FLAT(data) === '[true,true,{"id":"123"}]');
// remove the 'id:123' section without trigger the event
_route.remove('/id:', false);
ASSERT('_route.get() after cut', _route.get() === '/books');
// no change to the data
ASSERT('_route.remove()', FLAT(data) === '[true,true,{"id":"123"}]');
data = [];
// send an array to _route.add()
_route.append([123, 456]);
ASSERT('_route.append() array', _route.get() === '/books/123/456');
// '/books' match, 'books/id:{id}' no match, 'books/{id}/{page}' match
ASSERT('_route.append() result', FLAT(data) === '[true,false,{"id":"123","page":"456"}]');
// turn off event monitoring
_route.off();
// set a new route
_route.set('my/books/id:789');
ASSERT('_route.set() result', _route.get() === 'my/books/id:789');
// no change to the data
ASSERT('_route.set() array result', FLAT(data) === '[true,false,{"id":"123","page":"456"}]');
data = [];
// check window.location.hash
_route.check();
// '/books' no match, 'books/id:{id}' match, 'books/{id}/{page}' no match
ASSERT('_route.on() again', FLAT(data) === '[false,{"id":"789"},false]');
data = [];
// move to root
_route.set('/');
_route.check();
// '/' match, 'books/id:{id}' no match
ASSERT('_route.set() to root', FLAT(data) === '["/",false]');
</code></pre>
<h4>ERROR TESTS</h4>
<pre class="collapse run-code"><code class="error-test language-javascript">
// A TypeError will be thrown on invalid parameter types
// Detailed information can be found in console
try{
_route.watch(null);
FAIL('_route.watch() paramter 1 error');
}catch(err){
ASSERT('_route.watch() paramter 1 error', err instanceof TypeError );
}
try{
_route.watch('/test', null);
FAIL('_route.watch() paramter 2 error');
}catch(err){
ASSERT('_route.watch() paramter 2 error', err instanceof TypeError );
}
try{
_route.check(123);
FAIL('_route.check() paramter 1 error');
}catch(err){
ASSERT('_route.check() paramter 1 error', err instanceof TypeError );
}
try{
_route.append(null);
FAIL('_route.append() paramter 1 error');
}catch(err){
ASSERT('_route.append() paramter 1 error', err instanceof TypeError );
}
try{
_route.remove(null);
FAIL('_route.remove() paramter 1 error');
}catch(err){
ASSERT('_route.remove() paramter 1 error', err instanceof TypeError );
}
</code></pre>
<hr />
<h2><a class="mark" name="dom">DOM Manipulation</a></h2>
<h3><a class="mark" name="q">_q() and _q1()</a></h3>
<p>A tiny jQuery like function solely relies on <code>document.querySelector()</code>. It just provide some
basic functions in order to keep lean. And we shouldn't reinvent the wheel when there are plenty good ones.</p>
<p>Note: It might have problems under IE8 since many CSS3 selectors are not supported and IE8 has some
namespace problems on selectors.</p>
<hr />
<h2><a class="mark" name="storage-func">Local Storage</a></h2>
<h3><a class="mark" name="storage">_storage()</a></h3>
<p>A handy function for accessing <code>window.localStorage</code>.</p>
<p>Integer, Boolean, Array, Date & Object types will be automatically converted on access.</p>
<p>Note: All local pages might share a same localStorage store in some browsers. You can use <code>keyPrefix</code>
to avoid collisions.</p>
<pre><code class="language-javascript">
// filter keys by prefix, default is ''
tiny.storage.keyPrefix = 'your_prefix';
_storage() // return all values as an Object
_storage(key) // get value by key
_storage(key, value) // set value by key
_storage(key, null) // delete item of given key
_storage(null, null) // delete all contents
// or send an object for batch operations
_storage({
key1: value1,
key2: null,
...
})
</code></pre>
<h4>EXAMPLES</h4>
<pre class="run-code"><code class="language-javascript">
// clean up for test
_storage('tiny_lib_test', null);
// write a value
_storage('test', false);
ASSERT('_storage() no prefix', _storage('test') === false);
// set a prefix
tiny.storage.keyPrefix = 'tiny_lib';
// should not be able to read from previous key
ASSERT('_storage() with prefix', _storage('test') === undefined);
// write value - with prefix
_storage('test', true);
_storage('num', 123);
_storage('array', [4, 5, 6]);
_storage('date', new Date(1467190047725));
_storage('object', {x:1, y:2});
// read - with prefix
ASSERT('_storage() 1', _storage('test') === true);
ASSERT('_storage() 2', _storage('num') === 123);
ASSERT('_storage() 3', FLAT(_storage('array')) === '[4,5,6]');
ASSERT('_storage() 4', FLAT(_storage('date')) === '"2016-06-29T08:47:27.725Z"');
ASSERT('_storage() 5', FLAT(_storage('object')) === '{"x":1,"y":2}');
// read all - with prefix
var data = _storage();
ASSERT('_storage() all 1', data.test === true);
ASSERT('_storage() all 2', data.num === 123);
ASSERT('_storage() all 3', FLAT(data.array) === '[4,5,6]');
ASSERT('_storage() all 4', FLAT(data.date) === '"2016-06-29T08:47:27.725Z"');
ASSERT('_storage() all 5', FLAT(data.object) === '{"x":1,"y":2}');
// delete - with prefix
_storage('date', null);
ASSERT('_storage() delete', _storage('date') === undefined);
// batch operation
_storage({
bool: false, // add
num: 789, // modify
array: null // delete
});
ASSERT('_storage() batch 1', _storage('bool') === false);
ASSERT('_storage() batch 2', _storage('num') === 789);
ASSERT('_storage() batch 3', _storage('array') === undefined);
// delete all - with prefix
_storage(null, null);
ASSERT('_storage() delete all', FLAT(_storage()) === '{}');
tiny.storage.keyPrefix = '';
// previous value with no prefix should still there
ASSERT('_storage() no prefix value', _storage('test') === false);
</code></pre>
<h4>ERROR TESTS</h4>
<pre class="collapse run-code"><code class="error-test language-javascript">
// A TypeError will be thrown on invalid parameter types
// Detailed information can be found in console
try{
_storage(123);
FAIL('_storage() paramter 1 error');
}catch(err){
ASSERT('_storage() paramter 1 error', err instanceof TypeError );
}
</code></pre>
<h2><a class="mark" name="i18n">Internationalize</a></h2>
<p>Tiny provides a set of simple functions to make i18n easier.</p>
<h3><a class="mark" name="lang">_lang()</a></h3>
<p>Get a string from language string set.</p>
<p>Undefined strings will be collected in a list <code>_lang.missing</code> in runtime, this will make finding
missing
language strings easier.</p>
<pre><code class="language-javascript">
_lang(str [,lang_code])
// str : the string to lookup in current language string set
// lang_code : the language code
</pre></code>
<pre><code class="language-javascript">
</pre></code>
<p>The language strings should be assign to <code>_lang.strings</code>. You can modify it whatever you want.
But the default section set in <code>_lang.code</code> must exist.</p>
<pre class="run-code"><code class="language-javascript">
// current languague code - default is 'en'
// please use
_lang.code = 'en';
// assign set object
_lang.strings = {
// default = 'en'
"en" : {
'_name' : 'English',
"hello" : "Hello!"
},
// Simplified Chinese - 'zh-cn'
"zh-cn" : {
'_name' : '简体中文',
'language' : '=> _name', // refer to '_name', must starts with '=> '
// these options will be set to format functions on _lang.set() call if exist
'_decimalDelimiter' : '.',
'_thousandsDelimiter' : ' ',
'_currencyFormat' : '¥ [.00]',
'_dateFormat' : 'datetime',
'_dateNames' : {
day: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
dayAbbr: ['日', '一', '二', '三', '四', '五', '六'],
month: ['一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'],
monthAbbr: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'],
ap: ['上午', '下午']
},
// here goes your language strings
'hello' : "你好!"
}
};
</pre></code>
<h3><a class="mark" name="lang-set">_lang.set()</a></h3>
<p>Get a string from language string set.</p>
<pre><code class="language-javascript">
_lang.set([lang_code])
// lang_code : the language code
</pre></code>
<hr />
<h2><a class="mark" name="format-func">Format Template</a></h2>
<h3><a class="mark" name="format-format">_format()</a></h3>
<p>Simply yet powerful enough format function with template support.</p>
<p>This function support both normal html templates and the shorthand templates.</p>
<p>Templates are parsed in a one-time loop over the string. In favor of performance, no RegExp is used.</p>
<p>Expanded shorthand templates will be cached in memory or their original <script> tags for performance.</p>
<pre><code class="language-javascript">
_format(template, data_object);
// template : the template string
// data_object : an object contains data you want to fill into the template
String._format(data_object);
// format html template string
_format('<span class="date-tag">Today is {date}</span>', {date: new Date()});
// format shorthand template string (starts with '...')
_format('span.date-tag :Today is {date}', new Date());
// get template for HTML tag with id 'date-tag-template'
'#date-tag-template'._format(obj_date);
/*
>> Template Token format for value fill-in
{index} : refer to data_object[index] item by index
{key} : refer to data_object[key], the token will be kept if undefined
{*key} : optional token, fill with '' if undefined
{key|format} : set format string for data_object[key]
{} : fill with whole object, format with default style
{|format} : fill with whole object with given 'format' string
{[any text]} : contents inside brackets will be output directly
If a token's value is undefined, the token will be kept untouched.
>> Special Formats
{key|!html} : don't encode special html chars