Compare commits
1478 commits
Author | SHA1 | Date | |
---|---|---|---|
7afe45f1e6 | |||
6e01e45dfb | |||
12c30629af | |||
3e763efac9 | |||
825b915ae8 | |||
8e48bf2943 | |||
be7a5e585f | |||
c44bef22a2 | |||
6dcebbf67e | |||
44cb6f9124 | |||
571c31e8da | |||
8633891fdb | |||
8eddc6f35c | |||
577fb0b90a | |||
88a5bfe425 | |||
cac2900444 | |||
5dad4dae8e | |||
134851525c | |||
9a66748bb8 | |||
c52004421f | |||
f53b392c59 | |||
c496b0916d | |||
25b89b19e7 | |||
8b4a05be02 | |||
1c4961a25b | |||
40daf54768 | |||
c43debc7ac | |||
3ac2603e47 | |||
185c119342 | |||
80c9b3d734 | |||
e8605e5e19 | |||
bd95afb3eb | |||
c16f885951 | |||
6be9ad491f | |||
447a3119aa | |||
2025aa4010 | |||
bdc03755e8 | |||
2dceed888e | |||
982769a020 | |||
4318826ead | |||
66b8fa9d94 | |||
79cea3e27f | |||
7af493da27 | |||
90c3b7f937 | |||
85dce5847a | |||
65863b635a | |||
3c001f52e2 | |||
e3957977b6 | |||
8923c4db94 | |||
c7d1a25467 | |||
ebdd817199 | |||
27365e25d4 | |||
2e960867ab | |||
3d35d53005 | |||
02bc5e3ae1 | |||
09c1f8ff52 | |||
6b339f7bd7 | |||
a1efe26a85 | |||
64e6c6597f | |||
9363a7a661 | |||
41e220efa9 | |||
64d3808a2b | |||
eb15788965 | |||
fede2f6e86 | |||
9738c8ed91 | |||
b285d01b54 | |||
37eed72f2c | |||
71103cd04b | |||
42a347550b | |||
bed04b5b9b | |||
43fc68df31 | |||
d116525f73 | |||
d7ff36514d | |||
0d0abf3206 | |||
4cd004e423 | |||
459829a214 | |||
b8c0c1bc3f | |||
5686e8a470 | |||
b04d4f55ed | |||
3004963058 | |||
3454187be6 | |||
1dac7736b1 | |||
2c7ad88346 | |||
6e41c4a8c6 | |||
859f6b1eeb | |||
7204c4b546 | |||
f29b59fdbd | |||
6cc887e806 | |||
f7ad9741ca | |||
323a80d1d7 | |||
fc46f0b4ae | |||
0471570985 | |||
20243b641f | |||
99722cbdb0 | |||
64cc6825c5 | |||
bcb541fcad | |||
1c3c1bbb0a | |||
bba009c505 | |||
48659ec1d5 | |||
1d32dcea7d | |||
0e1c617b06 | |||
5c705fe591 | |||
3c501e3aef | |||
78ae09c861 | |||
2e1af5efb3 | |||
da270df5d6 | |||
8a8419bf02 | |||
a6c24c7885 | |||
ed2fef999a | |||
47c66d71bb | |||
f99f850c4d | |||
10c06b5100 | |||
1cc810c341 | |||
c707978e19 | |||
32b541239e | |||
2963962250 | |||
bbec44cc15 | |||
352a700fb2 | |||
fae4ec9e07 | |||
226f18a6f6 | |||
1d58191127 | |||
1559c74fd9 | |||
2cd30b1bfb | |||
cd5e5589a4 | |||
5499093810 | |||
df54ddda33 | |||
80ebc0aa8c | |||
a5fa336a08 | |||
36d4f85f6c | |||
e886a6f1a8 | |||
7204746e5f | |||
ecd33d0631 | |||
f97394fc32 | |||
eef0ab3bbd | |||
c48b48f924 | |||
d699d02914 | |||
09b3f7875d | |||
9c54974338 | |||
b89a52fb77 | |||
ef46fd4295 | |||
8d8c8ae74c | |||
fafa983dac | |||
a19c5a106f | |||
1e391233dc | |||
aade342976 | |||
0443442916 | |||
ed4979331c | |||
0e1a1ff3d1 | |||
e71b3a5c93 | |||
a22b009c57 | |||
f6a9a058c9 | |||
f7c231b99f | |||
6f4e5a91ab | |||
f405a45d79 | |||
fbe43b1db4 | |||
e13201733d | |||
002c46297e | |||
017337b2e2 | |||
51679ad61b | |||
bc34e8b187 | |||
58e90cb766 | |||
3243fad4da | |||
ea2a8be8b6 | |||
5e1fe901b7 | |||
c001e90617 | |||
a446c0afc0 | |||
ff88f6c7d7 | |||
d46265eb57 | |||
3057b1949c | |||
bca2cee175 | |||
077eb3fe87 | |||
90e4367951 | |||
589b356bc6 | |||
7664e054e5 | |||
2dcad20d0c | |||
86369ff116 | |||
f1bdd8997f | |||
eefbf3fe41 | |||
0b455e2ec4 | |||
de6745821f | |||
f79234ea7e | |||
8bcf731d3c | |||
5247493035 | |||
69a1fd3ee9 | |||
9c80252935 | |||
c22f9981e6 | |||
04012e5a97 | |||
02997076d4 | |||
9117097750 | |||
e3f9851848 | |||
f1febe1688 | |||
cf9a566e92 | |||
34e8b4099b | |||
495b64ecf0 | |||
0a69a99dc5 | |||
2d9671e10d | |||
0f380c03da | |||
df85babf34 | |||
131280c1a3 | |||
372473227c | |||
cb7fc0d4fa | |||
c3b5ef529c | |||
89b1814c2d | |||
9a1202c0b5 | |||
95475b022d | |||
8b1d3a6e23 | |||
6b2fbd1eef | |||
8d1dc88f6a | |||
34c56e5765 | |||
87ee312fc5 | |||
df0221d5e7 | |||
528d587a80 | |||
b8bdff83aa | |||
d40244b7e5 | |||
ec7799b57b | |||
fa28abbee8 | |||
e06dfbdc35 | |||
64f56c22a4 | |||
ecfa6c64ad | |||
37f6db7025 | |||
bfa440b265 | |||
bf9cbd0a69 | |||
967ae801b4 | |||
066c5fee56 | |||
807c191a01 | |||
9382a07c7b | |||
8b09b6feab | |||
d733766621 | |||
ceba24d727 | |||
c25cae1679 | |||
04342ac316 | |||
0ad57767f8 | |||
fd56dabea8 | |||
0b11de4f90 | |||
bbd0ce06ce | |||
500344c64a | |||
5845f8c52d | |||
d232f64fa5 | |||
53a77d1b97 | |||
44ce01be4b | |||
1f17364a00 | |||
a437dad841 | |||
99b34c07b4 | |||
ff977281c7 | |||
bf667cdd6f | |||
94d6d59593 | |||
1fc5fa5f8a | |||
34d35a3495 | |||
9750b07e06 | |||
bfe6b6dc2c | |||
67b1d4b65e | |||
c7482bec2d | |||
b38bc836ee | |||
79d06ff09e | |||
bd6afe484c | |||
6560392771 | |||
599717ae14 | |||
e96273e95f | |||
dcc0a43258 | |||
31ece0ff81 | |||
db0c95e197 | |||
2945c27ae9 | |||
786bb519b0 | |||
db1ecaec42 | |||
3f74756d58 | |||
dca79c8391 | |||
dd6de7f219 | |||
4e222fd98c | |||
e45c7ce863 | |||
b45815cfc7 | |||
481c7c3970 | |||
6b47928c92 | |||
259a88d0c5 | |||
91ec79d226 | |||
6630a87263 | |||
4d4d1f4212 | |||
7631b51f3d | |||
5bde707a0c | |||
2cea817247 | |||
68acd1fcb7 | |||
4d424d1d7e | |||
50fda2a900 | |||
335f97a9de | |||
a7e0f235a3 | |||
d6a79e14fc | |||
7e780f3463 | |||
439f183d95 | |||
10b80612fe | |||
72327b84cc | |||
ba5323ab3b | |||
a9c0fd9c2f | |||
e6049975ee | |||
62a55c8d95 | |||
5d4def11fc | |||
13eb677b8e | |||
d48bbeaab5 | |||
6fec4947c4 | |||
d3634bce5f | |||
6813debb6e | |||
b14667a345 | |||
2f825a25b4 | |||
cca223c4d4 | |||
09243a9bdb | |||
24e0f3d184 | |||
d70cf23624 | |||
2d963e782f | |||
c100f0f63f | |||
875fdc2603 | |||
4bdf873ec8 | |||
d1875f19f2 | |||
5254983778 | |||
7e13d59d0f | |||
a66402e139 | |||
8cb8f74fa3 | |||
c5775ae8aa | |||
a10d3a2830 | |||
2e089a1823 | |||
3231da9760 | |||
7533499c40 | |||
80cf351eab | |||
d790156373 | |||
70d791f41c | |||
f1f28b51cb | |||
8a94f2ffd2 | |||
a0f9f28397 | |||
71292db999 | |||
42a40a295b | |||
7a90cb5b13 | |||
7eecd95122 | |||
a45217bc9f | |||
d14a0a359a | |||
e09bab000b | |||
efffa40248 | |||
50afb94340 | |||
d35ea7cb26 | |||
52705e56f7 | |||
b34e5696f3 | |||
606cc00393 | |||
c728209c92 | |||
2e3bdda8bc | |||
7bc7b16cfc | |||
c5bc7cbb33 | |||
ec6d0779a1 | |||
bcbfe5251f | |||
453ff1c59c | |||
948055da09 | |||
f534a8b23f | |||
6c23ae883f | |||
55e4d6b511 | |||
2d5f7401f9 | |||
00143a686b | |||
46f7028580 | |||
313336ab5f | |||
89851eae3b | |||
e49538c442 | |||
cb0aa73a6d | |||
9b89b78f38 | |||
1a32ef216b | |||
bcf7189bbc | |||
9a55c86e5f | |||
c710fa3585 | |||
a5164f4ba5 | |||
0babe0249e | |||
ca4f0f2afa | |||
00150bc9e1 | |||
46b21b4a20 | |||
89b0cf843a | |||
946f600260 | |||
0cd8fb2aa3 | |||
ad8c86c599 | |||
bf1bdc5a0e | |||
ed01c6e201 | |||
b4905f7703 | |||
2cc28e8072 | |||
521d02c374 | |||
fe51fdecb8 | |||
bfa23c3b6e | |||
2abd81906b | |||
f1e046e331 | |||
ea8ca2a125 | |||
6bc6c8a8eb | |||
c0520dd4bb | |||
3d7945b09c | |||
84c964bf13 | |||
c7d6de72f7 | |||
81f31a3106 | |||
3995dee394 | |||
7540169fcd | |||
4bff89b70c | |||
c8df8992b3 | |||
4889b29cda | |||
b3101a8871 | |||
bf2f2bd747 | |||
b08b517fa7 | |||
f2c5edbfb8 | |||
ca6ced712a | |||
0d95699fa6 | |||
4378141d7d | |||
0631fd2ef7 | |||
7cbb38d9ba | |||
edd2494899 | |||
49eb4b7bd6 | |||
5d4627277a | |||
907ccd5e4a | |||
c26e6d3c4b | |||
e1106f23a8 | |||
7909cde78b | |||
bc416e0e39 | |||
934ba4add0 | |||
73fc78e9e3 | |||
c1000b7684 | |||
a34009f1e9 | |||
51e27c4a2f | |||
2fee4d95f2 | |||
5b6919ba39 | |||
974bb3cc77 | |||
1a1560d636 | |||
2d3f67d504 | |||
81c593382a | |||
62cab3ac2c | |||
4ac92a3e73 | |||
da8977433d | |||
cde2e8a326 | |||
7b03e23f3c | |||
dc3d40b12c | |||
48674c3b31 | |||
a92acfa615 | |||
be1bdf72cd | |||
b6d9bddc6f | |||
025717f61c | |||
e09d30c2cd | |||
39bbe6a19a | |||
2f7b650f5b | |||
85092bbd1c | |||
3bed943a42 | |||
2452a0bd9e | |||
98aca44cb9 | |||
33ed3cc194 | |||
2da8a56659 | |||
71d5cd7a49 | |||
05c2d6b666 | |||
50f4a0014b | |||
07eebbd528 | |||
9ccc4dbfd4 | |||
9d184df49a | |||
d664ebb780 | |||
0226300af8 | |||
1e8e4a6467 | |||
0ec96ed520 | |||
11d01b1661 | |||
2d0219b54d | |||
317d795974 | |||
f2f853c517 | |||
9468602f3d | |||
cc0932f5f7 | |||
f97ff3a52d | |||
8575d0ea68 | |||
d274876976 | |||
a9737da2f6 | |||
62964c2478 | |||
71e7012ba0 | |||
4a3fbbb736 | |||
ee128f9d80 | |||
9b29d1632e | |||
1946d75125 | |||
83ff0aab79 | |||
8dbc307a39 | |||
c7eefc8ddc | |||
0e2d17714d | |||
650959237b | |||
4a10876a96 | |||
d5ef7afa1e | |||
4cf9a4081d | |||
c1e26349ef | |||
16b2997a71 | |||
ab273644e5 | |||
731909a267 | |||
7f75e23b1b | |||
55eadd72eb | |||
4855c9064b | |||
032bdd53b0 | |||
f73aa02733 | |||
029f2c35e8 | |||
52a514088b | |||
c7756d8d58 | |||
054423f10d | |||
cccc4b71cf | |||
db2bb7de18 | |||
740cce0551 | |||
f6a93f7f00 | |||
b6db436bca | |||
158c8835f9 | |||
7a2bc50a56 | |||
74cea36219 | |||
2deeb5d57a | |||
619c272469 | |||
7862bc68d0 | |||
e2e8331a08 | |||
3134ee804b | |||
1c930a0184 | |||
51f188e63f | |||
2ca11374a0 | |||
79367df924 | |||
fe384896d5 | |||
f8c5d081d4 | |||
3ff774d78f | |||
33ae62cab7 | |||
8be5990a02 | |||
88d31090b4 | |||
fbfa2fdf96 | |||
f447199905 | |||
be67034e02 | |||
a01c914f0a | |||
c288fe44ce | |||
dc05239d57 | |||
42fb561866 | |||
95c8206eb7 | |||
26beced5c7 | |||
cf7a53354d | |||
653b1ce006 | |||
d69f60aced | |||
949f21f446 | |||
722c420dc6 | |||
590dd8316c | |||
376102c801 | |||
a243c96dd3 | |||
631f6bdb5c | |||
fc089f1b73 | |||
dfa93b378b | |||
0affe2c187 | |||
8231252f34 | |||
75faca6e8d | |||
4a6ae393a5 | |||
0e669bcc45 | |||
08fb37b982 | |||
bde954e570 | |||
78f6df704b | |||
7cf9be1342 | |||
86a650ff6d | |||
13adb6fb3a | |||
f2d54839c3 | |||
9abc9802bd | |||
82a03d428c | |||
0b5160b45d | |||
fca1fcf23c | |||
e311aa36f1 | |||
9b54c10bc6 | |||
eb62703e50 | |||
81062452e2 | |||
6e72912c90 | |||
52999f000e | |||
6999b0c7f9 | |||
e205a6b15a | |||
fbeb52bda2 | |||
465b216e01 | |||
ff622ea585 | |||
6a0179681b | |||
68adc86cbc | |||
e661005ff8 | |||
a861cff760 | |||
12e4052a9f | |||
e9441fe1d5 | |||
175a23934f | |||
a757bd02fa | |||
b03ec21a28 | |||
5f66df7e6a | |||
5a10841f84 | |||
2ef37ed58d | |||
828a42f938 | |||
4428f7b48b | |||
e855fc5bbd | |||
a7f0f442d8 | |||
2a424e7f6f | |||
50f537fbfc | |||
7ba78c94ae | |||
763fbcc86e | |||
3abca0ef22 | |||
7888a8ee24 | |||
abec798b27 | |||
eb9ac51b5a | |||
d86601e1a1 | |||
fd48f8c3ec | |||
fa28d6ce17 | |||
4250fda098 | |||
4f9e3dee01 | |||
e05f0b2c73 | |||
7c7a5fc1b7 | |||
a54abc6ea1 | |||
e4ed4d5567 | |||
29c4c107bc | |||
0b242aadc7 | |||
44a39e7e1f | |||
e82640c7da | |||
f89bf8859b | |||
6b5c432ce7 | |||
409a3bc6eb | |||
d406a09a9e | |||
36b045c6d2 | |||
59902adcf5 | |||
41842eede4 | |||
62c7cb1918 | |||
d23f6f4cc8 | |||
80f1fc3371 | |||
ea87137a82 | |||
aa8df36e28 | |||
0947a87d6c | |||
72c47a9b0d | |||
f0c9a800a5 | |||
ad5405e699 | |||
cc5c8b9ed5 | |||
04d8fd3d14 | |||
fb0a6a0a01 | |||
78059d1bf8 | |||
3352765fed | |||
f96166be48 | |||
3a878c9e90 | |||
7b8c3c7412 | |||
9f6751533e | |||
82e9b27598 | |||
237fff1351 | |||
e00c560c80 | |||
407e772fdb | |||
645437b5e3 | |||
7f6221cd8e | |||
d9c770855a | |||
70a7d6540c | |||
acf6678619 | |||
f2242c4e57 | |||
8fb7c76e2d | |||
72e78fd4bc | |||
2f1e065571 | |||
761c3730ed | |||
cdf7b926ba | |||
d3addb09c4 | |||
471203b0a6 | |||
7cac5b105e | |||
e8c3f27d93 | |||
97d2ed0d64 | |||
7278aab649 | |||
e33e634d0d | |||
92af5d8842 | |||
140f949190 | |||
c5d043f85b | |||
06cea02484 | |||
287b7413e3 | |||
3c330ac4f0 | |||
51bc126c8d | |||
edcd166cd8 | |||
aaead0e5ca | |||
990ef45e45 | |||
9ae607d802 | |||
3c07d8dcc4 | |||
bb8b8a9d50 | |||
08303e93ca | |||
01bdad6368 | |||
8145ef116f | |||
ab9ff96684 | |||
9858ac84c1 | |||
c6b876d7f9 | |||
05f1c62ad0 | |||
bf631efd24 | |||
f86e73b374 | |||
7cd85bbd34 | |||
38424bfdca | |||
9dde61e7a0 | |||
5fc1c78d1b | |||
2cbfc0b1ec | |||
5043378c2c | |||
92e743a67e | |||
9262e7fd13 | |||
5adfa8a1be | |||
0d64d5aa00 | |||
11e15f0482 | |||
07355d7442 | |||
e8392b8fa5 | |||
bfcf221a91 | |||
c29cbda581 | |||
5294a31e30 | |||
ed7ead95d5 | |||
aee6063410 | |||
45e78777e9 | |||
1de6a6288c | |||
4b7af32eb1 | |||
955c49a2c1 | |||
eeb5afe97b | |||
f5d818c91a | |||
0a38e422b8 | |||
f059faecbc | |||
195918f088 | |||
022e52e31c | |||
b249399b1d | |||
132d46fed5 | |||
49e16a3631 | |||
06c3b3f025 | |||
39e4340dee | |||
cb3a633807 | |||
b0bc11007f | |||
7781bcfe4b | |||
308ea2ce25 | |||
d66c7906c0 | |||
a0bf340378 | |||
1b8bb8a9ea | |||
25101b278a | |||
5c4c8c0fe9 | |||
7a861eabee | |||
b8f549a432 | |||
5a82498d60 | |||
890d004c3c | |||
c7c20cdacf | |||
3f2bc41176 | |||
70779f192c | |||
6fc121a314 | |||
29960cada8 | |||
6dc1c9e53b | |||
5b7b1527d5 | |||
6bfccca291 | |||
39c8933a3f | |||
5411843f9f | |||
88b0dc5215 | |||
2a8571d940 | |||
9882a99417 | |||
2f8fb874d8 | |||
5f229311f0 | |||
4a88f6cc56 | |||
151ec256fa | |||
37c90ed12b | |||
c21863c71a | |||
508fc8fd1c | |||
9f6b3160fc | |||
650de1d7e3 | |||
68ebfcd537 | |||
4cc870b1bb | |||
a15fb5719e | |||
6926ef3b9a | |||
76a61d5a01 | |||
f24c5da431 | |||
b9ab9582a8 | |||
f9426c9ed0 | |||
067cce1d7b | |||
b03445ea46 | |||
9d7f49dda9 | |||
dfd78be5b3 | |||
f0e2ec6a8a | |||
06ed30de3b | |||
5cfc6c573b | |||
ec940d428a | |||
4f593cb2d3 | |||
611fc1ba24 | |||
4f7249bfa4 | |||
8f8e468278 | |||
23916b753a | |||
e8ba629217 | |||
ef2c2917db | |||
6daed0f4a8 | |||
8624920e2c | |||
e303cccd2a | |||
88c4f2d8d0 | |||
41feee7a22 | |||
c0101f45b5 | |||
c49eb2e50f | |||
238af84a97 | |||
87d166c4a4 | |||
d05a3c4dc0 | |||
a2ccc2668a | |||
e93df29cc0 | |||
aafe5da783 | |||
35174fb849 | |||
1905d89a75 | |||
d566d6a315 | |||
08529d0d59 | |||
ce946b9aed | |||
f7aa5cd65c | |||
d9c0fd6d0b | |||
41db8d67df | |||
7a5985ba8f | |||
574e2c3761 | |||
90b5e3cdca | |||
b062d44b87 | |||
ca1c801d64 | |||
c3a6c61739 | |||
5a496f228a | |||
e5f8013034 | |||
a1581a6ea1 | |||
f81ce3f533 | |||
d3cece2424 | |||
7351798ea4 | |||
ccc7c27032 | |||
e17b34d98a | |||
8cb947a6e5 | |||
00b339954b | |||
2fa3ef4140 | |||
fe3e2dcb67 | |||
f9bd89af04 | |||
2742716de4 | |||
b3910ca837 | |||
b608d8c778 | |||
2e3e68bb6f | |||
787f97eeb2 | |||
9a7e8f6982 | |||
44af1dc552 | |||
8c9a033c67 | |||
0d6530fe8d | |||
b91cf53ac1 | |||
7e5cd343d2 | |||
b15f3ed396 | |||
385317eeff | |||
2aecbfd424 | |||
47d350470c | |||
4acb352edc | |||
ff99b14f6b | |||
51f8521ddd | |||
0fcfe0208d | |||
ed164cc64f | |||
c773927945 | |||
77e606cf12 | |||
dcfe5d44c7 | |||
1f5ad88a39 | |||
b4a1d207b7 | |||
5ea5a88560 | |||
98f230e14f | |||
c2fef2aac8 | |||
995eda0498 | |||
d1daa51391 | |||
9787edf857 | |||
926d4e80b5 | |||
12af93cd17 | |||
284da0fde1 | |||
dc5b7ab41a | |||
1d9c1a435b | |||
a3716371f8 | |||
b2700c9972 | |||
2cc5b597ec | |||
9d8744392a | |||
231b8bffaa | |||
c4a1c47176 | |||
222587f1f3 | |||
0c89a3eef9 | |||
90b3f21332 | |||
b802d7bd50 | |||
312d8b1b9c | |||
556bf1c264 | |||
c1f0d71bcb | |||
c9c4d351a1 | |||
7e779a0242 | |||
72dbf6ba27 | |||
8af1cab7ff | |||
ed30bc7d93 | |||
f41f268975 | |||
7aaf192386 | |||
c75fbe169c | |||
8481b69061 | |||
8af59ef676 | |||
9e5216ebc1 | |||
b6b798b548 | |||
4711b71b80 | |||
6b43172199 | |||
bdb728a817 | |||
7ef31a4c62 | |||
47deeb1893 | |||
dd6d03a521 | |||
ec36d4c39d | |||
f9f99798e4 | |||
9b93b2fc26 | |||
558236a6ac | |||
0f337c8fe9 | |||
11ca10cdfd | |||
5dcd756414 | |||
54866f958f | |||
e54ac4b84f | |||
d7da92f9e4 | |||
5adf82abc7 | |||
8dee7d0cfb | |||
143cbffd63 | |||
0b1f4b67f5 | |||
d096de8852 | |||
b1a67d8a80 | |||
87fb91cea5 | |||
4b1146c385 | |||
7b2b040cc9 | |||
5d8f81ab78 | |||
5cebe6c217 | |||
29a8265efd | |||
d5ede81ba7 | |||
2d60de24b6 | |||
7ede842828 | |||
99a814db94 | |||
f44ce0678d | |||
a3b9d0e37c | |||
606d2e0fed | |||
afa4d68563 | |||
0cd9854634 | |||
5e7ff39b04 | |||
27a083444f | |||
57548bd3c0 | |||
873d79c58a | |||
d7413372f8 | |||
0ed1ac7d1e | |||
f59b0f056b | |||
061270774c | |||
a5178ecace | |||
f52f52f0ae | |||
88a6ed17fc | |||
eb5a733f03 | |||
0b95fdc79d | |||
7a78e9d980 | |||
fd9c257e52 | |||
782073fa14 | |||
f73f3d62d9 | |||
cbe5d8ef71 | |||
777d8df76e | |||
a50e0fa4bc | |||
7370173811 | |||
9db430c1d1 | |||
3e977fa6e5 | |||
f4960e250b | |||
e8a2ada208 | |||
ff975005b4 | |||
f6a2377be9 | |||
3ff52002e4 | |||
15c2035064 | |||
b99405997a | |||
df6a18b3d9 | |||
ad3335e488 | |||
0644368cd6 | |||
adb9539ee3 | |||
904e47957d | |||
eeffb5b102 | |||
597be6faed | |||
21305e2e62 | |||
d9f28e4908 | |||
85c62a7bff | |||
0b72eb55b0 | |||
3299742898 | |||
94ea4a9d69 | |||
81d7a529ff | |||
4af3db82df | |||
c4c90e3c15 | |||
2eedc8dd60 | |||
111a4dd246 | |||
d57ede865f | |||
ef7abbf0d6 | |||
a885c8caab | |||
9cbc364a92 | |||
f5a989d977 | |||
32fa832a6e | |||
66f36a7531 | |||
c5b4251039 | |||
d26ed8e2b9 | |||
288af8b94e | |||
5d0671f445 | |||
95236a22ae | |||
715a768203 | |||
028932e53c | |||
42fb664228 | |||
fedf365400 | |||
0f0ea9f2c4 | |||
e19fc2839e | |||
83de328c2b | |||
b6a4a35bfa | |||
b0f62e5015 | |||
aa457177c3 | |||
1c4c21b085 | |||
e276729f1d | |||
b882404fe3 | |||
e73471e10a | |||
07ba2b7718 | |||
6f1742d412 | |||
91b72ed3a2 | |||
36e6a92fb5 | |||
8b0bad54cc | |||
0be164c99f | |||
8fa502b2c4 | |||
494e4656ef | |||
033a6516cf | |||
bfe7293619 | |||
7ecb53a701 | |||
a0707554bb | |||
d656ec90b0 | |||
c70aa29523 | |||
a3c4f2158b | |||
035801dc7e | |||
08b244cbc4 | |||
a29c10099e | |||
eb857e3766 | |||
51453d6191 | |||
90bc10b52a | |||
32ea4a28cc | |||
a0019df507 | |||
2bed4c656a | |||
f394ef236a | |||
37c6b70f7e | |||
f0607403c8 | |||
af75fe5236 | |||
b20d3c1726 | |||
35b2b2d173 | |||
c723b962bf | |||
84b5287d73 | |||
aee9c11551 | |||
a173feb26f | |||
6d32d6b5bd | |||
8ab9fca0e1 | |||
7ea7e5be5c | |||
9e43398ab2 | |||
17ce8759ac | |||
3fb33c3a3d | |||
c5c17c1659 | |||
03a5d251a1 | |||
fb2f201f29 | |||
48ce8bf3db | |||
e770b13320 | |||
900e5b1b2e | |||
a14a27a920 | |||
ed2f638bf2 | |||
2ea8830ecf | |||
34cc60ace9 | |||
4d08db4874 | |||
d222f32674 | |||
4f5fa9b708 | |||
d5d2ec0554 | |||
60b85d2221 | |||
895d9978a0 | |||
f43ccbfd50 | |||
0b1256d571 | |||
3dc579f176 | |||
dc7ffd0321 | |||
912433a032 | |||
3edf79b035 | |||
b5dafdb173 | |||
10f843430d | |||
96ded0a079 | |||
31891cafa3 | |||
5639b16594 | |||
330755f78c | |||
bb34fa1ded | |||
01a34e082c | |||
8c3597be4b | |||
3fec7e3562 | |||
c11674178e | |||
5e316bc778 | |||
a962c04213 | |||
8c29e2c51d | |||
d5ee78a0ff | |||
7616498fa6 | |||
2f042e68dd | |||
c0c0f1925b | |||
a14bb9d6b8 | |||
82046dcbee | |||
714fa5d91a | |||
04d854c669 | |||
fb56fab0c7 | |||
d52e58ec74 | |||
be23ac7285 | |||
e1e56daac0 | |||
21d45aa6b6 | |||
8d7a7d77e6 | |||
86ef087310 | |||
0775470b49 | |||
f63d15e0a3 | |||
969824c029 | |||
3899aee53c | |||
e92e96b828 | |||
6e009e84d0 | |||
1405e9728f | |||
1c44fc970b | |||
9594b9de50 | |||
96237553bd | |||
49f55fc751 | |||
fa271c5445 | |||
b61bac1494 | |||
603de740a3 | |||
4134359406 | |||
f135d3776d | |||
d031e31c0c | |||
6b8783b830 | |||
9a7f9296d0 | |||
aabf116a3a | |||
e5b6115b54 | |||
837a266d74 | |||
4b11f2ddfa | |||
c5fd43d150 | |||
856bd7d81a | |||
35579453a6 | |||
bb289549c8 | |||
c4b3579e08 | |||
7c2eb8ad4c | |||
d0360c01e5 | |||
e4ab8be82f | |||
794a40a11e | |||
6070d9b52f | |||
1cd4f9e9b8 | |||
d845e7c8e4 | |||
a965265977 | |||
0172dea15f | |||
0e403e6f09 | |||
cd9964db46 | |||
2e2515586e | |||
f5848c7b8e | |||
9c094e6efe | |||
8e5aba04e2 | |||
42842b52b5 | |||
403bef7920 | |||
52f8cbd55d | |||
9790a1ef84 | |||
3f58be9f8c | |||
7c6ec56fa3 | |||
80ff22c02b | |||
4095fcf888 | |||
b23aeea53a | |||
73f72ce959 | |||
6134ef3293 | |||
7d84c7d09e | |||
9fec6eb6ef | |||
35f23d46eb | |||
31473c6537 | |||
971408cecd | |||
3434185dbe | |||
b6c4fd6675 | |||
64f197866a | |||
f3fb49bd3d | |||
c849418e2f | |||
8c1b3014a6 | |||
e3b9525091 | |||
a3c554aab8 | |||
27ce9873ec | |||
dbf44d488a | |||
457ad1032a | |||
bc8e3c34a8 | |||
b38f216409 | |||
7f30796d60 | |||
6a9e89c73a | |||
62894bdf64 | |||
540b3b9e2a | |||
d4b14b13a7 | |||
924884dfd0 | |||
be3903d2eb | |||
0d58349432 | |||
25b1a6cd5b | |||
fcdda9269d | |||
d5dcc38cc9 | |||
6f3fe2eb23 | |||
49664d8dfc | |||
03eb1ecb38 | |||
47ff546182 | |||
d3aac7476e | |||
9d801d2f83 | |||
a1317ec130 | |||
ea6344e37a | |||
075427c37c | |||
f67bb8e15c | |||
d558667586 | |||
55cab197cc | |||
ec44825c13 | |||
472d349689 | |||
dd3fb09da2 | |||
96399df765 | |||
8a2a7c7ba7 | |||
cf34009205 | |||
03aaf7f650 | |||
b3d2ead151 | |||
4965cd5360 | |||
9a694ba263 | |||
e75e6897fd | |||
5f667f733f | |||
bebcd45647 | |||
0e626a3a62 | |||
762eb0be14 | |||
5a5f89cd52 | |||
6afef2915b | |||
610974e1c9 | |||
88183ac770 | |||
2b3a728d91 | |||
88bcc4c3b5 | |||
1ee1e306fd | |||
7c97b70873 | |||
3be600a070 | |||
74d0e99a1a | |||
ac8337fe4b | |||
578b1c7313 | |||
76d53b1001 | |||
deef9aad5e | |||
02668ed843 | |||
361d90cae0 | |||
3336664b70 | |||
df1028acbc | |||
56c0f8bd36 | |||
d75787c21a | |||
2a89916681 | |||
b7156566ce | |||
e188110cb2 | |||
574a371ba4 | |||
d33824a508 | |||
31d0f843f7 | |||
096f7027a2 | |||
db33c6b26d | |||
c977357b25 | |||
40cdf1c64e | |||
638892fd14 | |||
1d29ec9f81 | |||
16d221913d | |||
f3760a0dcd | |||
73c7a6eec6 | |||
c5347ae4fe | |||
ae6c2eee6b | |||
29d58d3a45 | |||
0900d81dfd | |||
f48a16e954 | |||
01b4cf5f3a | |||
88de0ba17c | |||
7dd8a3189a | |||
ed840793b4 | |||
0004afe9bb | |||
711f3a696b | |||
9156bdbb9d | |||
139eaf998e | |||
974c9c95e9 | |||
87d63f04ae | |||
eb9374f48d | |||
531198c81f | |||
6d92ea3979 | |||
2eae85d950 | |||
9045e85484 | |||
8c2108b3ab | |||
f731b6c79c | |||
2656435d00 | |||
680e13840d | |||
135af7bfda | |||
ccdde872f4 | |||
3e13e787a1 | |||
50b7ab616b | |||
4c416d5fbf | |||
091bdf267a | |||
48b0fd27f6 | |||
aec61c390d | |||
8513160cc7 | |||
1e0af118d1 | |||
3c5511a28a | |||
a8c8eab984 | |||
b22b0f663c | |||
c97bf15218 | |||
8b985f67fe | |||
953c7b7e4e | |||
c58cee5fa7 | |||
4fc67df95e | |||
dfc842b2ed | |||
22ecda6411 | |||
237531fd96 | |||
39b7a56491 | |||
f38f05e15b | |||
2fb84174dd | |||
8001582c2b | |||
cda05accb5 | |||
2faac66509 | |||
df5cd45da1 | |||
e880368825 | |||
11fab705fa | |||
786d4cb476 | |||
209d89582e | |||
6cd10dd9bf | |||
250787391e | |||
a62a41cbc4 | |||
ea604b76ba | |||
1e3f51db10 | |||
4a8613f68b | |||
e4a048fd2d | |||
f45fae30ee | |||
9e7253b0df | |||
a75e6902f8 | |||
ad04da9051 | |||
07c5ae827d | |||
c6ef352b9b | |||
b945191dfc | |||
01027bae68 | |||
6334667767 | |||
b55bd0119a | |||
975dc35059 | |||
b710c1216b | |||
65dae9cd5b | |||
e924e1570b | |||
7e9de15870 | |||
fcaccaaa5b | |||
1f26e468c9 | |||
87232bf5dd | |||
cbf1078b6c | |||
f9af8bf321 | |||
37d47a5adc | |||
ff7ee786c3 | |||
c1a6cd9220 | |||
f32b0291e8 | |||
3d61bf5e20 | |||
31bbb61d5f | |||
7017859b1f | |||
fb90965444 | |||
86722da61f | |||
6ebc378811 | |||
c44371492d | |||
9c9136f7f1 | |||
fc5ceb2187 | |||
fd5ae2e284 | |||
70c1a4e384 | |||
a066aa977e | |||
c19b38a2df | |||
f95f0eb749 | |||
27ff11597b | |||
d37825eef2 | |||
9f5fba25e3 | |||
b7d61e714a | |||
7572d350ef | |||
4f66b7410e | |||
e6725d3b63 | |||
c2f6bb5a7c | |||
c81f3d5128 | |||
73c7a0a654 | |||
9c52832323 | |||
07f11c79e9 | |||
11519f76d1 | |||
10afa1c172 | |||
44474c9ac8 | |||
0d41632c05 | |||
d344f02484 | |||
e540eabfc6 | |||
a88d01b317 | |||
49307f42eb | |||
b8993685f0 | |||
305d1d4679 | |||
791da3a5c3 | |||
bd3db40a18 | |||
53b6405068 | |||
4ee7402f2c | |||
d77cf15690 | |||
63076678d6 | |||
4a78062d89 | |||
52be1d6928 | |||
a4d31faa3a | |||
cef0d48ff1 | |||
25eba79a6b | |||
ea53940cca | |||
f6707547f9 | |||
6f0a009200 | |||
1691f38368 | |||
4e8d4ec9da | |||
1910d93b9f | |||
803e0a7d03 | |||
665909e02f | |||
279bc1ca60 | |||
40069e9652 | |||
c806c8278e | |||
6b7ce2e4b4 | |||
866d34d538 | |||
d0ad41148b | |||
69f0c6fcc8 | |||
9f992b0cba | |||
eee964346a | |||
8335d35c39 | |||
5112a28775 | |||
378ca84d15 | |||
079e027ba3 | |||
65fe2d1838 | |||
9b1c98c307 | |||
df802d0c0b | |||
800751d015 | |||
183aab6f84 | |||
08f94b254f | |||
45b8ae13ae | |||
e5a67eeee9 | |||
885b957d31 | |||
db80c7c381 | |||
7a57101ace | |||
cf8a7fbc66 | |||
0c032efbd8 | |||
6b14415976 | |||
c22f9a5c6a | |||
0a209f342a | |||
bd9740c419 | |||
ada1374ade | |||
3db685f0c9 | |||
671bd18ee6 | |||
4e2ae18aff | |||
5568663f46 | |||
24cd54f23c | |||
8427f58b2f | |||
93d902f235 | |||
fbd8fa52d2 | |||
fe2e06e637 | |||
4a7f935272 | |||
cf876c730f | |||
c9b5a28f3a | |||
2a51fdf0b2 | |||
20aa5c2e9c | |||
010d9add36 | |||
2a0ed49314 | |||
ba7228cca1 | |||
1109888366 | |||
fd8d5c759c | |||
ea478ec500 | |||
b3e236e111 | |||
b41f7b25c6 | |||
9964032b88 | |||
fa4fe6dddf | |||
62eb69e491 | |||
7b47726aee | |||
114d508c97 | |||
8e3416e24c | |||
5f8287ad36 | |||
f96e9b97b8 | |||
9ab5f5fb9d | |||
5da3079c13 | |||
784eeea4a1 | |||
b0eea0bf3a | |||
da9f8ad885 | |||
b20e9899b1 | |||
4fcfaf1ec7 | |||
488c5f3aa8 | |||
c43f5dd3d5 | |||
7fd2c29c71 | |||
3641fc941a | |||
70f4213a8c | |||
3a84392b79 | |||
9dcf63866b | |||
4908fed6e4 | |||
037b91479e | |||
43f2b2b36b | |||
ca833780c5 | |||
b0dfb2e4a8 | |||
f399dc85ec | |||
dbe7aab8f9 | |||
5ff9620028 | |||
0f5811cdd9 | |||
0805649c58 | |||
5856f42397 | |||
ea9e41721a | |||
82f4bf5244 | |||
5215673f81 | |||
6bf78974ae | |||
ef29a10564 | |||
5b944d8168 | |||
06e1098ef2 | |||
086e25ee76 | |||
0f3f94239f | |||
28a74d1146 | |||
20517cdfe2 | |||
91cd6a88c5 | |||
4f2316a4b5 | |||
f08250eedd | |||
0c8731cd54 | |||
3565c0dad3 | |||
310247bd48 | |||
f2c5bf33f5 | |||
bba32b2ff3 | |||
dcb271e0ba | |||
fe0a4d7fd7 | |||
3e271bb1cf | |||
4619176c86 | |||
bd2aa92fe4 | |||
81819b9f9e | |||
8f93b8f52c | |||
abd94108d7 | |||
d2ed273b50 | |||
a6d4fa458e | |||
cdb24de481 | |||
cc4b931bf5 | |||
f7e100c06e | |||
3956d1abb7 | |||
88175f5cae | |||
b2dc1db8a6 | |||
b42ffa5339 | |||
2da9b3dd66 | |||
091977b748 | |||
8a97f31a3a | |||
e82980f404 | |||
69f1c64118 | |||
8caf8ceb82 | |||
7d02bb0fc0 | |||
92bf6a4011 | |||
cdf64d9c2b | |||
9997589885 | |||
4d8392c5ed | |||
5acab38c53 | |||
3f9a952862 |
15
.editorconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
# Trailing whitespace is significant in markdown files.
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
14
.esdoc.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"source": "src",
|
||||
"destination": "docs/docs",
|
||||
"plugins": [{
|
||||
"name": "esdoc-standard-plugin",
|
||||
"option": {
|
||||
"lint": {"enable": false},
|
||||
"accessor": {
|
||||
"access": ["public", "protected"],
|
||||
"autoPrivate": true
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
37
.eslintrc
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": { "modules": true }
|
||||
},
|
||||
"rules": {
|
||||
"max-len": [2, 80, 2, {"ignoreUrls": true}],
|
||||
"indent": [2, 4, {"SwitchCase": 1}],
|
||||
"semi": ["error", "always"],
|
||||
"no-trailing-spaces": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"array-bracket-spacing": 2,
|
||||
"keyword-spacing": ["error", { "after": true, "before": true }],
|
||||
"max-depth": [2, 7],
|
||||
"max-statements": [2, 133],
|
||||
"complexity": [2, 45],
|
||||
"no-unused-vars": 2,
|
||||
"no-eval": 2,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-loop-func": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"curly": 2,
|
||||
"eqeqeq": [2, "smart"],
|
||||
"quotes": [2, "single"],
|
||||
"new-cap": 2,
|
||||
"radix": [2, "always"]
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"node": false
|
||||
},
|
||||
"globals": {
|
||||
"__webpack_public_path__": false
|
||||
}
|
||||
}
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Reproduction steps:**
|
||||
Steps to reproduce the behavior:
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [Other Steps...]
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Observed behavior:**
|
||||
A clear and concise description of observed behavior.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
![Screenshots and GIFs which follow reproduction steps to demonstrate the problem](url)
|
||||
|
||||
**TableFilter version:** [Enter TableFilter version here]
|
||||
**Browser and version:** [Enter Browser name and version here]
|
||||
**OS and version:** [Enter OS name and version here]
|
||||
**Device:** [e.g. iPhone6]
|
||||
|
||||
**Additional information/context:**
|
||||
Add any other context about the problem here.
|
||||
|
||||
* Problem started happening recently, didn't happen in an older version of TableFilter: [Yes/No] if yes [version here]
|
||||
* Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
|
36
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Enhancement/feature description**
|
||||
[A clear and concise description of what the problem is. Ex. I'm always frustrated when ...]
|
||||
|
||||
**Steps which explain the enhancement/feature**
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [Other Steps...]
|
||||
|
||||
**Current and suggested behavior**
|
||||
[Describe current and suggested behavior here]
|
||||
|
||||
**Why would the enhancement be useful to most users**
|
||||
[Explain why the enhancement would be useful to most users]
|
||||
|
||||
**Screenshots and GIFs**
|
||||
![Screenshots and GIFs which demonstrate the steps or part of TableFilter the enhancement suggestion is related to](url)
|
||||
|
||||
**Additional context**
|
||||
[Add any other context or screenshots about the feature request here.]
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**TableFilter Version:** [Enter TableFilter version here]
|
||||
**Browser and version:** [Enter Browser name and version here]
|
||||
**OS and version:** [Enter OS name and version here]
|
||||
**Device:** [e.g. iPhone6]
|
17
.github/ISSUE_TEMPLATE/support.md
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: Support
|
||||
about: I need support with TableFilter
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
For usage and support questions, please check out resources below, you might find an answer:
|
||||
|
||||
- https://www.tablefilter.com/
|
||||
- https://www.tablefilter.com/docs/
|
||||
- https://www.tablefilter.com/examples.html
|
||||
- https://github.com/koalyptus/TableFilter/wiki/
|
||||
- https://github.com/koalyptus/TableFilter/issues?q=is%3Aissue+is%3Aclosed
|
||||
- https://codepen.io/koalyptus/pens/public/
|
4
.gitignore
vendored
|
@ -1,8 +1,10 @@
|
|||
demos
|
||||
docs
|
||||
node_modules
|
||||
report
|
||||
.grunt
|
||||
|
||||
npm-debug.log
|
||||
*.js.map
|
||||
.codio
|
||||
.settings
|
||||
.vscode
|
||||
|
|
17
.jshintrc
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"-W024": true,
|
||||
"scripturl": true,
|
||||
"evil": true,
|
||||
"curly": true,
|
||||
"indent": 4,
|
||||
"es3": true,
|
||||
"esnext": true,
|
||||
"unused": true,
|
||||
"maxlen" : 80,
|
||||
"trailing": true,
|
||||
"quotmark": "single",
|
||||
"immed": true,
|
||||
"maxstatements": 197,
|
||||
"maxdepth": 7,
|
||||
"maxcomplexity": 103
|
||||
}
|
46
.travis.yml
|
@ -1,13 +1,53 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '0.10'
|
||||
- '8.9.4'
|
||||
before_script:
|
||||
- npm install grunt-cli -g
|
||||
- npm install grunt-cli -g
|
||||
- npm install codecov -g
|
||||
script:
|
||||
- grunt test deploy
|
||||
- npm test
|
||||
- npm run codecov
|
||||
- npm run build:all
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^greenkeeper/.*$/
|
||||
before_deploy:
|
||||
- git config --global user.email "maxgug@hotmail.com"
|
||||
- git config --global user.name "koalyptus"
|
||||
- export GIT_TAG=$(git describe --tags --abbrev=0)
|
||||
- echo -n $GIT_TAG
|
||||
- export NEW_GIT_TAG=$(node -p -e "require('./package.json').version")
|
||||
- echo -n $NEW_GIT_TAG
|
||||
- |
|
||||
if [ $GIT_TAG != $NEW_GIT_TAG ]; then
|
||||
git tag $NEW_GIT_TAG -am "Generated tag from TravisCI build $TRAVIS_BUILD_NUMBER"
|
||||
fi
|
||||
deploy:
|
||||
- provider: releases
|
||||
on:
|
||||
branch: master
|
||||
api_key: $GITHUB_OAUTH_ACCESS_TOKEN
|
||||
skip_cleanup: true
|
||||
name: $NEW_GIT_TAG
|
||||
- provider: s3
|
||||
on:
|
||||
branch: master
|
||||
access_key_id: $AWS_ACCESS_KEY_ID
|
||||
secret_access_key: $AWS_SECRET_ACCESS_KEY
|
||||
region: $AWS_DEFAULT_REGION
|
||||
bucket: www.tablefilter.com
|
||||
skip_cleanup: true
|
||||
local_dir: dist
|
||||
- provider: s3
|
||||
on:
|
||||
branch: master
|
||||
access_key_id: $AWS_ACCESS_KEY_ID
|
||||
secret_access_key: $AWS_SECRET_ACCESS_KEY
|
||||
region: $AWS_DEFAULT_REGION
|
||||
bucket: www.tablefilter.com
|
||||
skip_cleanup: true
|
||||
local_dir: docs
|
||||
env:
|
||||
global:
|
||||
- secure: A1G8GvJkV0rjy7XCTVdOpTHy3xaoSZZAbMWhI+ikrqBqd8mRz+sB71FhRusouTcYdsT5VfF9Io2doS8LKAeP0TNC34Pp0uvjtsvarzn8a/oNEOuqR3Ub0ws2bmbZIZc+wOpgErKOj1H1QSJAUpd6ZjIuEAbOVXlhGBJz3zUCmcpRDh32CpFKC62oFWeGlvttxPciLLzBfKgkVKEGhPtdGP/xCHL1MCQptYVHZiXwWsaIQ5wHFO6KCVlRrPgdfOL+Yce3mT02iXH6ZjW6U6zA6vYQVQZVD873AkU5RmirYblW+jW1wdvu4UXI71lSH6Z3uXRVnrw1b0TsLVTjP9ZUbCtkTHtLbxYzeDjEukxKoCjpAppIhOtaNIxrdA8oKJAabQYp5X+QK6lkosy0zdT5u2B1+g8unZhsf0y//7lgLUe04iQ7sc1Q6AHiiEGtByaXg4BHNW53bUfKgNnbV4+IbXf8rz5wWOxL2/yWAU/GoiszjqRQfajAXCpSf6SyMjXjhhvQdeFn+Cz6FwdtaxH+tOIY0Hq9Gqy1xrLIkv/httd3O+AbhLrU1c/M0MwlFQue7GeJb7ZyF3KsK7bXvoz2dEqvzHd98NZXiQEqFXCIs77uVh4eZMoYrbEyrkOAgkUZNQYhHh9fuvfynJ/zgUvyA0v3GUvBebq3ybYKD/vqX7s=
|
||||
|
|
128
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,128 @@
|
|||
# Contributing to TableFilter
|
||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||
|
||||
## Reporting bugs
|
||||
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/).
|
||||
Create an issue and provide the following information.
|
||||
|
||||
Explain the problem and include additional details to help maintainers reproduce
|
||||
the problem:
|
||||
|
||||
* **Use a clear, concise and descriptive title** for the issue to identify the
|
||||
problem.
|
||||
* **Describe the exact steps which reproduce the problem** in as many details
|
||||
as possible.
|
||||
* **Provide specific examples to demonstrate the steps**. Include links to
|
||||
files or GitHub projects, or copy/pasteable snippets, which you use in those
|
||||
examples. If you're providing snippets in the issue, use
|
||||
[Markdown code blocks](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown).
|
||||
Do not paste large snippets completely unrelated to the issue.
|
||||
* **Describe the behavior you observed after following the steps** and point
|
||||
out what exactly is the problem with that behavior.
|
||||
* **Explain which behavior you expected to see instead and why.**
|
||||
* Do not hesitate to **include screenshots and animated GIFs** which show you
|
||||
following the described steps and clearly demonstrate the problem.
|
||||
You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS
|
||||
and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or
|
||||
[this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
|
||||
### Template for submitting bug reports
|
||||
|
||||
[Short description of problem here]
|
||||
|
||||
**Reproduction steps:**
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [Other Steps...]
|
||||
|
||||
**Expected behavior:**
|
||||
|
||||
[Describe expected behavior here]
|
||||
|
||||
**Observed behavior:**
|
||||
|
||||
[Describe observed behavior here]
|
||||
|
||||
**Screenshots and GIFs**
|
||||
|
||||
![Screenshots and GIFs which follow reproduction steps to demonstrate the
|
||||
problem](url)
|
||||
|
||||
**TableFilter version:** [Enter TableFilter version here]
|
||||
**Browser and version:** [Enter Browser name and version here]
|
||||
**OS and version:** [Enter OS name and version here]
|
||||
|
||||
**Additional information:**
|
||||
|
||||
* Problem started happening recently, didn't happen in an older version of
|
||||
TableFilter: [Yes/No]
|
||||
* Problem can be reliably reproduced, doesn't happen randomly: [Yes/No]
|
||||
|
||||
## Suggesting enhancements and features
|
||||
Enhancement and feature suggestions are tracked as
|
||||
[GitHub issues](https://guides.github.com/features/issues/).
|
||||
Create an issue and provide the following information:
|
||||
|
||||
* **Use a clear and descriptive title** for the issue to identify the
|
||||
suggestion.
|
||||
* **Provide a step-by-step description of the suggested enhancement/feature**
|
||||
in as many details as possible.
|
||||
* When applicable **describe the current behavior** and
|
||||
**explain which behavior you expected to see instead** and why.
|
||||
* **Include screenshots and animated GIFs** which help you demonstrate the
|
||||
steps or point out the part of TableFilter which the suggestion is related to.
|
||||
You can use [this tool](http://www.cockos.com/licecap/) to record GIFs on macOS
|
||||
and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or
|
||||
[this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
* **Explain why this enhancement would be useful** to most TableFilter users.
|
||||
|
||||
### Template for submitting enhancement and feature suggestions
|
||||
|
||||
[Short description of suggestion]
|
||||
|
||||
**Steps which explain the enhancement/feature**
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [Other Steps...]
|
||||
|
||||
**Current and suggested behavior**
|
||||
|
||||
[Describe current and suggested behavior here]
|
||||
|
||||
**Why would the enhancement be useful to most users**
|
||||
|
||||
[Explain why the enhancement would be useful to most users]
|
||||
|
||||
**Screenshots and GIFs**
|
||||
|
||||
![Screenshots and GIFs which demonstrate the steps or part of TableFilter
|
||||
the enhancement suggestion is related to](url)
|
||||
|
||||
**TableFilter Version:** [Enter TableFilter version here]
|
||||
**Browser and version:** [Enter Browser name and version here]
|
||||
**OS and version:** [Enter OS name and version here]
|
||||
|
||||
## Code contribution
|
||||
TableFilter welcomes contributions from anyone and everyone. If you want to get
|
||||
your hands dirty:
|
||||
|
||||
### Pull requests
|
||||
In general, we follow the "fork-and-pull" Git workflow.
|
||||
|
||||
1. **Fork** the repo on GitHub
|
||||
2. **Clone** the project to your own machine
|
||||
3. **Commit** changes to your own branch
|
||||
- 3.1. **Add** unit tests covering your change in the `test` folder
|
||||
- 3.2. **Run** the tests with the `npm run eslint test` command
|
||||
- 3.3. **Ensure** build is working with `npm run dist` command
|
||||
4. **Push** your work back up to your fork
|
||||
5. Submit a **Pull request** so that we can review your changes
|
||||
|
||||
NOTE: Be sure to merge the latest from "upstream" before making a pull request!
|
||||
|
||||
## Donations
|
||||
Support this project by donating [here](http://www.tablefilter.com/donate.html).
|
||||
Thanks to all those who supported this project in the past and those who will
|
||||
in the future!
|
311
Gruntfile.js
|
@ -1,28 +1,26 @@
|
|||
module.exports = function (grunt) {
|
||||
|
||||
var webpack = require('webpack');
|
||||
var webpackConfig = require('./webpack.config.js');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var testDir = 'test';
|
||||
var testHost = 'http://localhost:8000/';
|
||||
var pkg = grunt.file.readJSON('package.json');
|
||||
var repo = 'github.com/koalyptus/TableFilter';
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
jshint: {
|
||||
src: [
|
||||
'Gruntfile.js',
|
||||
'webpack.config.js',
|
||||
'src/**/*.js'
|
||||
],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
},
|
||||
|
||||
qunit: {
|
||||
options: {
|
||||
'--web-security': 'no',
|
||||
coverage: {
|
||||
disposeCollector: true,
|
||||
src: ['dist/tablefilter/*.js'],
|
||||
instrumentedFiles: 'report/temp/',
|
||||
htmlReport: 'report/coverage',
|
||||
coberturaReport: 'report/',
|
||||
lcovReport: 'report/',
|
||||
jsonReport: 'report',
|
||||
linesThresholdPct: 80
|
||||
}
|
||||
},
|
||||
all: {
|
||||
options: {
|
||||
urls: getTestFiles(testDir, testHost)
|
||||
|
@ -82,21 +80,21 @@ module.exports = function (grunt) {
|
|||
{
|
||||
pattern: /{NAME}/ig,
|
||||
replacement: pkg.name
|
||||
},{
|
||||
}, {
|
||||
pattern: /{VERSION}/ig,
|
||||
replacement: pkg.version
|
||||
},{
|
||||
}, {
|
||||
pattern: /{EZEDITTABLE_LINK}/ig,
|
||||
replacement: '<a href="http://edittable.free.fr/' +
|
||||
'zip.php?f=ezEditTable.zip&p=1"' +
|
||||
'target="_blank" title="ezEditTable is a ' +
|
||||
'javascript code aimed at enhancing regular ' +
|
||||
'HTML tables by adding features such as ' +
|
||||
'inline editing components, advanced ' +
|
||||
'selection and keyboard navigation ' +
|
||||
'- Developed by '+ pkg.author.name +'">' +
|
||||
'ezEditTable</a>'
|
||||
},{
|
||||
'zip.php?f=ezEditTable.zip&p=1"' +
|
||||
'target="_blank" title="ezEditTable is a ' +
|
||||
'javascript code aimed at enhancing regular ' +
|
||||
'HTML tables by adding features such as ' +
|
||||
'inline editing components, advanced ' +
|
||||
'selection and keyboard navigation ' +
|
||||
'- Developed by ' + pkg.author.name + '">' +
|
||||
'ezEditTable</a>'
|
||||
}, {
|
||||
pattern: /<!-- @import (.*?) -->/ig,
|
||||
replacement: function (match, p1) {
|
||||
return grunt.file.read('static/' + p1);
|
||||
|
@ -109,26 +107,6 @@ module.exports = function (grunt) {
|
|||
|
||||
clean: ['demos/starter.html'],
|
||||
|
||||
'webpack-dev-server': {
|
||||
options: {
|
||||
webpack: webpack.dev,
|
||||
publicPath: '/dist/'
|
||||
},
|
||||
start: {
|
||||
keepAlive: true,
|
||||
webpack: {
|
||||
devtool: 'eval',
|
||||
debug: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
webpack: {
|
||||
options: webpackConfig,
|
||||
build: webpackConfig.build,
|
||||
dev: webpackConfig.dev
|
||||
},
|
||||
|
||||
watch: {
|
||||
app: {
|
||||
files: ['src/**/*', 'static/style/**/*'],
|
||||
|
@ -146,201 +124,79 @@ module.exports = function (grunt) {
|
|||
}
|
||||
},
|
||||
|
||||
babel: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
modules: 'amd',
|
||||
compact: false
|
||||
// temporary shell commands while decommissioning grunt
|
||||
shell: {
|
||||
eslint: {
|
||||
command: 'npm run lint'
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'src',
|
||||
src: ['**/*.js'],
|
||||
dest: 'dist/tablefilter'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
esdoc: {
|
||||
dist: {
|
||||
options: {
|
||||
source: 'src',
|
||||
destination: 'docs/docs',
|
||||
title: pkg.name + ' v' + pkg.version
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stylus: {
|
||||
compile: {
|
||||
options: {
|
||||
banner: '/** \n' +
|
||||
' *\t '+ pkg.name +' v'+ pkg.version +
|
||||
' by '+ pkg.author.name +' \n' +
|
||||
' *\t build date: '+ new Date().toISOString() +' \n' +
|
||||
' *\t MIT License \n' +
|
||||
' */ \n'
|
||||
},
|
||||
files: [
|
||||
{
|
||||
src: ['static/style/*.styl'],
|
||||
dest: 'dist/tablefilter/style/tablefilter.css'
|
||||
},{
|
||||
src: ['static/style/extensions/colsVisibility.styl'],
|
||||
dest: 'dist/tablefilter/style/colsVisibility.css'
|
||||
},{
|
||||
src: ['static/style/extensions/filtersVisibility.styl'],
|
||||
dest: 'dist/tablefilter/style/filtersVisibility.css'
|
||||
},{
|
||||
src: ['static/style/themes/default/*.styl'],
|
||||
dest:
|
||||
'dist/tablefilter/style/themes/default/default.css'
|
||||
},{
|
||||
src: ['static/style/themes/mytheme/*.styl'],
|
||||
dest:
|
||||
'dist/tablefilter/style/themes/mytheme/mytheme.css'
|
||||
},{
|
||||
src: ['static/style/themes/skyblue/*.styl'],
|
||||
dest:
|
||||
'dist/tablefilter/style/themes/skyblue/skyblue.css'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
'gh-pages': {
|
||||
options: {
|
||||
branch: 'gh-pages',
|
||||
add: true
|
||||
esdoc: {
|
||||
command: 'npm run esdoc'
|
||||
},
|
||||
'publish-lib': {
|
||||
options: {
|
||||
base: 'dist',
|
||||
repo: 'https://' + repo,
|
||||
message: 'publish TableFilter lib to gh-pages (cli)'
|
||||
},
|
||||
src: ['**/*']
|
||||
build: {
|
||||
command: 'npm run build'
|
||||
},
|
||||
'publish-readme': {
|
||||
options: {
|
||||
base: './',
|
||||
repo: 'https://' + repo,
|
||||
message: 'publish README to gh-pages (cli)'
|
||||
},
|
||||
src: ['README.md']
|
||||
dev: {
|
||||
command: 'npm run dev'
|
||||
},
|
||||
'publish-docs': {
|
||||
options: {
|
||||
base: 'docs',
|
||||
repo: 'https://' + repo,
|
||||
message: 'publish Docs to gh-pages (cli)'
|
||||
},
|
||||
src: ['**/*']
|
||||
test: {
|
||||
command: 'npm run build:test'
|
||||
},
|
||||
'deploy-lib': {
|
||||
options: {
|
||||
user: {
|
||||
name: 'koalyptus'
|
||||
},
|
||||
base: 'dist',
|
||||
repo: 'https://' + process.env.GH_TOKEN + '@' + repo,
|
||||
message: 'publish TableFilter to gh-pages (auto)' +
|
||||
getDeployMessage(),
|
||||
silent: true
|
||||
},
|
||||
src: ['**/*']
|
||||
},
|
||||
'deploy-readme': {
|
||||
options: {
|
||||
user: {
|
||||
name: 'koalyptus'
|
||||
},
|
||||
base: './',
|
||||
repo: 'https://' + process.env.GH_TOKEN + '@' + repo,
|
||||
message: 'publish README to gh-pages (auto)' +
|
||||
getDeployMessage(),
|
||||
silent: true
|
||||
},
|
||||
src: ['README.md']
|
||||
},
|
||||
'deploy-docs': {
|
||||
options: {
|
||||
user: {
|
||||
name: 'koalyptus'
|
||||
},
|
||||
base: 'docs',
|
||||
repo: 'https://' + process.env.GH_TOKEN + '@' + repo,
|
||||
message: 'publish Docs to gh-pages (auto)' +
|
||||
getDeployMessage(),
|
||||
silent: true
|
||||
},
|
||||
src: ['**/*']
|
||||
'build-css': {
|
||||
command: 'npm run build:css'
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-qunit');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-string-replace');
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-esdoc');
|
||||
grunt.loadNpmTasks('grunt-contrib-stylus');
|
||||
grunt.loadNpmTasks('grunt-gh-pages');
|
||||
grunt.loadNpmTasks('grunt-shell');
|
||||
grunt.loadNpmTasks('grunt-qunit-istanbul');
|
||||
|
||||
grunt.registerTask('default', ['build', 'test', 'build-demos']);
|
||||
grunt.registerTask('eslint', ['shell:eslint']);
|
||||
grunt.registerTask('esdoc', ['shell:esdoc']);
|
||||
|
||||
// Development server
|
||||
grunt.registerTask('server', ['webpack-dev-server:start']);
|
||||
grunt.registerTask('default', ['test', 'build', 'build-demos']);
|
||||
|
||||
// Dev dev/build/watch cycle
|
||||
grunt.registerTask('dev',
|
||||
['jshint', 'webpack:dev', 'copy:dist', 'stylus:compile', 'watch:app']);
|
||||
['eslint', 'shell:dev', 'copy:dist', 'shell:build-css', 'watch:app']);
|
||||
|
||||
// Production build
|
||||
grunt.registerTask('build',
|
||||
['jshint', 'webpack:build', 'copy:dist', 'stylus:compile']);
|
||||
['eslint', 'shell:build', 'copy:dist', 'shell:build-css']);
|
||||
|
||||
// Build demos
|
||||
grunt.registerTask('dev-demos', ['build-demos', 'watch:templates']);
|
||||
grunt.registerTask('build-demos', ['copy:templates', 'copy:assets',
|
||||
'string-replace:demos', 'copy:starter', 'clean']);
|
||||
|
||||
// Transpile with Babel
|
||||
grunt.registerTask('dev-modules', ['babel', 'copy:dist']);
|
||||
// Build tests
|
||||
grunt.registerTask('build-test',
|
||||
['eslint', 'shell:test', 'copy:dist', 'shell:build-css']);
|
||||
|
||||
// Tests
|
||||
grunt.registerTask('test', ['jshint', 'connect', 'qunit:all']);
|
||||
// Tests with coverage
|
||||
grunt.registerTask('test', ['build-test', 'connect', 'qunit:all']);
|
||||
|
||||
// Publish to gh-pages
|
||||
grunt.registerTask('publish', 'Publish from CLI', [
|
||||
'build', 'build-demos', 'esdoc', 'gh-pages:publish-lib',
|
||||
'gh-pages:publish-readme', 'gh-pages:publish-docs'
|
||||
]);
|
||||
|
||||
// Deploy to gh-pages
|
||||
grunt.registerTask('deploy', 'Publish from Travis', [
|
||||
'build', 'build-demos', 'esdoc', 'check-deploy'
|
||||
// Build all for deployment from travis
|
||||
grunt.registerTask('build-all', 'Prepare for deployment', [
|
||||
'build', 'build-demos', 'esdoc'
|
||||
]);
|
||||
|
||||
// Custom task running QUnit tests for specified files.
|
||||
// Usage example: grunt test-only:test.html,test-help.html
|
||||
grunt.registerTask('test-only',
|
||||
'A task that runs only specified tests.',
|
||||
function(tests) {
|
||||
if(!tests) {
|
||||
function (tests) {
|
||||
if (!tests) {
|
||||
return;
|
||||
}
|
||||
tests = tests.split(',');
|
||||
var res = [];
|
||||
|
||||
tests.forEach(function(itm) {
|
||||
tests.forEach(function (itm) {
|
||||
var filePath = path.resolve(testDir, itm);
|
||||
var parts = filePath.split(path.sep);
|
||||
res.push(buildTestUrl(testHost, testDir, parts));
|
||||
|
@ -349,13 +205,14 @@ module.exports = function (grunt) {
|
|||
grunt.task.run('connect');
|
||||
grunt.config('qunit.only.options.urls', res);
|
||||
grunt.task.run('qunit:only');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
function isTestFile(pth) {
|
||||
var allowedExts = ['.html', '.htm'];
|
||||
for(var i=0, len=allowedExts.length; i<len; i++){
|
||||
for (var i = 0, len = allowedExts.length; i < len; i++) {
|
||||
var ext = allowedExts[i];
|
||||
if(pth.indexOf(ext) !== -1){
|
||||
if (pth.indexOf(ext) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -371,18 +228,17 @@ module.exports = function (grunt) {
|
|||
|
||||
// Returns the list of test files from the test folder for qunit task
|
||||
function getTestFiles(testDir, host) {
|
||||
|
||||
var getFiles = function(dir, host) {
|
||||
var getFiles = function (dir, host) {
|
||||
var res = [];
|
||||
var items = fs.readdirSync(dir);
|
||||
|
||||
items.forEach(function(itm){
|
||||
items.forEach(function (itm) {
|
||||
var fileOrDir = path.resolve(dir, itm);
|
||||
if(isTestFile(fileOrDir)) {
|
||||
if (isTestFile(fileOrDir)) {
|
||||
var parts = fileOrDir.split(path.sep);
|
||||
res.push(buildTestUrl(host, testDir, parts));
|
||||
} else {
|
||||
if(fs.lstatSync(fileOrDir).isDirectory()) {
|
||||
if (fs.lstatSync(fileOrDir).isDirectory()) {
|
||||
res = res.concat(getFiles(fileOrDir, host));
|
||||
}
|
||||
}
|
||||
|
@ -393,43 +249,4 @@ module.exports = function (grunt) {
|
|||
|
||||
return getFiles(testDir, host);
|
||||
}
|
||||
|
||||
grunt.registerTask('check-deploy', function() {
|
||||
var env = process.env;
|
||||
// need this
|
||||
this.requires(['build', 'build-demos', 'esdoc']);
|
||||
|
||||
// only deploy under these conditions
|
||||
if (env.TRAVIS === 'true' &&
|
||||
env.TRAVIS_SECURE_ENV_VARS === 'true' &&
|
||||
env.TRAVIS_PULL_REQUEST === 'false') {
|
||||
grunt.log.writeln('executing deployment');
|
||||
// queue deploy
|
||||
grunt.task.run([
|
||||
'gh-pages:deploy-lib',
|
||||
'gh-pages:deploy-readme',
|
||||
'gh-pages:deploy-docs'
|
||||
]);
|
||||
} else {
|
||||
grunt.log.writeln('skipped deployment');
|
||||
}
|
||||
});
|
||||
|
||||
// Get a formatted commit message to review changes from the commit log
|
||||
// github will turn some of these into clickable links
|
||||
function getDeployMessage() {
|
||||
var ret = '\n\n';
|
||||
var env = process.env;
|
||||
if (env.TRAVIS !== 'true') {
|
||||
ret += 'missing env vars for travis-ci';
|
||||
return ret;
|
||||
}
|
||||
ret += 'branch: ' + env.TRAVIS_BRANCH + '\n';
|
||||
ret += 'SHA: ' + env.TRAVIS_COMMIT + '\n';
|
||||
ret += 'range SHA: ' + env.TRAVIS_COMMIT_RANGE + '\n';
|
||||
ret += 'build id: ' + env.TRAVIS_BUILD_ID + '\n';
|
||||
ret += 'build number: ' + env.TRAVIS_BUILD_NUMBER + '\n';
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#The MIT License (MIT)
|
||||
The MIT License (MIT)
|
||||
|
||||
##Copyright (c) 2015 Max Guglielmi
|
||||
Copyright (c) 2015 Max Guglielmi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
24
PULL_REQUEST_TEMPLATE.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
- [ ] Development tool configuration change
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes do not lower current test coverage
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
136
README.md
|
@ -1,5 +1,10 @@
|
|||
TableFilter [![Build Status](https://api.travis-ci.org/koalyptus/TableFilter.svg?branch=master)](https://travis-ci.org/koalyptus/TableFilter)
|
||||
===========================
|
||||
[![Build Status](https://api.travis-ci.org/koalyptus/TableFilter.svg?branch=master)](https://travis-ci.org/koalyptus/TableFilter)
|
||||
[![Document](https://www.tablefilter.com/docs/badge.svg)](https://www.tablefilter.com/docs/source.html)
|
||||
[![codecov](https://codecov.io/gh/koalyptus/TableFilter/branch/master/graph/badge.svg)](https://codecov.io/gh/koalyptus/TableFilter)
|
||||
[![Greenkeeper badge](https://badges.greenkeeper.io/koalyptus/TableFilter.svg)](https://greenkeeper.io/)
|
||||
[![Donate](https://img.shields.io/badge/donate-%24-green.svg)](https://www.tablefilter.com/donate.html)
|
||||
|
||||
# TableFilter
|
||||
|
||||
> A Javascript library making HTML tables filterable
|
||||
|
||||
|
@ -10,36 +15,63 @@ users to filter and limit the data displayed within a long table. By default, th
|
|||
## Features
|
||||
* Convert a regular HTML table into an advanced grid component providing:
|
||||
* Advanced columns filtering model
|
||||
* Sorting and pagination facilities
|
||||
* Complete selection model ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123) extension)
|
||||
* Extended keyboard navigation ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123) extension)
|
||||
* Inline cell or row editing ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123) extension)
|
||||
* Row insertion or deleting ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123) extension)
|
||||
* And even more behaviors...
|
||||
* Sorting and pagination capabilities
|
||||
* Complete selection model ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
|
||||
* Extended keyboard navigation ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
|
||||
* Inline cell or row editing ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
|
||||
* Row insertion or deleting ([ezEditTable](http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus) extension)
|
||||
* And even more features...
|
||||
* Attach to an existing HTML table
|
||||
* Integration with any server-side technology as this is a pure client-side
|
||||
solution
|
||||
* Callbacks for all events, and delegates for most actions
|
||||
* Exhaustive documentation and API
|
||||
* Valuable support provided under a Premium request
|
||||
* Exhaustive documentation and powerful API
|
||||
|
||||
## Getting started
|
||||
Clone the repo using Git:
|
||||
* Clone the repo using Git:
|
||||
```shell
|
||||
git clone --bare https://github.com/koalyptus/TableFilter.git
|
||||
git clone https://github.com/koalyptus/TableFilter.git
|
||||
```
|
||||
|
||||
Alternatively you can [download](https://github.com/koalyptus/TableFilter/archive/master.zip) this repository.
|
||||
* You can [download](https://github.com/koalyptus/TableFilter/archive/master.zip) this repository.
|
||||
|
||||
* TableFilter is available on [npm repository](https://www.npmjs.com/package/tablefilter), you can install it from the command line using the following command:
|
||||
```shell
|
||||
npm install tablefilter --save-dev
|
||||
```
|
||||
* or get the future features from the ``next`` release channel:
|
||||
```shell
|
||||
npm install tablefilter@next --save-dev
|
||||
```
|
||||
* Alternatively you can also [access these files from unpkg CDN](https://unpkg.com/tablefilter/), download them or point your package manager to them.
|
||||
|
||||
## Setup
|
||||
### Using modules
|
||||
Require `TableFilter`:
|
||||
```javascript
|
||||
// ES2015 modules
|
||||
import TableFilter from 'tablefilter';
|
||||
|
||||
// CommonJS or AMD modules
|
||||
var TableFilter = require('tablefilter');
|
||||
```
|
||||
|
||||
### Using distribution scripts
|
||||
If you are not using a module system, you can reference the distribution scripts directly in your html pages:
|
||||
```html
|
||||
<script src="path_to/node_modules/tablefilter/dist/tablefilter/tablefilter.js"></script>
|
||||
```
|
||||
|
||||
### Placing manually the distribution scripts in your project
|
||||
Copy the ``tablefilter`` directory under ``dist`` and place it at desired location in your project. Then include the main js file in your page:
|
||||
```shell
|
||||
<script src="path/to/my/scripts/tablefilter/tablefilter.js"></script>
|
||||
```
|
||||
|
||||
### Usage
|
||||
Place the following snippet just under the HTML table and always define a ``base_path`` property in the configuration object to reflect the path to the script
|
||||
```shell
|
||||
<script>
|
||||
var tf = new TableFilter('my-table-id', {
|
||||
var tf = new TableFilter(document.querySelector('.my-table'), {
|
||||
base_path: 'path/to/my/scripts/tablefilter/'
|
||||
});
|
||||
tf.init();
|
||||
|
@ -49,11 +81,11 @@ If the ``base_path`` property is not specified, it will default to ``/tablefilte
|
|||
```shell
|
||||
your-page.html
|
||||
|— tablefilter
|
||||
```
|
||||
```
|
||||
|
||||
## Development
|
||||
This project requires node.js and Grunt to be installed:
|
||||
- install [node.js](https://nodejs.org/)
|
||||
- install [node.js](https://nodejs.org/) v8.9.4 or higher
|
||||
- install [Grunt](http://gruntjs.com/getting-started) from the command line using npm (comes with node.js):
|
||||
```shell
|
||||
npm install -g grunt-cli
|
||||
|
@ -64,42 +96,52 @@ Start by installing any dependencies.
|
|||
```shell
|
||||
npm install
|
||||
```
|
||||
Use the Grunt ``dev`` task to launch a build / watch cycle and start the local
|
||||
sever on port ``8080``:
|
||||
Use
|
||||
```shell
|
||||
npm run dev
|
||||
```
|
||||
command to launch a build / watch cycle and start the local
|
||||
sever on port ``8080``.
|
||||
|
||||
Use
|
||||
```shell
|
||||
npm run build
|
||||
```
|
||||
command to generate a production build.
|
||||
|
||||
The
|
||||
```shell
|
||||
npm run dist
|
||||
```
|
||||
command will create a production build, run the tests and finally generate
|
||||
the demos:
|
||||
|
||||
To run all the tests and generate the coverage report:
|
||||
|
||||
```shell
|
||||
grunt dev
|
||||
npm test
|
||||
```
|
||||
|
||||
Use the ``build`` task to generate a production build:
|
||||
|
||||
```shell
|
||||
grunt build
|
||||
```
|
||||
|
||||
The ``default`` Grunt task will create a production build, run the tests and finally generate the demos:
|
||||
|
||||
```shell
|
||||
grunt
|
||||
```
|
||||
|
||||
To run all the tests:
|
||||
|
||||
```shell
|
||||
grunt test
|
||||
```
|
||||
|
||||
and to run specific test(s):
|
||||
or to run specific test(s):
|
||||
|
||||
```shell
|
||||
grunt test-only:test.html
|
||||
grunt test-only:test.html,test-sort.html
|
||||
```
|
||||
|
||||
to view the coverage report(s), open the `index.html` under the
|
||||
`report/coverage` folder or
|
||||
[online](https://codecov.io/gh/koalyptus/TableFilter).
|
||||
|
||||
## Demos
|
||||
Check out the online [examples](http://koalyptus.github.io/TableFilter/examples) or run the local webserver:
|
||||
Check out the online [examples](http://www.tablefilter.com/examples.html)
|
||||
or generate the demos locally:
|
||||
```shell
|
||||
grunt server
|
||||
npm run build:demos
|
||||
```
|
||||
then run the local webserver:
|
||||
```shell
|
||||
npm start
|
||||
```
|
||||
then pick a demo from:
|
||||
```shell
|
||||
|
@ -109,19 +151,21 @@ http://localhost:8080/demos/
|
|||
## Documentation
|
||||
Find exhaustive documentation on the configuration options in the [WIKI](https://github.com/koalyptus/TableFilter/wiki) section.
|
||||
|
||||
Autogenerated documentation of the ES6 javascript modules is available on the website: [docs](http://koalyptus.github.io/TableFilter/docs)
|
||||
Autogenerated documentation of the ES6 modules is available on the website: [docs](http://www.tablefilter.com/docs)
|
||||
|
||||
If you previously used the HTML Table Filter Generator plugin, verify the configuration
|
||||
options you are using are still supported: [Obsolete](https://github.com/koalyptus/TableFilter/wiki/Obsolete)
|
||||
|
||||
Run this task to generate the documentation in the ``docs/docs`` directory:
|
||||
```shell
|
||||
grunt esdoc
|
||||
npm run esdoc
|
||||
```
|
||||
|
||||
|
||||
## Support
|
||||
* GitHub for reporting bugs and feature requests.
|
||||
* GitHub for [reporting bugs](https://github.com/koalyptus/TableFilter/blob/master/CONTRIBUTING.md#reporting-bugs) and [feature requests](https://github.com/koalyptus/TableFilter/blob/master/CONTRIBUTING.md#suggesting-enhancements-and-features).
|
||||
|
||||
## License
|
||||
[MIT](LICENSE.md)
|
||||
[MIT](LICENSE)
|
||||
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.4 KiB |
13034
demos/auto-filter.html
13058
demos/demo.html
|
@ -1,460 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>TableFilter v0.0.1 - Filter and Sort Images Demo</title>
|
||||
<link rel="stylesheet" type="text/css" href="../dist/tablefilter/style/tablefilter.css">
|
||||
<style type="text/css">
|
||||
body{
|
||||
font-family: Helvetica, arial, nimbussansl, liberationsans, freesans,
|
||||
clean, sans-serif;
|
||||
padding: 0 1em;
|
||||
}
|
||||
pre{
|
||||
margin: auto 1em 1em 1em;
|
||||
padding: 0 1em 1em 1em;
|
||||
float: right;
|
||||
line-height: 1.45;
|
||||
background-color: #F7F7F7;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.panel{
|
||||
float: left;
|
||||
background: #F7F7F7 none repeat scroll 0% 0%;
|
||||
width: 250px;
|
||||
margin-right: 2em;
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>TableFilter v0.0.1</h1>
|
||||
<h2>Filter and sort images demo</h2>
|
||||
<p>
|
||||
This demo shows how to filter and sort a column containing images by using
|
||||
the <code>custom_cell_data</code> delegate and the
|
||||
<code>data-tf-sortKey</code> attribute.
|
||||
</p>
|
||||
|
||||
<pre></pre>
|
||||
|
||||
|
||||
<table id="demo">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">Book</th>
|
||||
<th>Price</th>
|
||||
<th colspan="2">Delivery Items</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="50"> </th>
|
||||
<th width="210">Title</th>
|
||||
<th width="150">Author</th>
|
||||
<th width="50">$</th>
|
||||
<th width="30">In store</th>
|
||||
<th width="70">Shipping</th>
|
||||
<th width="100">Publication Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td data-tf-sortKey="new edition">
|
||||
<img src="assets/img_new-edition.png" alt="new edition" />
|
||||
</td>
|
||||
<td>Boris Godunov</td>
|
||||
<td>Alexandr Pushkin</td>
|
||||
<td>
|
||||
<div align="right">7.15</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">01/01/1999</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="sold out">
|
||||
<img src="assets/img_sold-out.png" alt="sold out" />
|
||||
</td>
|
||||
<td>The Rainmaker</td>
|
||||
<td>John Grisham</td>
|
||||
<td>
|
||||
<div align="right">7.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">12/01/2001</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="best price">
|
||||
<img src="assets/img_best-price.png" alt="best price" />
|
||||
</td>
|
||||
<td>The Green Mile</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">11.10</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">01/01/1992</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="great deal">
|
||||
<img src="assets/img_great-deal.png" alt="great deal" />
|
||||
</td>
|
||||
<td>Misery</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">7.70</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="na">na</td>
|
||||
<td>
|
||||
<div align="right">01/01/2003</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="great deal">
|
||||
<img src="assets/img_great-deal.png" alt="great deal" />
|
||||
</td>
|
||||
<td>The Dark Half</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">0</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">10/30/1999</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="sold out">
|
||||
<img src="assets/img_sold-out.png" alt="sold out" />
|
||||
</td>
|
||||
<td>The Partner</td>
|
||||
<td>John Grisham</td>
|
||||
<td>
|
||||
<div align="right">12.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">01/01/2005</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="sold out">
|
||||
<img src="assets/img_sold-out.png" alt="sold out" />
|
||||
</td>
|
||||
<td>It</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">9.70</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="12">12 Hours</td>
|
||||
<td>
|
||||
<div align="right">10/15/2001</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="great deal">
|
||||
<img src="assets/img_great-deal.png" alt="great deal" />
|
||||
</td>
|
||||
<td>Cousin Bette</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">0</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">12/01/1991</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="great deal">
|
||||
<img src="assets/img_great-deal.png" alt="great deal" />
|
||||
</td>
|
||||
<td>The Testament</td>
|
||||
<td>John Grisham</td>
|
||||
<td>
|
||||
<div align="right">19.10</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">12/17/1999</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="new edition">
|
||||
<img src="assets/img_new-edition.png" alt="new edition" />
|
||||
</td>
|
||||
<td>Eugene Onegin</td>
|
||||
<td>Alexandr Pushkin</td>
|
||||
<td>
|
||||
<div align="right">11.20</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">09/27/2005</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="best price">
|
||||
<img src="assets/img_best-price.png" alt="best price" />
|
||||
</td>
|
||||
<td>Dark Avenues</td>
|
||||
<td>Ivan Bunin</td>
|
||||
<td>
|
||||
<div align="right">14.96</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">10/01/2008</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="great deal">
|
||||
<img src="assets/img_great-deal.png" alt="great deal" />
|
||||
</td>
|
||||
<td>Father Goriot</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">9.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">06/06/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="sold out">
|
||||
<img src="assets/img_sold-out.png" alt="sold out" />
|
||||
</td>
|
||||
<td>The Captain's Daughter</td>
|
||||
<td>Alexandr Pushkin</td>
|
||||
<td>
|
||||
<div align="right">10.21</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">03/01/2001</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="sold out">
|
||||
<img src="assets/img_sold-out.png" alt="sold out" />
|
||||
</td>
|
||||
<td>Hamlet</td>
|
||||
<td>William Shakespeare</td>
|
||||
<td>
|
||||
<div align="right">5.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">04/15/2003</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="best price">
|
||||
<img src="assets/img_best-price.png" alt="best price" />
|
||||
</td>
|
||||
<td>The Village</td>
|
||||
<td>Ivan Bunin</td>
|
||||
<td>
|
||||
<div align="right">11.66</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">01/02/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="new edition">
|
||||
<img src="assets/img_new-edition.png" alt="new edition" />
|
||||
</td>
|
||||
<td>The Winter's Tale</td>
|
||||
<td>William Shakespeare</td>
|
||||
<td>
|
||||
<div align="right">19.31</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">02/12/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="great deal">
|
||||
<img src="assets/img_great-deal.png" alt="great deal" />
|
||||
</td>
|
||||
<td>The Black Sheep</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">16.00</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">08/28/1976</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td data-tf-sortKey="new edition">
|
||||
<img src="assets/img_new-edition.png" alt="new edition" />
|
||||
</td>
|
||||
<td>Lost Illusions</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">8.0</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="na">na</td>
|
||||
<td>
|
||||
<div align="right">07/10/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script src="../dist/tablefilter/tablefilter.js"></script>
|
||||
<script data-config>
|
||||
var tfConfig = {
|
||||
base_path: '../dist/tablefilter/',
|
||||
filters_row_index: 2,
|
||||
headers_row_index: 1,
|
||||
col_number_format: [
|
||||
null, null, null,
|
||||
'US', null, null, null
|
||||
],
|
||||
col_date_type: [
|
||||
null, null, null,
|
||||
null, null, null, 'mdy'
|
||||
],
|
||||
rows_counter: true,
|
||||
rows_counter_text: 'Books: ',
|
||||
alternate_rows: true,
|
||||
btn_reset: true,
|
||||
|
||||
/* filter types*/
|
||||
col_0: 'select',
|
||||
col_4: 'select',
|
||||
col_5: 'select',
|
||||
|
||||
/* custom_cell_data delegate used for filtering
|
||||
images in a column */
|
||||
custom_cell_data_cols: [0, 4],
|
||||
custom_cell_data: function(o, cell, colIndex){
|
||||
if(colIndex === 0){
|
||||
var img = cell.getElementsByTagName('img')[0];
|
||||
if(!img){
|
||||
return '';
|
||||
}
|
||||
return img.alt;
|
||||
} else if(colIndex === 4){
|
||||
var chk = cell.getElementsByTagName('input')[0];
|
||||
if(chk.checked){
|
||||
return 'yes';
|
||||
} else {
|
||||
return 'no';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* Custom options for 'Shipping' column */
|
||||
custom_options: {
|
||||
cols: [5],
|
||||
texts: [
|
||||
[
|
||||
'1 Hour', '12 Hours', '24 Hours',
|
||||
'2 Days', 'na'
|
||||
]
|
||||
],
|
||||
values: [
|
||||
[
|
||||
'1 Hour', '12 Hours', '24 Hours',
|
||||
'2 Days', 'na'
|
||||
]
|
||||
],
|
||||
sorts: [false]
|
||||
},
|
||||
|
||||
/* SkyBlue theme */
|
||||
themes: [{ name: 'skyblue'}],
|
||||
|
||||
/* Sorting feature */
|
||||
extensions: [{ name: 'sort' }]
|
||||
};
|
||||
|
||||
var tf = new TableFilter('demo', 2, tfConfig);
|
||||
tf.init();
|
||||
</script>
|
||||
|
||||
|
||||
<!--
|
||||
DO NOT COPY: NOT PART OF TABLEFILTER
|
||||
-->
|
||||
<script>
|
||||
var configs = document.querySelectorAll('script[data-config]');
|
||||
var pre = document.body.getElementsByTagName('pre')[0];
|
||||
|
||||
[].forEach.call(configs, function(config) {
|
||||
if(pre){
|
||||
pre.innerHTML +=
|
||||
config.innerHTML.replace('<', '<').replace('>', '>');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- -->
|
||||
|
||||
</body>
|
||||
</html>
|
13041
demos/grid-layout.html
13060
demos/pagination.html
2685
demos/requirejs.html
|
@ -1,415 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>TableFilter v0.0.1 - Theme Roller Demo</title>
|
||||
<link rel="stylesheet" type="text/css" href="../dist/tablefilter/style/tablefilter.css">
|
||||
<style type="text/css">
|
||||
body{
|
||||
font-family: Helvetica, arial, nimbussansl, liberationsans, freesans,
|
||||
clean, sans-serif;
|
||||
padding: 0 1em;
|
||||
}
|
||||
pre{
|
||||
margin: auto 1em 1em 1em;
|
||||
padding: 0 1em 1em 1em;
|
||||
float: right;
|
||||
line-height: 1.45;
|
||||
background-color: #F7F7F7;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.panel{
|
||||
float: left;
|
||||
background: #F7F7F7 none repeat scroll 0% 0%;
|
||||
width: 250px;
|
||||
margin-right: 2em;
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>TableFilter v0.0.1</h1>
|
||||
<h2>Theme roller demo</h2>
|
||||
<p>
|
||||
This demo shows how to implement a theme roller.
|
||||
</p>
|
||||
|
||||
<pre></pre>
|
||||
|
||||
|
||||
<div>
|
||||
Theme:
|
||||
<select onchange="javascript:changeTheme(this.value);">
|
||||
<option value="default" selected="selected">default</option>
|
||||
<option value="mytheme">mytheme</option>
|
||||
<option value="skyblue">skyblue</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table id="books">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Book</th>
|
||||
<th>Price</th>
|
||||
<th colspan="2">Delivery Items</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="210">Title</th>
|
||||
<th width="150">Author</th>
|
||||
<th width="50">$</th>
|
||||
<th width="30">In store</th>
|
||||
<th width="70">Shipping</th>
|
||||
<th width="100">Publication Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Boris Godunov</td>
|
||||
<td>Alexandr Pushkin</td>
|
||||
<td>
|
||||
<div align="right">7.15</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">01/01/1999</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Rainmaker</td>
|
||||
<td>John Grisham</td>
|
||||
<td>
|
||||
<div align="right">7.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">12/01/2001</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Green Mile</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">11.10</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">01/01/1992</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Misery</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">7.70</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="na">na</td>
|
||||
<td>
|
||||
<div align="right">01/01/2003</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Dark Half</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">0</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">10/30/1999</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Partner</td>
|
||||
<td>John Grisham</td>
|
||||
<td>
|
||||
<div align="right">12.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">01/01/2005</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>It</td>
|
||||
<td>Stephen King</td>
|
||||
<td>
|
||||
<div align="right">9.70</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="12">12 Hours</td>
|
||||
<td>
|
||||
<div align="right">10/15/2001</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cousin Bette</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">0</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">12/01/1991</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Testament</td>
|
||||
<td>John Grisham</td>
|
||||
<td>
|
||||
<div align="right">19.10</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">12/17/1999</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Eugene Onegin</td>
|
||||
<td>Alexandr Pushkin</td>
|
||||
<td>
|
||||
<div align="right">11.20</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">09/27/2005</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dark Avenues</td>
|
||||
<td>Ivan Bunin</td>
|
||||
<td>
|
||||
<div align="right">14.96</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">10/01/2008</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Father Goriot</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">9.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">06/06/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Captain's Daughter</td>
|
||||
<td>Alexandr Pushkin</td>
|
||||
<td>
|
||||
<div align="right">10.21</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="48">2 Days</td>
|
||||
<td>
|
||||
<div align="right">03/01/2001</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hamlet</td>
|
||||
<td>William Shakespeare</td>
|
||||
<td>
|
||||
<div align="right">5.99</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">04/15/2003</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Village</td>
|
||||
<td>Ivan Bunin</td>
|
||||
<td>
|
||||
<div align="right">11.66</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="24">24 Hours</td>
|
||||
<td>
|
||||
<div align="right">01/02/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Winter's Tale</td>
|
||||
<td>William Shakespeare</td>
|
||||
<td>
|
||||
<div align="right">19.31</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">02/12/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The Black Sheep</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">16.00</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="0">
|
||||
<input type="checkbox" disabled="disabled" value="" />
|
||||
</td>
|
||||
<td data-tf-sortKey="1">1 Hour</td>
|
||||
<td>
|
||||
<div align="right">08/28/1976</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lost Illusions</td>
|
||||
<td>Honore de Balzac</td>
|
||||
<td>
|
||||
<div align="right">8.0</div>
|
||||
</td>
|
||||
<td data-tf-sortKey="1">
|
||||
<input type="checkbox" disabled="disabled" value="" checked="checked" />
|
||||
</td>
|
||||
<td data-tf-sortKey="na">na</td>
|
||||
<td>
|
||||
<div align="right">07/10/2010</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script src="../dist/tablefilter/tablefilter.js"></script>
|
||||
<script data-config>
|
||||
var tfConfig = {
|
||||
base_path: '../dist/tablefilter/',
|
||||
filters_row_index: 2,
|
||||
headers_row_index: 1,
|
||||
sort: true,
|
||||
col_number_format: [
|
||||
null, null, 'US',
|
||||
null, null, null
|
||||
],
|
||||
col_date_type: [
|
||||
null, null, null,
|
||||
null, null, 'mdy'
|
||||
],
|
||||
rows_counter: true,
|
||||
rows_counter_text: 'Books: ',
|
||||
alternate_rows: true,
|
||||
btn_reset: true,
|
||||
mark_active_columns: true,
|
||||
|
||||
/* Filter types*/
|
||||
col_3: 'select',
|
||||
col_4: 'select',
|
||||
|
||||
/* delegate for filtering 'In store' column */
|
||||
custom_cell_data_cols: [3],
|
||||
custom_cell_data: function(o, cell, colIndex){
|
||||
if(colIndex === 3){
|
||||
var chk = cell.getElementsByTagName('input')[0];
|
||||
if(!chk){
|
||||
return '';
|
||||
}
|
||||
if(chk.checked){
|
||||
return 'yes';
|
||||
} else {
|
||||
return 'no';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* Custom options for 'Shipping' column */
|
||||
custom_options: {
|
||||
cols: [4],
|
||||
texts: [
|
||||
['1 Hour','12 Hours','24 Hours','2 Days','na']
|
||||
],
|
||||
values: [
|
||||
['1 Hour','12 Hours','24 Hours','2 Days','na']
|
||||
],
|
||||
sorts: [false]
|
||||
},
|
||||
|
||||
/* theme */
|
||||
themes: [{ name: 'default' }],
|
||||
|
||||
extensions: [{ name: 'sort' }]
|
||||
};
|
||||
|
||||
var tf = new TableFilter('books', tfConfig, 2);
|
||||
tf.init();
|
||||
|
||||
// TableFilter themes
|
||||
var THEMES = {
|
||||
'default': tf.themesPath + 'default/default.css',
|
||||
'mytheme': tf.themesPath + 'mytheme/mytheme.css',
|
||||
'skyblue': tf.themesPath + 'skyblue/skyblue.css'
|
||||
};
|
||||
|
||||
function changeTheme(name){
|
||||
var stylesheet = tf.getStylesheet('default');
|
||||
stylesheet.href = THEMES[name];
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
DO NOT COPY: NOT PART OF TABLEFILTER
|
||||
-->
|
||||
<script>
|
||||
var configs = document.querySelectorAll('script[data-config]');
|
||||
var pre = document.body.getElementsByTagName('pre')[0];
|
||||
|
||||
[].forEach.call(configs, function(config) {
|
||||
if(pre){
|
||||
pre.innerHTML +=
|
||||
config.innerHTML.replace('<', '<').replace('>', '>');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- -->
|
||||
|
||||
</body>
|
||||
</html>
|
16
dist/starter.html
vendored
|
@ -1,10 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>TableFilter v0.0.1 - Starter</title>
|
||||
<title>tablefilter v0.7.3 - Starter</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>TableFilter v0.0.1</h1>
|
||||
<h1>tablefilter v0.7.3</h1>
|
||||
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>country</th>
|
||||
<th>isocode</th>
|
||||
<th>iso</th>
|
||||
<th>year</th>
|
||||
<th>POP</th>
|
||||
<th>XRAT</th>
|
||||
|
@ -460,10 +460,10 @@
|
|||
status_bar: true,
|
||||
mark_active_columns: true,
|
||||
highlight_keywords: true,
|
||||
col_number_format: [
|
||||
null, null, 'US',
|
||||
'US', 'US', 'US',
|
||||
'US', 'US', 'US'
|
||||
col_types: [
|
||||
'string', 'string', 'number',
|
||||
'number', 'number', 'number',
|
||||
'number', 'number', 'number'
|
||||
],
|
||||
custom_options: {
|
||||
cols:[3],
|
||||
|
@ -479,7 +479,7 @@
|
|||
},
|
||||
col_widths: [
|
||||
'150px', '100px', '100px',
|
||||
'70px', '70px', '70px',
|
||||
'100px', '100px', '100px',
|
||||
'70px', '60px', '60px'
|
||||
],
|
||||
extensions:[{ name: 'sort' }]
|
||||
|
|
7
dist/tablefilter/style/colsVisibility.css
vendored
|
@ -1,6 +1 @@
|
|||
/**
|
||||
* TableFilter v0.0.1 by Max Guglielmi
|
||||
* build date: 2015-10-15T08:29:12.260Z
|
||||
* MIT License
|
||||
*/
|
||||
span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:12px;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff}
|
||||
span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:inherit;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style-type:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff}
|
5
dist/tablefilter/style/filtersVisibility.css
vendored
|
@ -1,6 +1 @@
|
|||
/**
|
||||
* TableFilter v0.0.1 by Max Guglielmi
|
||||
* build date: 2015-10-15T08:29:12.260Z
|
||||
* MIT License
|
||||
*/
|
||||
span.expClpFlt a.btnExpClpFlt{width:35px;height:35px;display:inline-block;}span.expClpFlt a.btnExpClpFlt:hover{background-color:#f4f4f4}span.expClpFlt img{padding:8px 11px 11px 11px}
|
23
dist/tablefilter/style/tablefilter.css
vendored
|
@ -1,6 +1 @@
|
|||
/**
|
||||
* TableFilter v0.0.1 by Max Guglielmi
|
||||
* build date: 2015-10-15T08:29:12.260Z
|
||||
* MIT License
|
||||
*/
|
||||
table.TF{border-left:1px solid #ccc !important;border-top:none !important;border-right:none !important;border-bottom:none !important;}table.TF th{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important;color:#333 !important}table.TF td{border-bottom:1px dotted #999 !important;padding:5px !important}.fltrow{background-color:#ebecee !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #666 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #999 !important}input.flt{width:99% !important}.inf{height:$min-height;background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important}input.reset{background:transparent url("images/btn_eraser.gif") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background:transparent url("images/btn_over_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background:transparent url("images/btn_over_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background:transparent url("images/btn_over_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background:transparent url("images/btn_over_last_page.gif") center center no-repeat !important}div.grd_Cont{background-color:#ebecee !important;border:1px solid #ccc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#d5d5d5}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important;}div.grd_headTblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #999 !important}.grd_inf{background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important;border-top:1px solid #d0d0d0 !important}.loader{border:1px solid #999}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#fff}.odd{background-color:#d5d5d5}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}
|
||||
table.TF{border-left:1px solid #ccc;border-top:none;border-right:none;border-bottom:none;}table.TF th{background:#ebecee url("images/bg_th.jpg") left top repeat-x;border-bottom:1px solid #d0d0d0;border-right:1px solid #d0d0d0;border-left:1px solid #fff;border-top:1px solid #fff;color:#333}table.TF td{border-bottom:1px dotted #999;padding:5px}.fltrow{background-color:#ebecee !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #666 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #999 !important}input.flt{width:99% !important}.inf{height:$min-height;background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important}input.reset{background:transparent url("images/btn_eraser.gif") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background:transparent url("images/btn_over_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background:transparent url("images/btn_over_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background:transparent url("images/btn_over_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background:transparent url("images/btn_over_last_page.gif") center center no-repeat !important}div.grd_Cont{background-color:#ebecee !important;border:1px solid #ccc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#d5d5d5}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important;}div.grd_headTblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #999 !important}.grd_inf{background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important;border-top:1px solid #d0d0d0 !important}.loader{border:1px solid #999}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#fff}.odd{background-color:#d5d5d5}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#999 !important}
|
|
@ -1,6 +1 @@
|
|||
/**
|
||||
* TableFilter v0.0.1 by Max Guglielmi
|
||||
* build date: 2015-10-15T08:29:12.260Z
|
||||
* MIT License
|
||||
*/
|
||||
table.TF{border-left:1px dotted #81963b !important;border-top:none !important;border-right:0 !important;border-bottom:none !important;}table.TF th{background:#39424b url("images/bg_headers.jpg") left top repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;color:#fff !important}table.TF td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b;padding:5px !important}.fltrow{background-color:#81963b !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #687830 !important}input.flt{width:99% !important}.inf{background:#d8d8d8;height:$min-height}input.reset{width:53px;background:transparent url("images/btn_filter.png") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important}div.grd_Cont{background:#81963b url("images/bg_headers.jpg") left top repeat-x !important;border:1px solid #ccc !important;padding:0 1px 1px 1px !important;}div.grd_Cont .even{background-color:#bccd83}div.grd_Cont .odd{background-color:#fff}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important;}div.grd_tblCont table td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b}div.grd_tblCont table th,div.grd_headTblCont table th{background:transparent url("images/bg_headers.jpg") 0 0 repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;padding:0 4px 0 4px !important;color:#fff !important;height:35px !important}div.grd_headTblCont table td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;background-color:#81963b !important;padding:1px 3px 1px 3px !important}.grd_inf{background-color:#d8d8d8;border-top:1px solid #d0d0d0 !important}.loader{border:0 !important;background:#81963b !important}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#bccd83}.odd{background-color:#fff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}
|
||||
table.TF{border-left:1px dotted #81963b;border-top:none;border-right:0;border-bottom:none;}table.TF th{background:#39424b url("images/bg_headers.jpg") left top repeat-x;border-bottom:0;border-right:1px dotted #d0d0d0;border-left:0;border-top:0;color:#fff}table.TF td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b;padding:5px}.fltrow{background-color:#81963b !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #687830 !important}input.flt{width:99% !important}.inf{background:#d8d8d8;height:$min-height}input.reset{width:53px;background:transparent url("images/btn_filter.png") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important}div.grd_Cont{background:#81963b url("images/bg_headers.jpg") left top repeat-x !important;border:1px solid #ccc !important;padding:0 1px 1px 1px !important;}div.grd_Cont .even{background-color:#bccd83}div.grd_Cont .odd{background-color:#fff}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important;}div.grd_tblCont table td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b}div.grd_tblCont table th,div.grd_headTblCont table th{background:transparent url("images/bg_headers.jpg") 0 0 repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;padding:0 4px 0 4px !important;color:#fff !important;height:35px !important}div.grd_headTblCont table td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;background-color:#81963b !important;padding:1px 3px 1px 3px !important}.grd_inf{background-color:#d8d8d8;border-top:1px solid #d0d0d0 !important}.loader{border:0 !important;background:#81963b !important}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#bccd83}.odd{background-color:#fff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.activeHeader{background:#81963b !important}
|
|
@ -1,6 +1 @@
|
|||
/**
|
||||
* TableFilter v0.0.1 by Max Guglielmi
|
||||
* build date: 2015-10-15T08:29:12.260Z
|
||||
* MIT License
|
||||
*/
|
||||
table.TF{padding:0;color:#000;border-right:1px solid #a4bed4;border-top:1px solid #a4bed4;border-left:1px solid #a4bed4;border-bottom:0;}table.TF th{margin:0;color:inherit;background:#d1e5fe url("images/bg_skyblue.gif") 0 0 repeat-x;border-color:#fdfdfd #a4bed4 #a4bed4 #fdfdfd;border-width:1px;border-style:solid}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid #a4bed4;border-left:0;border-top:0;border-right:0}.fltrow{background-color:#d1e5fe !important;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4 !important}input.flt{width:99% !important}.inf{background-color:#e3efff !important;border:1px solid #a4bed4;height:$min-height;color:#004a6f}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#ffe4ab url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#ffe4ab url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#ffe4ab url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#ffe4ab url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.activeHeader{background:#ffe4ab !important;border:1px solid #ffb552 !important;color:inherit !important}div.grd_Cont{background-color:#d9eaed !important;border:1px solid #9cc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#e3efff}div.grd_headTblCont{background-color:#d9eaed !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#d9eaed url("images/bg_skyblue.gif") left top repeat-x;border-bottom:1px solid #a4bed4;border-right:1px solid #a4bed4 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #a4bed4 !important;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:#cce2fe;color:#004a6f;border-top:1px solid #9cc !important;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#2d8eef;border:1px solid #cce2fe;border-radius:5px}.even{background-color:#fff}.odd{background-color:#e3efff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ffdc61 !important;color:inherit}.ezSelectedRow{background-color:#ffe4ab !important;color:inherit}.ezActiveCell{background-color:#fff !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#fff !important;font-weight:bold;color:#000 !important}
|
BIN
dist/tablefilter/style/themes/transparent/images/btn_first_page.gif
vendored
Normal file
After Width: | Height: | Size: 63 B |
BIN
dist/tablefilter/style/themes/transparent/images/btn_last_page.gif
vendored
Normal file
After Width: | Height: | Size: 61 B |
BIN
dist/tablefilter/style/themes/transparent/images/btn_next_page.gif
vendored
Normal file
After Width: | Height: | Size: 59 B |
BIN
dist/tablefilter/style/themes/transparent/images/btn_prev_page.gif
vendored
Normal file
After Width: | Height: | Size: 58 B |
BIN
dist/tablefilter/style/themes/transparent/images/icn_clear_filters.png
vendored
Normal file
After Width: | Height: | Size: 601 B |
BIN
dist/tablefilter/style/themes/transparent/images/img_loading.gif
vendored
Normal file
After Width: | Height: | Size: 847 B |
1
dist/tablefilter/style/themes/transparent/transparent.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
table.TF{padding:0;color:inherit;border-right:1px solid transparent;border-top:1px solid transparent;border-left:1px solid transparent;border-bottom:0;}table.TF th{margin:0;color:inherit;background-color:transparent;border-color:transparent;border-width:1px;border-style:solid;}table.TF th:last-child{border-right:1px solid transparent}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid transparent;border-left:0;border-top:0;border-right:0}.fltrow{background-color:transparent;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px;border-bottom:1px solid transparent !important;}.fltrow th:last-child,.fltrow td:last-child{border-right:1px solid transparent}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4}input.flt{width:99% !important}.inf{background-color:transparent;border:1px solid transparent;height:$min-height;color:inherit}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#f7f7f7 url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#f7f7f7 url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#f7f7f7 url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#f7f7f7 url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #f7f7f7 !important}.activeHeader{background:#f7f7f7 !important;border:1px solid transparent;color:inherit !important}div.grd_Cont{-webkit-box-shadow:0 0 0 0 rgba(50,50,50,0.75);-moz-box-shadow:0 0 0 0 rgba(50,50,50,0.75);box-shadow:0 0 0 0 rgba(50,50,50,0.75);background-color:transparent;border:1px solid transparent;padding:0 !important;}div.grd_Cont .even{background-color:transparent}div.grd_Cont .odd{background-color:#f7f7f7}div.grd_headTblCont{background-color:transparent;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:transparent;border-bottom:1px solid transparent;border-right:1px solid transparent !important;border-left:1px solid transparent;border-top:1px solid transparent}div.grd_tblCont table td{border-bottom:1px solid transparent;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:transparent;color:inherit;border-top:1px solid transparent;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#f7f7f7;border:1px solid #f7f7f7;border-radius:5px;color:#000;text-shadow:none}.even{background-color:transparent}.odd{background-color:#f7f7f7}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ccc !important;color:inherit}.ezSelectedRow{background-color:#ccc !important;color:inherit}.ezActiveCell{background-color:transparent;color:inherit;font-weight:bold}.ezETSelectedCell{background-color:transparent;font-weight:bold;color:inherit}
|
11
dist/tablefilter/tablefilter.js
vendored
1
dist/tablefilter/tf-1-2aa33b10e0e549020c12.js
vendored
Normal file
8
dist/tablefilter/tf-1.js
vendored
3
index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('./dist/tablefilter/tablefilter').TableFilter;
|
12319
package-lock.json
generated
Normal file
81
package.json
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"name": "TableFilter",
|
||||
"version": "0.0.1",
|
||||
"description": "Filter HTML tables data easily",
|
||||
"main": "tablefilter.js",
|
||||
"name": "tablefilter",
|
||||
"version": "0.7.3",
|
||||
"description": "A Javascript library making HTML tables filterable and a bit more",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Max Guglielmi",
|
||||
|
@ -22,39 +21,59 @@
|
|||
"sort",
|
||||
"pagination"
|
||||
],
|
||||
"files": [
|
||||
"README.md",
|
||||
"LICENSE.md",
|
||||
"src/*"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
"lint": "eslint src/**/*.js test/*.js *.js",
|
||||
"dev": "webpack --colors --config webpack.dev.config.js",
|
||||
"build": "webpack --colors --config webpack.config.js",
|
||||
"build:css": "stylus -c static/style/tablefilter.styl -o dist/tablefilter/style && stylus -c static/style/extensions -o dist/tablefilter/style && stylus -c static/style/themes/default -o dist/tablefilter/style/themes/default && stylus -c static/style/themes/mytheme -o dist/tablefilter/style/themes/mytheme && stylus -c static/style/themes/skyblue -o dist/tablefilter/style/themes/skyblue && stylus -c static/style/themes/transparent -o dist/tablefilter/style/themes/transparent",
|
||||
"build:demos": "grunt build-demos",
|
||||
"build:test": "webpack --colors --config webpack.test.config.js",
|
||||
"server": "webpack-dev-server --colors --hot --config ./webpack.dev.config.js",
|
||||
"test": "grunt test",
|
||||
"codecov": "./node_modules/.bin/codecov",
|
||||
"esdoc": "esdoc",
|
||||
"dist": "grunt",
|
||||
"build:all": "grunt build-all",
|
||||
"start": "npm run server"
|
||||
},
|
||||
"publishConfig": {
|
||||
"tag": "next"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^5.1.13",
|
||||
"babel-loader": "^5.0.0",
|
||||
"clean-webpack-plugin": "^0.1.3",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-babel": "^5.0.0",
|
||||
"grunt-contrib-clean": "^0.6.0",
|
||||
"grunt-contrib-connect": "^0.10.1",
|
||||
"grunt-contrib-copy": "^0.8.0",
|
||||
"grunt-contrib-jshint": "^0.11.2",
|
||||
"grunt-contrib-qunit": "^0.7.0",
|
||||
"grunt-contrib-stylus": "^0.22.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-esdoc": "0.0.1",
|
||||
"grunt-gh-pages": "^0.10.0",
|
||||
"grunt-string-replace": "^1.2.0",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"script-loader": "^0.6.1",
|
||||
"string-replace-webpack-plugin": "0.0.1",
|
||||
"webpack": "^1.8.10",
|
||||
"webpack-dev-server": "^1.8.2"
|
||||
"@babel/core": "7.10.0",
|
||||
"@babel/preset-env": "7.10.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-loader": "^8.0.2",
|
||||
"babel-preset-env": "1.7.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"codecov": "3.7.1",
|
||||
"diacritics": "1.3.0",
|
||||
"esdoc": "1.1.0",
|
||||
"esdoc-standard-plugin": "1.0.0",
|
||||
"eslint": "6.5.0",
|
||||
"format-number": "3.0.0",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-cli": "1.3.2",
|
||||
"grunt-contrib-clean": "^2.0.0",
|
||||
"grunt-contrib-connect": "^2.0.0",
|
||||
"grunt-contrib-copy": "^1.0.0",
|
||||
"grunt-contrib-watch": "^1.0.0",
|
||||
"grunt-qunit-istanbul": "1.1.0",
|
||||
"grunt-shell": "3.0.0",
|
||||
"grunt-string-replace": "^1.3.1",
|
||||
"isparta-loader": "2.0.0",
|
||||
"script-loader": "^0.7.0",
|
||||
"string-replace-webpack-plugin": "^0.1.3",
|
||||
"stylus": "^0.54.5",
|
||||
"sugar-date": "2.0.6",
|
||||
"uglifyjs-webpack-plugin": "2.2.0",
|
||||
"webpack": "^4.38.0",
|
||||
"webpack-cli": "^3.0.8",
|
||||
"webpack-dev-server": "^3.1.11"
|
||||
},
|
||||
"dependencies": {},
|
||||
"bugs": {
|
||||
"url": "https://github.com/koalyptus/TableFilter/issues"
|
||||
},
|
||||
"homepage": "http://koalyptus.github.io/TableFilter"
|
||||
"homepage": "https://www.tablefilter.com"
|
||||
}
|
||||
|
|
32
src/array.js
|
@ -2,25 +2,21 @@
|
|||
* Array utilities
|
||||
*/
|
||||
|
||||
import Str from './string';
|
||||
import {matchCase} from './string';
|
||||
|
||||
export default {
|
||||
has: function(arr, val, caseSensitive){
|
||||
let sCase = caseSensitive===undefined ? false : caseSensitive;
|
||||
for (var i=0; i<arr.length; i++){
|
||||
if(Str.matchCase(arr[i].toString(), sCase) == val){
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Checks if given item can be found in the passed collection
|
||||
* @param {Array} arr collection
|
||||
* @param {Any} val item to search
|
||||
* @param {Boolean} caseSensitive respects case if true
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const has = (arr, val, caseSensitive) => {
|
||||
let sCase = Boolean(caseSensitive);
|
||||
for (var i = 0, l = arr.length; i < l; i++) {
|
||||
if (matchCase(arr[i].toString(), sCase) === val) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
indexByValue: function(arr, val, caseSensitive){
|
||||
let sCase = caseSensitive===undefined ? false : caseSensitive;
|
||||
for (var i=0; i<arr.length; i++){
|
||||
if(Str.matchCase(arr[i].toString(), sCase) == val){
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
118
src/const.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* Filter types
|
||||
*/
|
||||
|
||||
/**
|
||||
* Input filter type
|
||||
* @type {String}
|
||||
*/
|
||||
export const INPUT = 'input';
|
||||
/**
|
||||
* Select filter type
|
||||
* @type {String}
|
||||
*/
|
||||
export const SELECT = 'select';
|
||||
/**
|
||||
* Multiple select filter type
|
||||
* @type {String}
|
||||
*/
|
||||
export const MULTIPLE = 'multiple';
|
||||
/**
|
||||
* Checklist filter type
|
||||
* @type {String}
|
||||
*/
|
||||
export const CHECKLIST = 'checklist';
|
||||
/**
|
||||
* None filter type
|
||||
* @type {String}
|
||||
*/
|
||||
export const NONE = 'none';
|
||||
|
||||
/**
|
||||
* Key codes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enter key code
|
||||
* @type {Number}
|
||||
*/
|
||||
export const ENTER_KEY = 13;
|
||||
/**
|
||||
* Tab key code
|
||||
* @type {Number}
|
||||
*/
|
||||
export const TAB_KEY = 9;
|
||||
/**
|
||||
* Escape key code
|
||||
* @type {Number}
|
||||
*/
|
||||
export const ESC_KEY = 27;
|
||||
/**
|
||||
* Up arrow key code
|
||||
* @type {Number}
|
||||
*/
|
||||
export const UP_ARROW_KEY = 38;
|
||||
/**
|
||||
* Down arrow key code
|
||||
* @type {Number}
|
||||
*/
|
||||
export const DOWN_ARROW_KEY = 40;
|
||||
|
||||
/**
|
||||
* HTML tags
|
||||
*/
|
||||
|
||||
/**
|
||||
* Header cell tag
|
||||
* @type {String}
|
||||
*/
|
||||
export const HEADER_TAG = 'TH';
|
||||
/**
|
||||
* Cell tag
|
||||
* @type {String}
|
||||
*/
|
||||
export const CELL_TAG = 'TD';
|
||||
|
||||
/**
|
||||
* Data types
|
||||
*/
|
||||
|
||||
/**
|
||||
* String
|
||||
* @type {String}
|
||||
*/
|
||||
export const STRING = 'string';
|
||||
|
||||
/**
|
||||
* Number
|
||||
* @type {String}
|
||||
*/
|
||||
export const NUMBER = 'number';
|
||||
|
||||
/**
|
||||
* Formatted number
|
||||
* @type {String}
|
||||
*/
|
||||
export const FORMATTED_NUMBER = 'formatted-number';
|
||||
|
||||
/**
|
||||
* Date
|
||||
* @type {String}
|
||||
*/
|
||||
export const DATE = 'date';
|
||||
|
||||
/**
|
||||
* IP address
|
||||
* @type {String}
|
||||
*/
|
||||
export const IP_ADDRESS = 'ipaddress';
|
||||
|
||||
/**
|
||||
* Default values
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto filter delay in milliseconds
|
||||
* @type {Number}
|
||||
*/
|
||||
export const AUTO_FILTER_DELAY = 750;
|
|
@ -1,28 +1,43 @@
|
|||
import {root} from './root';
|
||||
|
||||
/**
|
||||
* Cookie utilities
|
||||
*/
|
||||
|
||||
const doc = root.document;
|
||||
|
||||
export default {
|
||||
|
||||
write(name, value, hours){
|
||||
/**
|
||||
* Write a cookie
|
||||
* @param {String} name Name of the cookie
|
||||
* @param {String} value Value of the cookie
|
||||
* @param {Number} hours Cookie duration in hours
|
||||
*/
|
||||
write(name, value, hours) {
|
||||
let expire = '';
|
||||
if(hours){
|
||||
if (hours) {
|
||||
expire = new Date((new Date()).getTime() + hours * 3600000);
|
||||
expire = '; expires=' + expire.toGMTString();
|
||||
}
|
||||
document.cookie = name + '=' + escape(value) + expire;
|
||||
doc.cookie = name + '=' + escape(value) + expire;
|
||||
},
|
||||
|
||||
read(name){
|
||||
/**
|
||||
* Read a cookie
|
||||
* @param {String} name Name of the cookie
|
||||
* @returns {String} Value of the cookie
|
||||
*/
|
||||
read(name) {
|
||||
let cookieValue = '',
|
||||
search = name + '=';
|
||||
if(document.cookie.length > 0){
|
||||
let cookie = document.cookie,
|
||||
if (doc.cookie.length > 0) {
|
||||
let cookie = doc.cookie,
|
||||
offset = cookie.indexOf(search);
|
||||
if(offset !== -1){
|
||||
if (offset !== -1) {
|
||||
offset += search.length;
|
||||
let end = cookie.indexOf(';', offset);
|
||||
if(end === -1){
|
||||
if (end === -1) {
|
||||
end = cookie.length;
|
||||
}
|
||||
cookieValue = unescape(cookie.substring(offset, end));
|
||||
|
@ -31,28 +46,12 @@ export default {
|
|||
return cookieValue;
|
||||
},
|
||||
|
||||
remove(name){
|
||||
/**
|
||||
* Remove a cookie
|
||||
* @param {String} name Name of the cookie
|
||||
*/
|
||||
remove(name) {
|
||||
this.write(name, '', -1);
|
||||
},
|
||||
|
||||
valueToArray(name, separator){
|
||||
if(!separator){
|
||||
separator = ',';
|
||||
}
|
||||
//reads the cookie
|
||||
let val = this.read(name);
|
||||
//creates an array with filters' values
|
||||
let arr = val.split(separator);
|
||||
return arr;
|
||||
},
|
||||
|
||||
getValueByIndex(name, index, separator){
|
||||
if(!separator){
|
||||
separator = ',';
|
||||
}
|
||||
//reads the cookie
|
||||
let val = this.valueToArray(name, separator);
|
||||
return val[index];
|
||||
}
|
||||
|
||||
};
|
||||
|
|
172
src/date.js
|
@ -1,172 +0,0 @@
|
|||
/**
|
||||
* Date utilities
|
||||
*/
|
||||
|
||||
export default {
|
||||
isValid(dateStr, format){
|
||||
if(!format) {
|
||||
format = 'DMY';
|
||||
}
|
||||
format = format.toUpperCase();
|
||||
if(format.length != 3) {
|
||||
if(format==='DDMMMYYYY'){
|
||||
let d = this.format(dateStr, format);
|
||||
dateStr = d.getDate() +'/'+ (d.getMonth()+1) +'/'+
|
||||
d.getFullYear();
|
||||
format = 'DMY';
|
||||
}
|
||||
}
|
||||
if((format.indexOf('M') === -1) || (format.indexOf('D') === -1) ||
|
||||
(format.indexOf('Y') === -1)){
|
||||
format = 'DMY';
|
||||
}
|
||||
let reg1, reg2;
|
||||
// If the year is first
|
||||
if(format.substring(0, 1) == 'Y') {
|
||||
reg1 = /^\d{2}(\-|\/|\.)\d{1,2}\1\d{1,2}$/;
|
||||
reg2 = /^\d{4}(\-|\/|\.)\d{1,2}\1\d{1,2}$/;
|
||||
} else if(format.substring(1, 2) == 'Y') { // If the year is second
|
||||
reg1 = /^\d{1,2}(\-|\/|\.)\d{2}\1\d{1,2}$/;
|
||||
reg2 = /^\d{1,2}(\-|\/|\.)\d{4}\1\d{1,2}$/;
|
||||
} else { // The year must be third
|
||||
reg1 = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{2}$/;
|
||||
reg2 = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{4}$/;
|
||||
}
|
||||
// If it doesn't conform to the right format (with either a 2 digit year
|
||||
// or 4 digit year), fail
|
||||
if(reg1.test(dateStr) === false && reg2.test(dateStr) === false) {
|
||||
return false;
|
||||
}
|
||||
// Split into 3 parts based on what the divider was
|
||||
let parts = dateStr.split(RegExp.$1);
|
||||
let mm, dd, yy;
|
||||
// Check to see if the 3 parts end up making a valid date
|
||||
if(format.substring(0, 1) === 'M'){
|
||||
mm = parts[0];
|
||||
} else if(format.substring(1, 2) === 'M'){
|
||||
mm = parts[1];
|
||||
} else {
|
||||
mm = parts[2];
|
||||
}
|
||||
if(format.substring(0, 1) === 'D'){
|
||||
dd = parts[0];
|
||||
} else if(format.substring(1, 2) === 'D'){
|
||||
dd = parts[1];
|
||||
} else {
|
||||
dd = parts[2];
|
||||
}
|
||||
if(format.substring(0, 1) === 'Y'){
|
||||
yy = parts[0];
|
||||
} else if(format.substring(1, 2) === 'Y'){
|
||||
yy = parts[1];
|
||||
} else {
|
||||
yy = parts[2];
|
||||
}
|
||||
if(parseInt(yy, 10) <= 50){
|
||||
yy = (parseInt(yy, 10) + 2000).toString();
|
||||
}
|
||||
if(parseInt(yy, 10) <= 99){
|
||||
yy = (parseInt(yy, 10) + 1900).toString();
|
||||
}
|
||||
let dt = new Date(
|
||||
parseInt(yy, 10), parseInt(mm, 10)-1, parseInt(dd, 10),
|
||||
0, 0, 0, 0);
|
||||
if(parseInt(dd, 10) != dt.getDate()){
|
||||
return false;
|
||||
}
|
||||
if(parseInt(mm, 10)-1 != dt.getMonth()){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
format(dateStr, formatStr) {
|
||||
if(!formatStr){
|
||||
formatStr = 'DMY';
|
||||
}
|
||||
if(!dateStr || dateStr === ''){
|
||||
return new Date(1001, 0, 1);
|
||||
}
|
||||
let oDate;
|
||||
let parts;
|
||||
|
||||
switch(formatStr.toUpperCase()){
|
||||
case 'DDMMMYYYY':
|
||||
parts = dateStr.replace(/[- \/.]/g,' ').split(' ');
|
||||
oDate = new Date(y2kDate(parts[2]),mmm2mm(parts[1])-1,parts[0]);
|
||||
break;
|
||||
case 'DMY':
|
||||
/* jshint ignore:start */
|
||||
parts = dateStr.replace(
|
||||
/^(0?[1-9]|[12][0-9]|3[01])([- \/.])(0?[1-9]|1[012])([- \/.])((\d\d)?\d\d)$/,'$1 $3 $5').split(' ');
|
||||
oDate = new Date(y2kDate(parts[2]),parts[1]-1,parts[0]);
|
||||
/* jshint ignore:end */
|
||||
break;
|
||||
case 'MDY':
|
||||
/* jshint ignore:start */
|
||||
parts = dateStr.replace(
|
||||
/^(0?[1-9]|1[012])([- \/.])(0?[1-9]|[12][0-9]|3[01])([- \/.])((\d\d)?\d\d)$/,'$1 $3 $5').split(' ');
|
||||
oDate = new Date(y2kDate(parts[2]),parts[0]-1,parts[1]);
|
||||
/* jshint ignore:end */
|
||||
break;
|
||||
case 'YMD':
|
||||
/* jshint ignore:start */
|
||||
parts = dateStr.replace(/^((\d\d)?\d\d)([- \/.])(0?[1-9]|1[012])([- \/.])(0?[1-9]|[12][0-9]|3[01])$/,'$1 $4 $6').split(' ');
|
||||
oDate = new Date(y2kDate(parts[0]),parts[1]-1,parts[2]);
|
||||
/* jshint ignore:end */
|
||||
break;
|
||||
default: //in case format is not correct
|
||||
/* jshint ignore:start */
|
||||
parts = dateStr.replace(/^(0?[1-9]|[12][0-9]|3[01])([- \/.])(0?[1-9]|1[012])([- \/.])((\d\d)?\d\d)$/,'$1 $3 $5').split(' ');
|
||||
oDate = new Date(y2kDate(parts[2]),parts[1]-1,parts[0]);
|
||||
/* jshint ignore:end */
|
||||
break;
|
||||
}
|
||||
return oDate;
|
||||
}
|
||||
};
|
||||
|
||||
function y2kDate(yr){
|
||||
if(yr === undefined){
|
||||
return 0;
|
||||
}
|
||||
if(yr.length>2){
|
||||
return yr;
|
||||
}
|
||||
let y;
|
||||
//>50 belong to 1900
|
||||
if(yr <= 99 && yr>50){
|
||||
y = '19' + yr;
|
||||
}
|
||||
//<50 belong to 2000
|
||||
if(yr<50 || yr === '00'){
|
||||
y = '20' + yr;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
function mmm2mm(mmm){
|
||||
if(mmm === undefined){
|
||||
return 0;
|
||||
}
|
||||
let mondigit;
|
||||
let MONTH_NAMES = [
|
||||
'january','february','march','april','may','june','july',
|
||||
'august','september','october','november','december',
|
||||
'jan','feb','mar','apr','may','jun','jul','aug','sep','oct',
|
||||
'nov','dec'
|
||||
];
|
||||
for(let m_i=0; m_i < MONTH_NAMES.length; m_i++){
|
||||
let month_name = MONTH_NAMES[m_i];
|
||||
if (mmm.toLowerCase() === month_name){
|
||||
mondigit = m_i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(mondigit > 11 || mondigit < 23){
|
||||
mondigit = mondigit - 12;
|
||||
}
|
||||
if(mondigit < 1 || mondigit > 12){
|
||||
return 0;
|
||||
}
|
||||
return mondigit;
|
||||
}
|
310
src/dom.js
|
@ -1,142 +1,192 @@
|
|||
import {root} from './root';
|
||||
import {isArray, isString, isUndef} from './types';
|
||||
import {trim} from './string';
|
||||
|
||||
/**
|
||||
* DOM utilities
|
||||
*/
|
||||
|
||||
export default {
|
||||
const doc = root.document;
|
||||
|
||||
/**
|
||||
* Returns text + text of children of given node
|
||||
* @param {NodeElement} node
|
||||
* @return {String}
|
||||
*/
|
||||
getText(node){
|
||||
let s = node.textContent || node.innerText ||
|
||||
node.innerHTML.replace(/<[^<>]+>/g, '');
|
||||
s = s.replace(/^\s+/, '').replace(/\s+$/, '');
|
||||
return s;
|
||||
},
|
||||
/**
|
||||
* Returns text + text of children of given node
|
||||
* @param {NodeElement} node
|
||||
* @return {String}
|
||||
*/
|
||||
export const getText = (node) => {
|
||||
if (isUndef(node.textContent)) {
|
||||
return trim(node.innerText);
|
||||
}
|
||||
return trim(node.textContent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an html element with given collection of attributes
|
||||
* @param {String} tag a string of the html tag to create
|
||||
* @param {Array} an undetermined number of arrays containing the with 2
|
||||
* items, the attribute name and its value ['id','myId']
|
||||
* @return {Object} created element
|
||||
*/
|
||||
create(tag){
|
||||
if(!tag || tag===''){
|
||||
return;
|
||||
/**
|
||||
* Returns the first text node contained in the supplied node
|
||||
* @param {NodeElement} node node
|
||||
* @return {String}
|
||||
*/
|
||||
export const getFirstTextNode = (node) => {
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
let n = node.childNodes[i];
|
||||
if (n.nodeType === 3) {
|
||||
return n.data;
|
||||
}
|
||||
|
||||
let el = document.createElement(tag),
|
||||
args = arguments;
|
||||
|
||||
if(args.length > 1){
|
||||
for(let i=0; i<args.length; i++){
|
||||
let argtype = typeof args[i];
|
||||
if(argtype.toLowerCase() === 'object' && args[i].length === 2){
|
||||
el.setAttribute(args[i][0], args[i][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return el;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a text node with given text
|
||||
* @param {String} txt
|
||||
* @return {Object}
|
||||
*/
|
||||
text(txt){
|
||||
return document.createTextNode(txt);
|
||||
},
|
||||
|
||||
hasClass(ele, cls){
|
||||
if(!ele){ return false; }
|
||||
|
||||
if(supportsClassList()){
|
||||
return ele.classList.contains(cls);
|
||||
}
|
||||
return ele.className.match(new RegExp('(\\s|^)'+ cls +'(\\s|$)'));
|
||||
},
|
||||
|
||||
addClass(ele, cls){
|
||||
if(!ele){ return; }
|
||||
|
||||
if(supportsClassList()){
|
||||
ele.classList.add(cls);
|
||||
return;
|
||||
}
|
||||
|
||||
if(ele.className === ''){
|
||||
ele.className = cls;
|
||||
}
|
||||
else if(!this.hasClass(ele, cls)){
|
||||
ele.className += ' ' + cls;
|
||||
}
|
||||
},
|
||||
|
||||
removeClass(ele, cls){
|
||||
if(!ele){ return; }
|
||||
|
||||
if(supportsClassList()){
|
||||
ele.classList.remove(cls);
|
||||
return;
|
||||
}
|
||||
let reg = new RegExp('(\\s|^)'+ cls +'(\\s|$)', 'g');
|
||||
ele.className = ele.className.replace(reg, '');
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and returns an option element
|
||||
* @param {String} text option text
|
||||
* @param {String} value option value
|
||||
* @param {Boolean} isSel whether option is selected
|
||||
* @return {Object} option element
|
||||
*/
|
||||
createOpt(text, value, isSel){
|
||||
let isSelected = isSel ? true : false,
|
||||
opt = isSelected ?
|
||||
this.create('option', ['value',value], ['selected','true']) :
|
||||
this.create('option', ['value',value]);
|
||||
opt.appendChild(this.text(text));
|
||||
return opt;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and returns a checklist item
|
||||
* @param {Number} chkIndex index of check item
|
||||
* @param {String} chkValue check item value
|
||||
* @param {String} labelText check item label text
|
||||
* @return {Object} li DOM element
|
||||
*/
|
||||
createCheckItem(chkIndex, chkValue, labelText){
|
||||
let li = this.create('li'),
|
||||
label = this.create('label', ['for', chkIndex]),
|
||||
check = this.create('input',
|
||||
['id', chkIndex],
|
||||
['name', chkIndex],
|
||||
['type', 'checkbox'],
|
||||
['value', chkValue]
|
||||
);
|
||||
label.appendChild(check);
|
||||
label.appendChild(this.text(labelText));
|
||||
li.appendChild(label);
|
||||
li.label = label;
|
||||
li.check = check;
|
||||
return li;
|
||||
},
|
||||
|
||||
id(_id){
|
||||
return document.getElementById(_id);
|
||||
},
|
||||
|
||||
tag(o, tagname){
|
||||
return o.getElementsByTagName(tagname);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an html element with given collection of attributes
|
||||
* @param {String} tag html tag name
|
||||
* @param {Array} an undetermined number of arrays containing the with 2
|
||||
* items, the attribute name and its value ['id','myId']
|
||||
* @return {Object} created element
|
||||
*/
|
||||
export const createElm = (...args) => {
|
||||
let tag = args[0];
|
||||
if (!isString(tag)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let el = doc.createElement(tag);
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
let arg = args[i];
|
||||
|
||||
if (isArray(arg) && arg.length === 2) {
|
||||
el.setAttribute(arg[0], arg[1]);
|
||||
}
|
||||
}
|
||||
return el;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes passed node from DOM
|
||||
* @param {DOMElement} node
|
||||
* @return {DOMElement} old node reference
|
||||
*/
|
||||
export const removeElm = (node) => node.parentNode.removeChild(node);
|
||||
|
||||
/**
|
||||
* Returns a text node with given text
|
||||
* @param {String} txt
|
||||
* @return {Object}
|
||||
*/
|
||||
export const createText = (txt) => doc.createTextNode(txt);
|
||||
|
||||
/**
|
||||
* Determine whether the passed elements is assigned the given class
|
||||
* @param {DOMElement} ele DOM element
|
||||
* @param {String} cls CSS class name
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export const hasClass = (ele, cls) => {
|
||||
if (isUndef(ele)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (supportsClassList()) {
|
||||
return ele.classList.contains(cls);
|
||||
}
|
||||
return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the specified class to the passed element
|
||||
* @param {DOMElement} ele DOM element
|
||||
* @param {String} cls CSS class name
|
||||
*/
|
||||
export const addClass = (ele, cls) => {
|
||||
if (isUndef(ele)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (supportsClassList()) {
|
||||
ele.classList.add(cls);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ele.className === '') {
|
||||
ele.className = cls;
|
||||
}
|
||||
else if (!hasClass(ele, cls)) {
|
||||
ele.className += ' ' + cls;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the specified class to the passed element
|
||||
* @param {DOMElement} ele DOM element
|
||||
* @param {String} cls CSS class name
|
||||
*/
|
||||
export const removeClass = (ele, cls) => {
|
||||
if (isUndef(ele)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (supportsClassList()) {
|
||||
ele.classList.remove(cls);
|
||||
return;
|
||||
}
|
||||
let reg = new RegExp('(\\s|^)' + cls + '(\\s|$)', 'g');
|
||||
ele.className = ele.className.replace(reg, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and returns an option element
|
||||
* @param {String} text option text
|
||||
* @param {String} value option value
|
||||
* @param {Boolean} isSel whether option is selected
|
||||
* @return {Object} option element
|
||||
*/
|
||||
export const createOpt = (text, value, isSel) => {
|
||||
let isSelected = isSel ? true : false;
|
||||
let opt = isSelected ?
|
||||
createElm('option', ['value', value], ['selected', 'true']) :
|
||||
createElm('option', ['value', value]);
|
||||
opt.appendChild(createText(text));
|
||||
return opt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and returns a checklist item
|
||||
* @param {String} id index of check item
|
||||
* @param {String} chkValue check item value
|
||||
* @param {String} labelText check item label text
|
||||
* @param {Array} extraAttr array containing attribute name and its value
|
||||
* @return {Object} li DOM element
|
||||
*/
|
||||
export const createCheckItem = (id, chkValue, labelText, extraAttr = []) => {
|
||||
let li = createElm('li');
|
||||
let label = createElm('label', ['for', id]);
|
||||
let check = createElm('input',
|
||||
['id', id],
|
||||
['name', id],
|
||||
['type', 'checkbox'],
|
||||
['value', chkValue],
|
||||
extraAttr
|
||||
);
|
||||
label.appendChild(check);
|
||||
label.appendChild(createText(labelText));
|
||||
li.appendChild(label);
|
||||
li.label = label;
|
||||
li.check = check;
|
||||
return li;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the element matching the supplied Id
|
||||
* @param {String} id Element identifier
|
||||
* @return {DOMElement}
|
||||
*/
|
||||
export const elm = (id) => doc.getElementById(id);
|
||||
|
||||
/**
|
||||
* Returns list of element matching the supplied tag name
|
||||
* @param {String} tagname Tag name
|
||||
* @return {NodeList}
|
||||
*/
|
||||
export const tag = (o, tagname) => o.getElementsByTagName(tagname);
|
||||
|
||||
// HTML5 classList API
|
||||
function supportsClassList(){
|
||||
return document.documentElement.classList;
|
||||
function supportsClassList() {
|
||||
return doc.documentElement.classList;
|
||||
}
|
||||
|
|
53
src/emitter.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Event emitter class
|
||||
*/
|
||||
export class Emitter {
|
||||
/**
|
||||
* Creates an instance of Emitter.
|
||||
*/
|
||||
constructor() {
|
||||
/**
|
||||
* Events object
|
||||
* @type {Object}
|
||||
*/
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to an event
|
||||
* @param {Array} evts Collection of event names
|
||||
* @param {Function} fn Function invoked when event is emitted
|
||||
*/
|
||||
on(evts, fn) {
|
||||
evts.forEach((evt) => {
|
||||
this.events[evt] = this.events[evt] || [];
|
||||
this.events[evt].push(fn);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe to an event
|
||||
* @param {Array} evts Collection of event names
|
||||
* @param {Function} fn Function invoked when event is emitted
|
||||
*/
|
||||
off(evts, fn) {
|
||||
evts.forEach((evt) => {
|
||||
if (evt in this.events) {
|
||||
this.events[evt].splice(this.events[evt].indexOf(fn), 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event
|
||||
* @param {String} evt Event name followed by any other argument passed to
|
||||
* the invoked function
|
||||
*/
|
||||
emit(evt /*, args...*/) {
|
||||
if (evt in this.events) {
|
||||
for (let i = 0; i < this.events[evt].length; i++) {
|
||||
this.events[evt][i].apply(this, [].slice.call(arguments, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
165
src/event.js
|
@ -1,53 +1,124 @@
|
|||
import {root} from './root';
|
||||
|
||||
/**
|
||||
* DOM event utilities
|
||||
*/
|
||||
|
||||
export default {
|
||||
add(obj, type, func, capture){
|
||||
if(obj.addEventListener){
|
||||
obj.addEventListener(type, func, capture);
|
||||
}
|
||||
else if(obj.attachEvent){
|
||||
obj.attachEvent('on'+type, func);
|
||||
} else {
|
||||
obj['on'+type] = func;
|
||||
}
|
||||
},
|
||||
remove(obj, type, func, capture){
|
||||
if(obj.detachEvent){
|
||||
obj.detachEvent('on'+type,func);
|
||||
}
|
||||
else if(obj.removeEventListener){
|
||||
obj.removeEventListener(type, func, capture);
|
||||
} else {
|
||||
obj['on'+type] = null;
|
||||
}
|
||||
},
|
||||
stop(evt){
|
||||
if(!evt){
|
||||
evt = window.event;
|
||||
}
|
||||
if(evt.stopPropagation){
|
||||
evt.stopPropagation();
|
||||
} else {
|
||||
evt.cancelBubble = true;
|
||||
}
|
||||
},
|
||||
cancel(evt){
|
||||
if(!evt){
|
||||
evt = window.event;
|
||||
}
|
||||
if(evt.preventDefault) {
|
||||
evt.preventDefault();
|
||||
} else {
|
||||
evt.returnValue = false;
|
||||
}
|
||||
},
|
||||
target(evt){
|
||||
return (evt && evt.target) || (window.event && window.event.srcElement);
|
||||
},
|
||||
keyCode(evt){
|
||||
return evt.charCode ? evt.charCode :
|
||||
(evt.keyCode ? evt.keyCode: (evt.which ? evt.which : 0));
|
||||
/**
|
||||
* Add event handler for specified event on passed element
|
||||
*
|
||||
* @param {DOMElement} obj Element
|
||||
* @param {String} type Event type
|
||||
* @param {Function} Handler
|
||||
* @param {Boolean} capture Specifiy whether the event should be executed in
|
||||
* the capturing or in the bubbling phase
|
||||
*/
|
||||
export const addEvt = (obj, type, func, capture) => {
|
||||
if (obj.addEventListener) {
|
||||
obj.addEventListener(type, func, capture);
|
||||
}
|
||||
else if (obj.attachEvent) {
|
||||
obj.attachEvent('on' + type, func);
|
||||
} else {
|
||||
obj['on' + type] = func;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove event handler for specified event on passed element
|
||||
*
|
||||
* @param {DOMElement} obj Element
|
||||
* @param {String} type Event type
|
||||
* @param {Function} Handler
|
||||
* @param {Boolean} capture Specifiy whether the event should be executed in
|
||||
* the capturing or in the bubbling phase
|
||||
*/
|
||||
export const removeEvt = (obj, type, func, capture) => {
|
||||
if (obj.removeEventListener) {
|
||||
obj.removeEventListener(type, func, capture);
|
||||
} else if (obj.detachEvent) {
|
||||
obj.detachEvent('on' + type, func);
|
||||
} else {
|
||||
obj['on' + type] = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prevents further propagation of the current event in the bubbling phase
|
||||
*
|
||||
* @param {Event} evt Event on the DOM
|
||||
*/
|
||||
export const stopEvt = (evt) => {
|
||||
if (!evt) {
|
||||
evt = root.event;
|
||||
}
|
||||
if (evt.stopPropagation) {
|
||||
evt.stopPropagation();
|
||||
} else {
|
||||
evt.cancelBubble = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancels the event if it is cancelable, without stopping further
|
||||
* propagation of the event.
|
||||
*
|
||||
* @param {Event} evt Event on the DOM
|
||||
*/
|
||||
export const cancelEvt = (evt) => {
|
||||
if (!evt) {
|
||||
evt = root.event;
|
||||
}
|
||||
if (evt.preventDefault) {
|
||||
evt.preventDefault();
|
||||
} else {
|
||||
evt.returnValue = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reference to the object that dispatched the event
|
||||
*
|
||||
* @param {Event} evt Event on the DOM
|
||||
* @returns {DOMElement}
|
||||
*/
|
||||
export const targetEvt = (evt) => {
|
||||
if (!evt) {
|
||||
evt = root.event;
|
||||
}
|
||||
return evt.target || evt.srcElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Unicode value of pressed key
|
||||
*
|
||||
* @param {Event} evt Event on the DOM
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const keyCode = (evt) => {
|
||||
return evt.charCode ? evt.charCode :
|
||||
(evt.keyCode ? evt.keyCode : (evt.which ? evt.which : 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check code of pressed key is one of the expected key codes
|
||||
*
|
||||
* @param {Event} evt key event
|
||||
* @param {Array} keyCodes list of keycodes to check
|
||||
*/
|
||||
export const isKeyPressed = (evt, keyCodes = []) => {
|
||||
return keyCodes.indexOf(keyCode(evt)) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind passed function to passed scope
|
||||
* @param {Function} fn function
|
||||
* @param {Object} scope object instance
|
||||
*/
|
||||
export function bound(fn, scope) {
|
||||
let boundFnName = `${fn.name}_bound`;
|
||||
if (!scope[boundFnName]) {
|
||||
scope[boundFnName] = fn.bind(scope);
|
||||
}
|
||||
return scope[boundFnName];
|
||||
}
|
||||
|
|
|
@ -1,117 +1,190 @@
|
|||
import Dom from '../../dom';
|
||||
import Arr from '../../array';
|
||||
import {Feature} from '../../feature';
|
||||
import {tag} from '../../dom';
|
||||
import {INPUT} from '../../const';
|
||||
import {defaultsStr} from '../../settings';
|
||||
import {root} from '../../root';
|
||||
|
||||
const INSTANTIATION_ERROR = `Failed to instantiate EditTable object.
|
||||
\n"ezEditTable" dependency not found.`;
|
||||
|
||||
/**
|
||||
* Adapter module for ezEditTable, an external library providing advanced
|
||||
* grid features (selection and edition):
|
||||
* http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus
|
||||
*/
|
||||
export default class AdapterEzEditTable extends Feature {
|
||||
|
||||
export default class AdapterEzEditTable {
|
||||
/**
|
||||
* Adapter module for ezEditTable, an external library providing advanced
|
||||
* grid features (selection and edition):
|
||||
* http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus
|
||||
* Creates an instance of AdapterEzEditTable
|
||||
*
|
||||
* @param {Object} tf TableFilter instance
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
* @param {Object} cfg Configuration options for ezEditTable library
|
||||
*/
|
||||
constructor(tf, cfg){
|
||||
// ezEditTable config
|
||||
this.initialized = false;
|
||||
this.desc = cfg.description || 'ezEditTable adapter';
|
||||
this.filename = cfg.filename || 'ezEditTable.js';
|
||||
constructor(tf, cfg) {
|
||||
super(tf, AdapterEzEditTable);
|
||||
|
||||
/**
|
||||
* Module description
|
||||
* @type {String}
|
||||
*/
|
||||
this.desc = defaultsStr(cfg.description, 'ezEditTable adapter');
|
||||
|
||||
/**
|
||||
* Filename of ezEditTable library
|
||||
* @type {String}
|
||||
*/
|
||||
this.filename = defaultsStr(cfg.filename, 'ezEditTable.js');
|
||||
|
||||
/**
|
||||
* Path to ezEditTable library
|
||||
* @type {String}
|
||||
*/
|
||||
this.vendorPath = cfg.vendor_path;
|
||||
|
||||
/**
|
||||
* Load ezEditTable stylesheet
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loadStylesheet = Boolean(cfg.load_stylesheet);
|
||||
this.stylesheet = cfg.stylesheet || this.vendorPath + 'ezEditTable.css';
|
||||
this.stylesheetName = cfg.stylesheet_name || 'ezEditTableCss';
|
||||
this.err = 'Failed to instantiate EditTable object.\n"ezEditTable" ' +
|
||||
'dependency not found.';
|
||||
|
||||
/**
|
||||
* Path to ezEditTable stylesheet
|
||||
* @type {String}
|
||||
*/
|
||||
this.stylesheet = defaultsStr(cfg.stylesheet,
|
||||
this.vendorPath + 'ezEditTable.css');
|
||||
|
||||
/**
|
||||
* Name of ezEditTable stylesheet
|
||||
* @type {String}
|
||||
*/
|
||||
this.stylesheetName = defaultsStr(cfg.stylesheet_name,
|
||||
'ezEditTableCss');
|
||||
|
||||
// Enable the ezEditTable's scroll into view behaviour if grid layout on
|
||||
cfg.scroll_into_view = cfg.scroll_into_view===false ?
|
||||
cfg.scroll_into_view = cfg.scroll_into_view === false ?
|
||||
false : tf.gridLayout;
|
||||
|
||||
/**
|
||||
* ezEditTable instance
|
||||
* @type {EditTable}
|
||||
* @private
|
||||
*/
|
||||
this._ezEditTable = null;
|
||||
|
||||
/**
|
||||
* ezEditTable configuration
|
||||
* @private
|
||||
*/
|
||||
this.cfg = cfg;
|
||||
this.tf = tf;
|
||||
|
||||
this.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally load ezEditTable library and set advanced grid
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
init(){
|
||||
var tf = this.tf;
|
||||
if(window.EditTable){
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
let tf = this.tf;
|
||||
if (root.EditTable) {
|
||||
this._setAdvancedGrid();
|
||||
} else {
|
||||
var path = this.vendorPath + this.filename;
|
||||
tf.import(this.filename, path, ()=> { this._setAdvancedGrid(); });
|
||||
let path = this.vendorPath + this.filename;
|
||||
tf.import(this.filename, path, () => this._setAdvancedGrid());
|
||||
}
|
||||
if(this.loadStylesheet && !tf.isImported(this.stylesheet, 'link')){
|
||||
if (this.loadStylesheet && !tf.isImported(this.stylesheet, 'link')) {
|
||||
tf.import(this.stylesheetName, this.stylesheet, null, 'link');
|
||||
}
|
||||
|
||||
// TODO: hack to prevent ezEditTable enter key event hijaking.
|
||||
// Needs to be fixed in the vendor's library
|
||||
this.emitter.on(['filter-focus', 'filter-blur'],
|
||||
() => this._toggleForInputFilter());
|
||||
|
||||
/**
|
||||
* @inherited
|
||||
*/
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate ezEditTable component for advanced grid features
|
||||
* @private
|
||||
*/
|
||||
_setAdvancedGrid(){
|
||||
var tf = this.tf;
|
||||
_setAdvancedGrid() {
|
||||
let tf = this.tf;
|
||||
|
||||
//start row for EditTable constructor needs to be calculated
|
||||
var startRow,
|
||||
let startRow,
|
||||
cfg = this.cfg,
|
||||
thead = Dom.tag(tf.tbl, 'thead');
|
||||
thead = tag(tf.dom(), 'thead');
|
||||
|
||||
//if thead exists and startRow not specified, startRow is calculated
|
||||
//automatically by EditTable
|
||||
if(thead.length > 0 && !cfg.startRow){
|
||||
if (thead.length > 0 && !cfg.startRow) {
|
||||
startRow = undefined;
|
||||
}
|
||||
//otherwise startRow config property if any or TableFilter refRow
|
||||
else{
|
||||
else {
|
||||
startRow = cfg.startRow || tf.refRow;
|
||||
}
|
||||
|
||||
cfg.base_path = cfg.base_path || tf.basePath + 'ezEditTable/';
|
||||
var editable = cfg.editable;
|
||||
var selectable = cfg.selection;
|
||||
cfg.base_path = cfg.base_path || tf.basePath + 'ezEditTable/';
|
||||
let editable = cfg.editable;
|
||||
let selectable = cfg.selection;
|
||||
|
||||
if(selectable){
|
||||
if (selectable) {
|
||||
cfg.default_selection = cfg.default_selection || 'row';
|
||||
}
|
||||
//CSS Styles
|
||||
cfg.active_cell_css = cfg.active_cell_css || 'ezETSelectedCell';
|
||||
|
||||
var _lastValidRowIndex = 0;
|
||||
var _lastRowIndex = 0;
|
||||
let _lastValidRowIndex = 0;
|
||||
let _lastRowIndex = 0;
|
||||
|
||||
if(selectable){
|
||||
if (selectable) {
|
||||
//Row navigation needs to be calculated according to TableFilter's
|
||||
//validRowsIndex array
|
||||
var onAfterSelection = function(et, selectedElm, e){
|
||||
var slc = et.Selection;
|
||||
let onAfterSelection = function (et, selectedElm, e) {
|
||||
let slc = et.Selection;
|
||||
//Next valid filtered row needs to be selected
|
||||
var doSelect = function(nextRowIndex){
|
||||
if(et.defaultSelection === 'row'){
|
||||
let doSelect = function (nextRowIndex) {
|
||||
if (et.defaultSelection === 'row') {
|
||||
/* eslint-disable */
|
||||
slc.SelectRowByIndex(nextRowIndex);
|
||||
/* eslint-enable */
|
||||
} else {
|
||||
/* eslint-disable */
|
||||
et.ClearSelections();
|
||||
var cellIndex = selectedElm.cellIndex,
|
||||
row = tf.tbl.rows[nextRowIndex];
|
||||
if(et.defaultSelection === 'both'){
|
||||
/* eslint-enable */
|
||||
let cellIndex = selectedElm.cellIndex,
|
||||
row = tf.dom().rows[nextRowIndex];
|
||||
if (et.defaultSelection === 'both') {
|
||||
/* eslint-disable */
|
||||
slc.SelectRowByIndex(nextRowIndex);
|
||||
/* eslint-enable */
|
||||
}
|
||||
if(row){
|
||||
if (row) {
|
||||
/* eslint-disable */
|
||||
slc.SelectCell(row.cells[cellIndex]);
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
//Table is filtered
|
||||
if(tf.validRowsIndex.length !== tf.getRowsNb()){
|
||||
var r = tf.tbl.rows[nextRowIndex];
|
||||
if(r){
|
||||
if (tf.validRowsIndex.length !== tf.getRowsNb()) {
|
||||
let r = tf.dom().rows[nextRowIndex];
|
||||
if (r) {
|
||||
r.scrollIntoView(false);
|
||||
}
|
||||
if(cell){
|
||||
if(cell.cellIndex === (tf.getCellsNb()-1) &&
|
||||
tf.gridLayout){
|
||||
if (cell) {
|
||||
if (cell.cellIndex === (tf.getCellsNb() - 1) &&
|
||||
tf.gridLayout) {
|
||||
tf.tblCont.scrollLeft = 100000000;
|
||||
}
|
||||
else if(cell.cellIndex===0 && tf.gridLayout){
|
||||
else if (cell.cellIndex === 0 && tf.gridLayout) {
|
||||
tf.tblCont.scrollLeft = 0;
|
||||
} else {
|
||||
cell.scrollIntoView(false);
|
||||
|
@ -121,45 +194,48 @@ export default class AdapterEzEditTable {
|
|||
};
|
||||
|
||||
//table is not filtered
|
||||
if(!tf.validRowsIndex){
|
||||
if (!tf.validRowsIndex) {
|
||||
return;
|
||||
}
|
||||
var validIndexes = tf.validRowsIndex,
|
||||
let validIndexes = tf.validRowsIndex,
|
||||
validIdxLen = validIndexes.length,
|
||||
row = et.defaultSelection !== 'row' ?
|
||||
selectedElm.parentNode : selectedElm,
|
||||
//cell for default_selection = 'both' or 'cell'
|
||||
cell = selectedElm.nodeName==='TD' ? selectedElm : null,
|
||||
cell = selectedElm.nodeName === 'TD' ? selectedElm : null,
|
||||
/* eslint-disable */
|
||||
keyCode = e !== undefined ? et.Event.GetKey(e) : 0,
|
||||
isRowValid = Arr.has(validIndexes, row.rowIndex),
|
||||
/* eslint-enable */
|
||||
isRowValid = validIndexes.indexOf(row.rowIndex) !== -1,
|
||||
nextRowIndex,
|
||||
paging = tf.feature('paging'),
|
||||
//pgup/pgdown keys
|
||||
d = (keyCode === 34 || keyCode === 33 ?
|
||||
(tf.feature('paging').pagingLength ||
|
||||
et.nbRowsPerPage) : 1);
|
||||
d = keyCode === 34 || keyCode === 33 ?
|
||||
(paging && paging.pageLength || et.nbRowsPerPage) :
|
||||
1;
|
||||
|
||||
//If next row is not valid, next valid filtered row needs to be
|
||||
//calculated
|
||||
if(!isRowValid){
|
||||
if (!isRowValid) {
|
||||
//Selection direction up/down
|
||||
if(row.rowIndex>_lastRowIndex){
|
||||
if (row.rowIndex > _lastRowIndex) {
|
||||
//last row
|
||||
if(row.rowIndex >= validIndexes[validIdxLen-1]){
|
||||
nextRowIndex = validIndexes[validIdxLen-1];
|
||||
if (row.rowIndex >= validIndexes[validIdxLen - 1]) {
|
||||
nextRowIndex = validIndexes[validIdxLen - 1];
|
||||
} else {
|
||||
var calcRowIndex = (_lastValidRowIndex + d);
|
||||
if(calcRowIndex > (validIdxLen-1)){
|
||||
nextRowIndex = validIndexes[validIdxLen-1];
|
||||
let calcRowIndex = (_lastValidRowIndex + d);
|
||||
if (calcRowIndex > (validIdxLen - 1)) {
|
||||
nextRowIndex = validIndexes[validIdxLen - 1];
|
||||
} else {
|
||||
nextRowIndex = validIndexes[calcRowIndex];
|
||||
}
|
||||
}
|
||||
} else{
|
||||
} else {
|
||||
//first row
|
||||
if(row.rowIndex <= validIndexes[0]){
|
||||
if (row.rowIndex <= validIndexes[0]) {
|
||||
nextRowIndex = validIndexes[0];
|
||||
} else {
|
||||
var v = validIndexes[_lastValidRowIndex - d];
|
||||
let v = validIndexes[_lastValidRowIndex - d];
|
||||
nextRowIndex = v ? v : validIndexes[0];
|
||||
}
|
||||
}
|
||||
|
@ -168,22 +244,21 @@ export default class AdapterEzEditTable {
|
|||
} else {
|
||||
//If filtered row is valid, special calculation for
|
||||
//pgup/pgdown keys
|
||||
if(keyCode!==34 && keyCode!==33){
|
||||
_lastValidRowIndex = Arr.indexByValue(validIndexes,
|
||||
row.rowIndex);
|
||||
if (keyCode !== 34 && keyCode !== 33) {
|
||||
_lastValidRowIndex = validIndexes.indexOf(row.rowIndex);
|
||||
_lastRowIndex = row.rowIndex;
|
||||
} else {
|
||||
if(keyCode === 34){ //pgdown
|
||||
if (keyCode === 34) { //pgdown
|
||||
//last row
|
||||
if((_lastValidRowIndex + d) <= (validIdxLen-1)){
|
||||
if ((_lastValidRowIndex + d) <= (validIdxLen - 1)) {
|
||||
nextRowIndex = validIndexes[
|
||||
_lastValidRowIndex + d];
|
||||
_lastValidRowIndex + d];
|
||||
} else {
|
||||
nextRowIndex = [validIdxLen-1];
|
||||
nextRowIndex = [validIdxLen - 1];
|
||||
}
|
||||
} else { //pgup
|
||||
//first row
|
||||
if((_lastValidRowIndex - d) <= validIndexes[0]){
|
||||
if ((_lastValidRowIndex - d) <= validIndexes[0]) {
|
||||
nextRowIndex = validIndexes[0];
|
||||
} else {
|
||||
nextRowIndex = validIndexes[
|
||||
|
@ -191,8 +266,7 @@ export default class AdapterEzEditTable {
|
|||
}
|
||||
}
|
||||
_lastRowIndex = nextRowIndex;
|
||||
_lastValidRowIndex = Arr.indexByValue(validIndexes,
|
||||
nextRowIndex);
|
||||
_lastValidRowIndex = validIndexes.indexOf(nextRowIndex);
|
||||
doSelect(nextRowIndex);
|
||||
}
|
||||
}
|
||||
|
@ -200,35 +274,35 @@ export default class AdapterEzEditTable {
|
|||
|
||||
//Page navigation has to be enforced whenever selected row is out of
|
||||
//the current page range
|
||||
var onBeforeSelection = function(et, selectedElm){
|
||||
var row = et.defaultSelection !== 'row' ?
|
||||
let onBeforeSelection = function (et, selectedElm) {
|
||||
let row = et.defaultSelection !== 'row' ?
|
||||
selectedElm.parentNode : selectedElm;
|
||||
if(tf.paging){
|
||||
if(tf.feature('paging').nbPages > 1){
|
||||
var paging = tf.feature('paging');
|
||||
if (tf.paging) {
|
||||
if (tf.feature('paging').nbPages > 1) {
|
||||
let paging = tf.feature('paging');
|
||||
//page length is re-assigned in case it has changed
|
||||
et.nbRowsPerPage = paging.pagingLength;
|
||||
var validIndexes = tf.validRowsIndex,
|
||||
et.nbRowsPerPage = paging.pageLength;
|
||||
let validIndexes = tf.validRowsIndex,
|
||||
validIdxLen = validIndexes.length,
|
||||
pagingEndRow = parseInt(paging.startPagingRow, 10) +
|
||||
parseInt(paging.pagingLength, 10);
|
||||
var rowIndex = row.rowIndex;
|
||||
parseInt(paging.pageLength, 10);
|
||||
let rowIndex = row.rowIndex;
|
||||
|
||||
if((rowIndex === validIndexes[validIdxLen-1]) &&
|
||||
paging.currentPageNb!==paging.nbPages){
|
||||
if ((rowIndex === validIndexes[validIdxLen - 1]) &&
|
||||
paging.currentPageNb !== paging.nbPages) {
|
||||
paging.setPage('last');
|
||||
}
|
||||
else if((rowIndex == validIndexes[0]) &&
|
||||
paging.currentPageNb!==1){
|
||||
else if ((rowIndex === validIndexes[0]) &&
|
||||
paging.currentPageNb !== 1) {
|
||||
paging.setPage('first');
|
||||
}
|
||||
else if(rowIndex > validIndexes[pagingEndRow-1] &&
|
||||
rowIndex < validIndexes[validIdxLen-1]){
|
||||
else if (rowIndex > validIndexes[pagingEndRow - 1] &&
|
||||
rowIndex < validIndexes[validIdxLen - 1]) {
|
||||
paging.setPage('next');
|
||||
}
|
||||
else if(
|
||||
else if (
|
||||
rowIndex < validIndexes[paging.startPagingRow] &&
|
||||
rowIndex > validIndexes[0]){
|
||||
rowIndex > validIndexes[0]) {
|
||||
paging.setPage('previous');
|
||||
}
|
||||
}
|
||||
|
@ -236,17 +310,21 @@ export default class AdapterEzEditTable {
|
|||
};
|
||||
|
||||
//Selected row needs to be visible when paging is activated
|
||||
if(tf.paging){
|
||||
tf.feature('paging').onAfterChangePage = function(paging){
|
||||
var advGrid = paging.tf.extension('advancedGrid');
|
||||
var et = advGrid._ezEditTable;
|
||||
var slc = et.Selection;
|
||||
var row = slc.GetActiveRow();
|
||||
if(row){
|
||||
if (tf.paging) {
|
||||
tf.feature('paging').onAfterChangePage = function (paging) {
|
||||
let advGrid = paging.tf.extension('advancedGrid');
|
||||
let et = advGrid._ezEditTable;
|
||||
let slc = et.Selection;
|
||||
/* eslint-disable */
|
||||
let row = slc.GetActiveRow();
|
||||
/* eslint-enable */
|
||||
if (row) {
|
||||
row.scrollIntoView(false);
|
||||
}
|
||||
var cell = slc.GetActiveCell();
|
||||
if(cell){
|
||||
/* eslint-disable */
|
||||
let cell = slc.GetActiveCell();
|
||||
/* eslint-enable */
|
||||
if (cell) {
|
||||
cell.scrollIntoView(false);
|
||||
}
|
||||
};
|
||||
|
@ -254,92 +332,92 @@ export default class AdapterEzEditTable {
|
|||
|
||||
//Rows navigation when rows are filtered is performed with the
|
||||
//EditTable row selection callback events
|
||||
if(cfg.default_selection==='row'){
|
||||
var fnB = cfg.on_before_selected_row;
|
||||
cfg.on_before_selected_row = function(){
|
||||
onBeforeSelection(arguments[0], arguments[1], arguments[2]);
|
||||
if(fnB){
|
||||
fnB.call(
|
||||
null, arguments[0], arguments[1], arguments[2]);
|
||||
if (cfg.default_selection === 'row') {
|
||||
let fnB = cfg.on_before_selected_row;
|
||||
cfg.on_before_selected_row = function () {
|
||||
var args = arguments;
|
||||
onBeforeSelection(args[0], args[1], args[2]);
|
||||
if (fnB) {
|
||||
fnB.call(null, args[0], args[1], args[2]);
|
||||
}
|
||||
};
|
||||
var fnA = cfg.on_after_selected_row;
|
||||
cfg.on_after_selected_row = function(){
|
||||
onAfterSelection(arguments[0], arguments[1], arguments[2]);
|
||||
if(fnA){
|
||||
fnA.call(
|
||||
null, arguments[0], arguments[1], arguments[2]);
|
||||
let fnA = cfg.on_after_selected_row;
|
||||
cfg.on_after_selected_row = function () {
|
||||
var args = arguments;
|
||||
onAfterSelection(args[0], args[1], args[2]);
|
||||
if (fnA) {
|
||||
fnA.call(null, args[0], args[1], args[2]);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
var fnD = cfg.on_before_selected_cell;
|
||||
cfg.on_before_selected_cell = function(){
|
||||
onBeforeSelection(arguments[0], arguments[1], arguments[2]);
|
||||
if(fnD){
|
||||
fnD.call(
|
||||
null, arguments[0], arguments[1], arguments[2]);
|
||||
let fnD = cfg.on_before_selected_cell;
|
||||
cfg.on_before_selected_cell = function () {
|
||||
var args = arguments;
|
||||
onBeforeSelection(args[0], args[1], args[2]);
|
||||
if (fnD) {
|
||||
fnD.call(null, args[0], args[1], args[2]);
|
||||
}
|
||||
};
|
||||
var fnC = cfg.on_after_selected_cell;
|
||||
cfg.on_after_selected_cell = function(){
|
||||
onAfterSelection(arguments[0], arguments[1], arguments[2]);
|
||||
if(fnC){
|
||||
fnC.call(
|
||||
null, arguments[0], arguments[1], arguments[2]);
|
||||
let fnC = cfg.on_after_selected_cell;
|
||||
cfg.on_after_selected_cell = function () {
|
||||
var args = arguments;
|
||||
onAfterSelection(args[0], args[1], args[2]);
|
||||
if (fnC) {
|
||||
fnC.call(null, args[0], args[1], args[2]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if(editable){
|
||||
if (editable) {
|
||||
//Added or removed rows, TF rows number needs to be re-calculated
|
||||
var fnE = cfg.on_added_dom_row;
|
||||
cfg.on_added_dom_row = function(){
|
||||
let fnE = cfg.on_added_dom_row;
|
||||
cfg.on_added_dom_row = function () {
|
||||
var args = arguments;
|
||||
tf.nbFilterableRows++;
|
||||
if(!tf.paging){
|
||||
tf.feature('rowsCounter').refresh();
|
||||
if (!tf.paging) {
|
||||
tf.emitter.emit('rows-changed', tf, this);
|
||||
} else {
|
||||
tf.nbRows++;
|
||||
tf.nbVisibleRows++;
|
||||
tf.nbFilterableRows++;
|
||||
tf.paging=false;
|
||||
tf.paging = false;
|
||||
tf.feature('paging').destroy();
|
||||
tf.feature('paging').reset();
|
||||
}
|
||||
if(tf.alternateBgs){
|
||||
if (tf.alternateRows) {
|
||||
tf.feature('alternateRows').init();
|
||||
}
|
||||
if(fnE){
|
||||
fnE.call(null, arguments[0], arguments[1], arguments[2]);
|
||||
if (fnE) {
|
||||
fnE.call(null, args[0], args[1], args[2]);
|
||||
}
|
||||
};
|
||||
if(cfg.actions && cfg.actions['delete']){
|
||||
var fnF = cfg.actions['delete'].on_after_submit;
|
||||
cfg.actions['delete'].on_after_submit = function(){
|
||||
if (cfg.actions && cfg.actions['delete']) {
|
||||
let fnF = cfg.actions['delete'].on_after_submit;
|
||||
cfg.actions['delete'].on_after_submit = function () {
|
||||
var args = arguments;
|
||||
tf.nbFilterableRows--;
|
||||
if(!tf.paging){
|
||||
tf.feature('rowsCounter').refresh();
|
||||
if (!tf.paging) {
|
||||
tf.emitter.emit('rows-changed', tf, this);
|
||||
} else {
|
||||
tf.nbRows--;
|
||||
tf.nbVisibleRows--;
|
||||
tf.nbFilterableRows--;
|
||||
tf.paging=false;
|
||||
tf.paging = false;
|
||||
tf.feature('paging').destroy();
|
||||
tf.feature('paging').reset(false);
|
||||
}
|
||||
if(tf.alternateBgs){
|
||||
if (tf.alternateRows) {
|
||||
tf.feature('alternateRows').init();
|
||||
}
|
||||
if(fnF){
|
||||
fnF.call(null, arguments[0], arguments[1]);
|
||||
if (fnF) {
|
||||
fnF.call(null, args[0], args[1]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
try {
|
||||
/* eslint-disable */
|
||||
this._ezEditTable = new EditTable(tf.id, cfg, startRow);
|
||||
this._ezEditTable.Init();
|
||||
} catch(e) { throw new Error(this.err); }
|
||||
/* eslint-enable */
|
||||
} catch (e) { throw new Error(INSTANTIATION_ERROR); }
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
@ -347,32 +425,85 @@ export default class AdapterEzEditTable {
|
|||
/**
|
||||
* Reset advanced grid when previously removed
|
||||
*/
|
||||
reset(){
|
||||
var ezEditTable = this._ezEditTable;
|
||||
if(ezEditTable){
|
||||
if(this.cfg.selection){
|
||||
reset() {
|
||||
let ezEditTable = this._ezEditTable;
|
||||
if (ezEditTable) {
|
||||
if (this.cfg.selection) {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Selection.Set();
|
||||
/* eslint-enable */
|
||||
}
|
||||
if(this.cfg.editable){
|
||||
if (this.cfg.editable) {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Editable.Set();
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle behaviour
|
||||
*/
|
||||
toggle() {
|
||||
let ezEditTable = this._ezEditTable;
|
||||
if (ezEditTable.editable) {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Editable.Remove();
|
||||
/* eslint-enable */
|
||||
} else {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Editable.Set();
|
||||
/* eslint-enable */
|
||||
}
|
||||
if (ezEditTable.selection) {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Selection.Remove();
|
||||
/* eslint-enable */
|
||||
} else {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Selection.Set();
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
|
||||
_toggleForInputFilter() {
|
||||
let tf = this.tf;
|
||||
if (!tf.getActiveFilterId()) {
|
||||
return;
|
||||
}
|
||||
let colIndex = tf.getColumnIndexFromFilterId(tf.getActiveFilterId());
|
||||
let filterType = tf.getFilterType(colIndex);
|
||||
if (filterType === INPUT) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove advanced grid
|
||||
*/
|
||||
destroy(){
|
||||
var ezEditTable = this._ezEditTable;
|
||||
if(ezEditTable){
|
||||
if(this.cfg.selection){
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
let ezEditTable = this._ezEditTable;
|
||||
if (ezEditTable) {
|
||||
if (this.cfg.selection) {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Selection.ClearSelections();
|
||||
ezEditTable.Selection.Remove();
|
||||
/* eslint-enable */
|
||||
}
|
||||
if(this.cfg.editable){
|
||||
if (this.cfg.editable) {
|
||||
/* eslint-disable */
|
||||
ezEditTable.Editable.Remove();
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
|
||||
this.emitter.off(['filter-focus', 'filter-blur'],
|
||||
() => this._toggleForInputFilter());
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
AdapterEzEditTable.meta = {altName: 'advancedGrid'};
|
||||
|
|
|
@ -1,28 +1,127 @@
|
|||
import Dom from '../../dom';
|
||||
import Str from '../../string';
|
||||
import Types from '../../types';
|
||||
import {Feature} from '../../feature';
|
||||
import {createText, elm} from '../../dom';
|
||||
import {isArray, isEmpty, EMPTY_FN} from '../../types';
|
||||
import {numSortAsc} from '../../sort';
|
||||
import {FORMATTED_NUMBER} from '../../const';
|
||||
import formatNumber from 'format-number';
|
||||
import {defaultsFn, defaultsArr} from '../../settings';
|
||||
import {bound} from '../../event';
|
||||
|
||||
export default class ColOps{
|
||||
const EVENTS = [
|
||||
'after-filtering',
|
||||
'after-page-change',
|
||||
'after-page-length-change'
|
||||
];
|
||||
|
||||
const SUM = 'sum';
|
||||
const MEAN = 'mean';
|
||||
const MIN = 'min';
|
||||
const MAX = 'max';
|
||||
const MEDIAN = 'median';
|
||||
const Q1 = 'q1';
|
||||
const Q3 = 'q3';
|
||||
|
||||
/**
|
||||
* Column calculations extension
|
||||
*/
|
||||
export default class ColOps extends Feature {
|
||||
|
||||
/**
|
||||
* Column calculations
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of ColOps
|
||||
*
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
* @param {Object} opts Configuration object
|
||||
*/
|
||||
constructor(tf, opts) {
|
||||
super(tf, ColOps);
|
||||
|
||||
//calls function before col operation
|
||||
this.onBeforeOperation = Types.isFn(opts.on_before_operation) ?
|
||||
opts.on_before_operation : null;
|
||||
//calls function after col operation
|
||||
this.onAfterOperation = Types.isFn(opts.on_after_operation) ?
|
||||
opts.on_after_operation : null;
|
||||
/**
|
||||
* Callback fired before columns operations start
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeOperation = defaultsFn(opts.on_before_operation, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after columns operations are completed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterOperation = defaultsFn(opts.on_after_operation, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Configuration options
|
||||
* @type {Object}
|
||||
*/
|
||||
this.opts = opts;
|
||||
this.tf = tf;
|
||||
|
||||
/**
|
||||
* List of DOM element IDs containing column's calculation result
|
||||
* @type {Array}
|
||||
*/
|
||||
this.labelIds = defaultsArr(opts.id, []);
|
||||
|
||||
/**
|
||||
* List of columns' indexes for calculations
|
||||
* @type {Array}
|
||||
*/
|
||||
this.colIndexes = defaultsArr(opts.col, []);
|
||||
|
||||
/**
|
||||
* List of operations - possible values: 'sum', 'mean', 'min', 'max',
|
||||
* 'median', 'q1', 'q3'
|
||||
* @type {Array}
|
||||
*/
|
||||
this.operations = defaultsArr(opts.operation, []);
|
||||
|
||||
/**
|
||||
* List of write methods used to write the result - possible values:
|
||||
* 'innerHTML', 'setValue', 'createTextNode'
|
||||
* @type {Array}
|
||||
*/
|
||||
this.outputTypes = defaultsArr(opts.write_method, []);
|
||||
|
||||
/**
|
||||
* List of format objects used for formatting the result -
|
||||
* refer to https://github.com/componitable/format-number to check
|
||||
* configuration options
|
||||
* @type {Array}
|
||||
*/
|
||||
this.formatResults = defaultsArr(opts.format_result, []);
|
||||
|
||||
/**
|
||||
* List of row indexes displaying the results
|
||||
* @type {Array}
|
||||
*/
|
||||
this.totRowIndexes = defaultsArr(opts.tot_row_index, []);
|
||||
|
||||
/**
|
||||
* List of row indexes excluded from calculations
|
||||
* @type {Array}
|
||||
*/
|
||||
this.excludeRows = defaultsArr(opts.exclude_row, []);
|
||||
|
||||
/**
|
||||
* List of decimal precision for calculation results
|
||||
* @type {Array}
|
||||
*/
|
||||
this.decimalPrecisions = defaultsArr(opts.decimal_precision, 2);
|
||||
|
||||
this.enable();
|
||||
}
|
||||
|
||||
init(){
|
||||
this.calc();
|
||||
/**
|
||||
* Initializes ColOps instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
// subscribe to events
|
||||
this.emitter.on(EVENTS, bound(this.calcAll, this));
|
||||
|
||||
this.calcAll();
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,275 +140,341 @@ export default class ColOps{
|
|||
* (1) optimized the routine (now it will only process each column once),
|
||||
* (2) added calculations for the median, lower and upper quartile.
|
||||
*/
|
||||
calc() {
|
||||
var tf = this.tf;
|
||||
if(!tf.isFirstLoad && !tf.hasGrid()){
|
||||
calcAll() {
|
||||
let tf = this.tf;
|
||||
if (!tf.isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.onBeforeOperation){
|
||||
this.onBeforeOperation.call(null, tf);
|
||||
}
|
||||
this.onBeforeOperation(tf, this);
|
||||
this.emitter.emit('before-column-operation', tf, this);
|
||||
|
||||
var opts = this.opts,
|
||||
labelId = opts.id,
|
||||
colIndex = opts.col,
|
||||
operation = opts.operation,
|
||||
outputType = opts.write_method,
|
||||
totRowIndex = opts.tot_row_index,
|
||||
excludeRow = opts.exclude_row,
|
||||
decimalPrecision = Types.isUndef(opts.decimal_precision) ?
|
||||
2 : opts.decimal_precision;
|
||||
let { colIndexes, operations: colOperations, outputTypes,
|
||||
totRowIndexes, excludeRows, formatResults,
|
||||
decimalPrecisions } = this;
|
||||
|
||||
//nuovella: determine unique list of columns to operate on
|
||||
var ucolIndex = [],
|
||||
ucolMax = 0;
|
||||
ucolIndex[ucolMax] = colIndex[0];
|
||||
let uIndexes = [];
|
||||
colIndexes.forEach((val) => {
|
||||
if (uIndexes.indexOf(val) === -1) {
|
||||
uIndexes.push(val);
|
||||
}
|
||||
});
|
||||
|
||||
for(var ii=1; ii<colIndex.length; ii++){
|
||||
var saved = 0;
|
||||
//see if colIndex[ii] is already in the list of unique indexes
|
||||
for(var jj=0; jj<=ucolMax; jj++){
|
||||
if(ucolIndex[jj] === colIndex[ii]){
|
||||
saved = 1;
|
||||
let nbCols = uIndexes.length,
|
||||
rows = tf.dom().rows,
|
||||
colValues = [];
|
||||
|
||||
for (let u = 0; u < nbCols; u++) {
|
||||
//this retrieves col values
|
||||
//use uIndexes because we only want to pass through this loop
|
||||
//once for each column get the values in this unique column
|
||||
colValues.push(
|
||||
tf.getVisibleColumnData(uIndexes[u], false, excludeRows)
|
||||
);
|
||||
|
||||
let curValues = colValues[u];
|
||||
|
||||
//next: calculate all operations for this column
|
||||
let result = 0,
|
||||
operations = [],
|
||||
precisions = [],
|
||||
labels = [],
|
||||
writeType,
|
||||
formatResult = [],
|
||||
idx = 0;
|
||||
|
||||
for (let k = 0; k < colIndexes.length; k++) {
|
||||
if (colIndexes[k] !== uIndexes[u]) {
|
||||
continue;
|
||||
}
|
||||
operations[idx] = (colOperations[k] || 'sum').toLowerCase();
|
||||
precisions[idx] = decimalPrecisions[k];
|
||||
labels[idx] = this.labelIds[k];
|
||||
writeType = isArray(outputTypes) ? outputTypes[k] : null;
|
||||
formatResult[idx] =
|
||||
this.configureFormat(uIndexes[u], formatResults[k]);
|
||||
idx++;
|
||||
}
|
||||
//if not saved then, save the index;
|
||||
if (saved === 0){
|
||||
ucolMax++;
|
||||
ucolIndex[ucolMax] = colIndex[ii];
|
||||
|
||||
for (let i = 0; i < idx; i++) {
|
||||
// emit values before column calculation
|
||||
this.emitter.emit(
|
||||
'before-column-calc',
|
||||
tf,
|
||||
this,
|
||||
uIndexes[u],
|
||||
curValues,
|
||||
operations[i],
|
||||
precisions[i]
|
||||
);
|
||||
|
||||
result = Number(this.calc(curValues, operations[i], null));
|
||||
|
||||
// emit column calculation result
|
||||
this.emitter.emit(
|
||||
'column-calc',
|
||||
tf,
|
||||
this,
|
||||
uIndexes[u],
|
||||
result,
|
||||
operations[i],
|
||||
precisions[i]
|
||||
);
|
||||
|
||||
// write result in expected DOM element
|
||||
this.writeResult(
|
||||
result,
|
||||
labels[i],
|
||||
writeType,
|
||||
precisions[i],
|
||||
formatResult[i]
|
||||
);
|
||||
|
||||
}//for i
|
||||
|
||||
// row(s) with result are always visible
|
||||
let totRow = totRowIndexes && totRowIndexes[u] ?
|
||||
rows[totRowIndexes[u]] : null;
|
||||
if (totRow) {
|
||||
totRow.style.display = '';
|
||||
}
|
||||
}//for u
|
||||
|
||||
this.onAfterOperation(tf, this);
|
||||
this.emitter.emit('after-column-operation', tf, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make desired calculation on specified column.
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {String} [operation=SUM] Operation type
|
||||
* @param {Number} precision Decimal precision
|
||||
* @returns {Number}
|
||||
*/
|
||||
columnCalc(colIndex, operation = SUM, precision) {
|
||||
let excludeRows = this.excludeRows || [];
|
||||
let colValues = tf.getVisibleColumnData(colIndex, false, excludeRows);
|
||||
|
||||
return Number(this.calc(colValues, operation, precision));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make calculation on passed values.
|
||||
* @param {Array} values List of values
|
||||
* @param {String} [operation=SUM] Optional operation type
|
||||
* @param {Number} precision Optional result precision
|
||||
* @returns {Number}
|
||||
* @private
|
||||
*/
|
||||
calc(colValues, operation = SUM, precision) {
|
||||
let result = 0;
|
||||
|
||||
if (operation === Q1 || operation === Q3 || operation === MEDIAN) {
|
||||
colValues = this.sortColumnValues(colValues, numSortAsc);
|
||||
}
|
||||
|
||||
if(Str.lower(typeof labelId)=='object' &&
|
||||
Str.lower(typeof colIndex)=='object' &&
|
||||
Str.lower(typeof operation)=='object'){
|
||||
var rows = tf.tbl.rows,
|
||||
colvalues = [];
|
||||
switch (operation) {
|
||||
case MEAN:
|
||||
result = this.calcMean(colValues);
|
||||
break;
|
||||
case SUM:
|
||||
result = this.calcSum(colValues);
|
||||
break;
|
||||
case MIN:
|
||||
result = this.calcMin(colValues);
|
||||
break;
|
||||
case MAX:
|
||||
result = this.calcMax(colValues);
|
||||
break;
|
||||
case MEDIAN:
|
||||
result = this.calcMedian(colValues);
|
||||
break;
|
||||
case Q1:
|
||||
result = this.calcQ1(colValues);
|
||||
break;
|
||||
case Q3:
|
||||
result = this.calcQ3(colValues);
|
||||
break;
|
||||
}
|
||||
|
||||
for(var ucol=0; ucol<=ucolMax; ucol++){
|
||||
//this retrieves col values
|
||||
//use ucolIndex because we only want to pass through this loop
|
||||
//once for each column get the values in this unique column
|
||||
colvalues.push(
|
||||
tf.getColValues(ucolIndex[ucol], true, excludeRow));
|
||||
return isEmpty(precision) ? result : result.toFixed(precision);
|
||||
}
|
||||
|
||||
//next: calculate all operations for this column
|
||||
var result,
|
||||
nbvalues=0,
|
||||
temp,
|
||||
meanValue=0,
|
||||
sumValue=0,
|
||||
minValue=null,
|
||||
maxValue=null,
|
||||
q1Value=null,
|
||||
medValue=null,
|
||||
q3Value=null,
|
||||
meanFlag=0,
|
||||
sumFlag=0,
|
||||
minFlag=0,
|
||||
maxFlag=0,
|
||||
q1Flag=0,
|
||||
medFlag=0,
|
||||
q3Flag=0,
|
||||
theList=[],
|
||||
opsThisCol=[],
|
||||
decThisCol=[],
|
||||
labThisCol=[],
|
||||
oTypeThisCol=[],
|
||||
mThisCol=-1;
|
||||
/**
|
||||
* Calculate the sum of passed values.
|
||||
* @param {Array} [values=[]] List of values
|
||||
* @returns {Number}
|
||||
*/
|
||||
calcSum(values = []) {
|
||||
if (isEmpty(values)) {
|
||||
return 0;
|
||||
}
|
||||
let result = values.reduce((x, y) => Number(x) + Number(y));
|
||||
return result;
|
||||
}
|
||||
|
||||
for(var k=0; k<colIndex.length; k++){
|
||||
if(colIndex[k] === ucolIndex[ucol]){
|
||||
mThisCol++;
|
||||
opsThisCol[mThisCol]=Str.lower(operation[k]);
|
||||
decThisCol[mThisCol]=decimalPrecision[k];
|
||||
labThisCol[mThisCol]=labelId[k];
|
||||
oTypeThisCol = outputType !== undefined &&
|
||||
Str.lower(typeof outputType)==='object' ?
|
||||
outputType[k] : null;
|
||||
/**
|
||||
* Calculate the mean of passed values.
|
||||
* @param {Array} [values=[]] List of values
|
||||
* @returns {Number}
|
||||
*/
|
||||
calcMean(values = []) {
|
||||
let result = this.calcSum(values) / values.length;
|
||||
return Number(result);
|
||||
}
|
||||
|
||||
switch(opsThisCol[mThisCol]){
|
||||
case 'mean':
|
||||
meanFlag=1;
|
||||
break;
|
||||
case 'sum':
|
||||
sumFlag=1;
|
||||
break;
|
||||
case 'min':
|
||||
minFlag=1;
|
||||
break;
|
||||
case 'max':
|
||||
maxFlag=1;
|
||||
break;
|
||||
case 'median':
|
||||
medFlag=1;
|
||||
break;
|
||||
case 'q1':
|
||||
q1Flag=1;
|
||||
break;
|
||||
case 'q3':
|
||||
q3Flag=1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate the max value of passed values.
|
||||
* @param {Array} [values=[]] List of values
|
||||
* @returns {Number}
|
||||
*/
|
||||
calcMax(values = []) {
|
||||
return Math.max.apply(null, values);
|
||||
}
|
||||
|
||||
for(var j=0; j<colvalues[ucol].length; j++){
|
||||
//sort the list for calculation of median and quartiles
|
||||
if((q1Flag==1)|| (q3Flag==1) || (medFlag==1)){
|
||||
if (j<colvalues[ucol].length -1){
|
||||
for(k=j+1; k<colvalues[ucol].length; k++) {
|
||||
if(eval(colvalues[ucol][k]) <
|
||||
eval(colvalues[ucol][j])){
|
||||
temp = colvalues[ucol][j];
|
||||
colvalues[ucol][j] = colvalues[ucol][k];
|
||||
colvalues[ucol][k] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var cvalue = parseFloat(colvalues[ucol][j]);
|
||||
theList[j] = parseFloat(cvalue);
|
||||
/**
|
||||
* Calculate the min value of passed values.
|
||||
* @param {Array} [values=[]] List of values
|
||||
* @returns {Number}
|
||||
*/
|
||||
calcMin(values = []) {
|
||||
return Math.min.apply(null, values);
|
||||
}
|
||||
|
||||
if(!isNaN(cvalue)){
|
||||
nbvalues++;
|
||||
if(sumFlag===1 || meanFlag===1){
|
||||
sumValue += parseFloat( cvalue );
|
||||
}
|
||||
if(minFlag===1){
|
||||
if(minValue===null){
|
||||
minValue = parseFloat( cvalue );
|
||||
} else{
|
||||
minValue = parseFloat( cvalue ) < minValue ?
|
||||
parseFloat( cvalue ): minValue;
|
||||
}
|
||||
}
|
||||
if(maxFlag===1){
|
||||
if (maxValue===null){
|
||||
maxValue = parseFloat( cvalue );
|
||||
} else {
|
||||
maxValue = parseFloat( cvalue ) > maxValue ?
|
||||
parseFloat( cvalue ): maxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}//for j
|
||||
if(meanFlag===1){
|
||||
meanValue = sumValue/nbvalues;
|
||||
}
|
||||
if(medFlag===1){
|
||||
var aux = 0;
|
||||
if(nbvalues%2 === 1){
|
||||
aux = Math.floor(nbvalues/2);
|
||||
medValue = theList[aux];
|
||||
} else{
|
||||
medValue =
|
||||
(theList[nbvalues/2] + theList[((nbvalues/2)-1)])/2;
|
||||
}
|
||||
}
|
||||
var posa;
|
||||
if(q1Flag===1){
|
||||
posa=0.0;
|
||||
posa = Math.floor(nbvalues/4);
|
||||
if(4*posa == nbvalues){
|
||||
q1Value = (theList[posa-1] + theList[posa])/2;
|
||||
} else {
|
||||
q1Value = theList[posa];
|
||||
}
|
||||
}
|
||||
if (q3Flag===1){
|
||||
posa=0.0;
|
||||
var posb=0.0;
|
||||
posa = Math.floor(nbvalues/4);
|
||||
if (4*posa === nbvalues){
|
||||
posb = 3*posa;
|
||||
q3Value = (theList[posb] + theList[posb-1])/2;
|
||||
} else {
|
||||
q3Value = theList[nbvalues-posa-1];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculate the median of passed values.
|
||||
* @param {Array} [values=[]] List of values
|
||||
* @returns {Number}
|
||||
*/
|
||||
calcMedian(values = []) {
|
||||
let nbValues = values.length;
|
||||
let aux = 0;
|
||||
if (nbValues % 2 === 1) {
|
||||
aux = Math.floor(nbValues / 2);
|
||||
return Number(values[aux]);
|
||||
}
|
||||
return (Number(values[nbValues / 2]) +
|
||||
Number(values[((nbValues / 2) - 1)])) / 2;
|
||||
}
|
||||
|
||||
for(var i=0; i<=mThisCol; i++){
|
||||
switch( opsThisCol[i] ){
|
||||
case 'mean':
|
||||
result=meanValue;
|
||||
break;
|
||||
case 'sum':
|
||||
result=sumValue;
|
||||
break;
|
||||
case 'min':
|
||||
result=minValue;
|
||||
break;
|
||||
case 'max':
|
||||
result=maxValue;
|
||||
break;
|
||||
case 'median':
|
||||
result=medValue;
|
||||
break;
|
||||
case 'q1':
|
||||
result=q1Value;
|
||||
break;
|
||||
case 'q3':
|
||||
result=q3Value;
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Calculate the lower quartile of passed values.
|
||||
* @param {Array} [values=[]] List of values
|
||||
* @returns {Number}
|
||||
*/
|
||||
calcQ1(values = []) {
|
||||
let nbValues = values.length;
|
||||
let posa = 0.0;
|
||||
posa = Math.floor(nbValues / 4);
|
||||
if (4 * posa === nbValues) {
|
||||
return (Number(values[posa - 1]) +
|
||||
Number(values[posa])) / 2;
|
||||
}
|
||||
return Number(values[posa]);
|
||||
}
|
||||
|
||||
var precision = !isNaN(decThisCol[i]) ? decThisCol[i] : 2;
|
||||
/**
|
||||
* Calculate the upper quartile of passed values.
|
||||
* @param {Array} [values=[]] List of values
|
||||
* @returns {Number}
|
||||
*/
|
||||
calcQ3(values = []) {
|
||||
let nbValues = values.length;
|
||||
let posa = 0.0;
|
||||
let posb = 0.0;
|
||||
posa = Math.floor(nbValues / 4);
|
||||
if (4 * posa === nbValues) {
|
||||
posb = 3 * posa;
|
||||
return (Number(values[posb]) +
|
||||
Number(values[posb - 1])) / 2;
|
||||
}
|
||||
return Number(values[nbValues - posa - 1]);
|
||||
}
|
||||
|
||||
//if outputType is defined
|
||||
if(oTypeThisCol && result){
|
||||
result = result.toFixed( precision );
|
||||
/**
|
||||
* Sort passed values with supplied sorter function.
|
||||
* @param {Array} [values=[]] List of values to be sorted
|
||||
* @param {Function} sorter Sorter function
|
||||
* @returns {Array}
|
||||
*/
|
||||
sortColumnValues(values = [], sorter) {
|
||||
return values.sort(sorter);
|
||||
}
|
||||
|
||||
if(Dom.id(labThisCol[i])){
|
||||
switch( Str.lower(oTypeThisCol) ){
|
||||
case 'innerhtml':
|
||||
if (isNaN(result) || !isFinite(result) ||
|
||||
nbvalues===0){
|
||||
Dom.id(labThisCol[i]).innerHTML = '.';
|
||||
} else{
|
||||
Dom.id(labThisCol[i]).innerHTML= result;
|
||||
}
|
||||
break;
|
||||
case 'setvalue':
|
||||
Dom.id( labThisCol[i] ).value = result;
|
||||
break;
|
||||
case 'createtextnode':
|
||||
var oldnode = Dom.id(labThisCol[i])
|
||||
.firstChild;
|
||||
var txtnode = Dom.text(result);
|
||||
Dom.id(labThisCol[i])
|
||||
.replaceChild(txtnode, oldnode);
|
||||
break;
|
||||
}//switch
|
||||
}
|
||||
} else {
|
||||
try{
|
||||
if(isNaN(result) || !isFinite(result) ||
|
||||
nbvalues===0){
|
||||
Dom.id(labThisCol[i]).innerHTML = '.';
|
||||
} else {
|
||||
Dom.id(labThisCol[i]).innerHTML =
|
||||
result.toFixed(precision);
|
||||
}
|
||||
} catch(e) {}//catch
|
||||
}//else
|
||||
}//for i
|
||||
/**
|
||||
* Write calculation result in passed DOM element with supplied write method
|
||||
* and decimal precision.
|
||||
* @param {Number} [result=0] Calculation result
|
||||
* @param {DOMElement} label DOM element
|
||||
* @param {String} [writeType='innerhtml'] Write method
|
||||
* @param {Number} [precision=2] Applied decimal precision
|
||||
* @private
|
||||
*/
|
||||
writeResult(result = 0, label, writeType = 'innerhtml',
|
||||
precision = 2, format = {}) {
|
||||
let labelElm = elm(label);
|
||||
|
||||
// row(s) with result are always visible
|
||||
var totRow = totRowIndex && totRowIndex[ucol] ?
|
||||
rows[totRowIndex[ucol]] : null;
|
||||
if(totRow){
|
||||
totRow.style.display = '';
|
||||
}
|
||||
}//for ucol
|
||||
}//if typeof
|
||||
if (!labelElm) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.onAfterOperation){
|
||||
this.onAfterOperation.call(null, tf);
|
||||
result = result.toFixed(precision);
|
||||
if (isNaN(result) || !isFinite(result)) {
|
||||
result = '';
|
||||
} else {
|
||||
result = formatNumber(format)(result);
|
||||
}
|
||||
|
||||
switch (writeType.toLowerCase()) {
|
||||
case 'innerhtml':
|
||||
labelElm.innerHTML = result;
|
||||
break;
|
||||
case 'setvalue':
|
||||
labelElm.value = result;
|
||||
break;
|
||||
case 'createtextnode':
|
||||
let oldNode = labelElm.firstChild;
|
||||
let txtNode = createText(result);
|
||||
labelElm.replaceChild(txtNode, oldNode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
destroy(){}
|
||||
/**
|
||||
* Configure the format options used to format the operation result based
|
||||
* on column type.
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Object} [format={}] Format object
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
configureFormat(colIndex, format = {}) {
|
||||
let tf = this.tf;
|
||||
if (tf.hasType(colIndex, [FORMATTED_NUMBER])) {
|
||||
let colType = tf.colTypes[colIndex];
|
||||
if (colType.decimal && !format.decimal) {
|
||||
format.decimal = colType.decimal;
|
||||
}
|
||||
if (colType.thousands && !format.integerSeparator) {
|
||||
format.integerSeparator = colType.thousands;
|
||||
}
|
||||
} else {
|
||||
format.decimal = format.decimal || '';
|
||||
format.integerSeparator = format.integerSeparator || '';
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
/** Remove extension */
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
// unsubscribe to events
|
||||
this.emitter.off(EVENTS, bound(this.calcAll, this));
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,274 +1,491 @@
|
|||
import Dom from '../../dom';
|
||||
import Types from '../../types';
|
||||
import Event from '../../event';
|
||||
import Arr from '../../array';
|
||||
import {Feature} from '../../feature';
|
||||
import {
|
||||
addClass, removeClass, createCheckItem, createElm, elm, removeElm,
|
||||
getText, tag
|
||||
} from '../../dom';
|
||||
import {isUndef, EMPTY_FN, isNull} from '../../types';
|
||||
import {addEvt, targetEvt, removeEvt} from '../../event';
|
||||
import {root} from '../../root';
|
||||
import {NONE} from '../../const';
|
||||
import {
|
||||
defaultsBool, defaultsStr, defaultsFn, defaultsNb, defaultsArr
|
||||
} from '../../settings';
|
||||
import {RIGHT} from '../../modules/toolbar';
|
||||
|
||||
export default class ColsVisibility{
|
||||
/**
|
||||
* Columns Visibility extension
|
||||
*/
|
||||
export default class ColsVisibility extends Feature {
|
||||
|
||||
/**
|
||||
* Columns Visibility extension
|
||||
* @param {Object} tf TableFilter instance
|
||||
* @param {Object} f Config
|
||||
* Creates an instance of ColsVisibility
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
* @param {Object} Configuration object
|
||||
*/
|
||||
constructor(tf, f){
|
||||
constructor(tf, f) {
|
||||
super(tf, ColsVisibility);
|
||||
|
||||
// Configuration object
|
||||
var cfg = tf.config();
|
||||
let cfg = this.config;
|
||||
|
||||
this.initialized = false;
|
||||
/**
|
||||
* Module name
|
||||
* @type {String}
|
||||
*/
|
||||
this.name = f.name;
|
||||
this.desc = f.description || 'Columns visibility manager';
|
||||
|
||||
//show/hide cols span element
|
||||
/**
|
||||
* Module description
|
||||
* @type {String}
|
||||
*/
|
||||
this.desc = defaultsStr(f.description, 'Columns visibility manager');
|
||||
|
||||
/**
|
||||
* show/hide columns container element
|
||||
* @private
|
||||
*/
|
||||
this.spanEl = null;
|
||||
//show/hide cols button element
|
||||
|
||||
/**
|
||||
* show/hide columns button element
|
||||
* @private
|
||||
*/
|
||||
this.btnEl = null;
|
||||
//show/hide cols container div element
|
||||
|
||||
/**
|
||||
* show/hide columns main container element
|
||||
* @private
|
||||
*/
|
||||
this.contEl = null;
|
||||
|
||||
//tick to hide or show column
|
||||
this.tickToHide = f.tick_to_hide===false ? false : true;
|
||||
//enables/disables cols manager generation
|
||||
this.manager = f.manager===false ? false : true;
|
||||
//only if external headers
|
||||
this.headersTbl = f.headers_table || false;
|
||||
//only if external headers
|
||||
this.headersIndex = f.headers_index || 1;
|
||||
//id of container element
|
||||
this.contElTgtId = f.container_target_id || null;
|
||||
//alternative headers text
|
||||
this.headersText = f.headers_text || null;
|
||||
//id of button container element
|
||||
this.btnTgtId = f.btn_target_id || null;
|
||||
//defines show/hide cols text
|
||||
this.btnText = f.btn_text || 'Columns▼';
|
||||
//defines show/hide cols button innerHtml
|
||||
this.btnHtml = f.btn_html || null;
|
||||
//defines css class for show/hide cols button
|
||||
this.btnCssClass = f.btn_css_class || 'colVis';
|
||||
//defines close link text
|
||||
this.btnCloseText = f.btn_close_text || 'Close';
|
||||
//defines close button innerHtml
|
||||
this.btnCloseHtml = f.btn_close_html || null;
|
||||
//defines css class for close button
|
||||
this.btnCloseCssClass = f.btn_close_css_class || this.btnCssClass;
|
||||
this.stylesheet = f.stylesheet || 'colsVisibility.css';
|
||||
//span containing show/hide cols button
|
||||
this.prfx = 'colVis_';
|
||||
//defines css class span containing show/hide cols
|
||||
this.spanCssClass = f.span_css_class || 'colVisSpan';
|
||||
this.prfxCont = this.prfx + 'Cont_';
|
||||
//defines css class div containing show/hide cols
|
||||
this.contCssClass = f.cont_css_class || 'colVisCont';
|
||||
//defines css class for cols list (ul)
|
||||
this.listCssClass = cfg.list_css_class ||'cols_checklist';
|
||||
//defines css class for list item (li)
|
||||
this.listItemCssClass = cfg.checklist_item_css_class ||
|
||||
'cols_checklist_item';
|
||||
//defines css class for selected list item (li)
|
||||
this.listSlcItemCssClass = cfg.checklist_selected_item_css_class ||
|
||||
'cols_checklist_slc_item';
|
||||
//text preceding columns list
|
||||
this.text = f.text || (this.tickToHide ? 'Hide: ' : 'Show: ');
|
||||
this.atStart = f.at_start || null;
|
||||
/**
|
||||
* Enable tick to hide a column, defaults to true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.tickToHide = defaultsBool(f.tick_to_hide, true);
|
||||
|
||||
/**
|
||||
* Enable columns manager UI, defaults to true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.manager = defaultsBool(f.manager, true);
|
||||
|
||||
/**
|
||||
* Headers HTML table reference only if headers are external
|
||||
* @type {DOMElement}
|
||||
*/
|
||||
this.headersTbl = f.headers_table || null;
|
||||
|
||||
/**
|
||||
* Headers row index only if headers are external
|
||||
* @type {Number}
|
||||
*/
|
||||
this.headersIndex = defaultsNb(f.headers_index, 1);
|
||||
|
||||
/**
|
||||
* ID of main container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.contElTgtId = defaultsStr(f.container_target_id, null);
|
||||
|
||||
/**
|
||||
* Alternative text for column headers in column manager UI
|
||||
* @type {Array}
|
||||
*/
|
||||
this.headersText = defaultsArr(f.headers_text, []);
|
||||
|
||||
/**
|
||||
* ID of button's container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnTgtId = defaultsStr(f.btn_target_id, null);
|
||||
|
||||
/**
|
||||
* Button's text, defaults to Columns▼
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnText = defaultsStr(f.btn_text, 'Columns▼');
|
||||
|
||||
/**
|
||||
* Button's inner HTML
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnHtml = defaultsStr(f.btn_html, null);
|
||||
|
||||
/**
|
||||
* Css class for button
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnCssClass = defaultsStr(f.btn_css_class, 'colVis');
|
||||
|
||||
/**
|
||||
* Columns manager UI close link text, defaults to 'Close'
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnCloseText = defaultsStr(f.btn_close_text, 'Close');
|
||||
|
||||
/**
|
||||
* Columns manager UI close link HTML
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnCloseHtml = defaultsStr(f.btn_close_html, null);
|
||||
|
||||
/**
|
||||
* Css for columns manager UI close link
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnCloseCssClass = defaultsStr(f.btn_close_css_class,
|
||||
this.btnCssClass);
|
||||
|
||||
/**
|
||||
* Extension's stylesheet filename
|
||||
* @type {String}
|
||||
*/
|
||||
this.stylesheet = defaultsStr(f.stylesheet, 'colsVisibility.css');
|
||||
|
||||
/**
|
||||
* Css for columns manager UI span
|
||||
* @type {String}
|
||||
*/
|
||||
this.spanCssClass = defaultsStr(f.span_css_class, 'colVisSpan');
|
||||
|
||||
/**
|
||||
* Css for columns manager UI main container
|
||||
* @type {String}
|
||||
*/
|
||||
this.contCssClass = defaultsStr(f.cont_css_class, 'colVisCont');
|
||||
|
||||
/**
|
||||
* Css for columns manager UI checklist (ul)
|
||||
* @type {String}
|
||||
*/
|
||||
this.listCssClass = defaultsStr(cfg.list_css_class, 'cols_checklist');
|
||||
|
||||
/**
|
||||
* Css for columns manager UI checklist item (li)
|
||||
* @type {String}
|
||||
*/
|
||||
this.listItemCssClass = defaultsStr(cfg.checklist_item_css_class,
|
||||
'cols_checklist_item');
|
||||
|
||||
/**
|
||||
* Css for columns manager UI checklist item selected state (li)
|
||||
* @type {String}
|
||||
*/
|
||||
this.listSlcItemCssClass = defaultsStr(
|
||||
cfg.checklist_selected_item_css_class,
|
||||
'cols_checklist_slc_item'
|
||||
);
|
||||
|
||||
/**
|
||||
* Text preceding the columns list, defaults to 'Hide' or 'Show'
|
||||
* depending on tick mode (tick_to_hide option)
|
||||
* @type {String}
|
||||
*/
|
||||
this.text = defaultsStr(f.text, this.tickToHide ? 'Hide: ' : 'Show: ');
|
||||
|
||||
/**
|
||||
* List of columns indexes to be hidden at initialization
|
||||
* @type {Array}
|
||||
*/
|
||||
this.atStart = defaultsArr(f.at_start, []);
|
||||
|
||||
/**
|
||||
* Enable hover behaviour on columns manager button/link
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableHover = Boolean(f.enable_hover);
|
||||
//enables select all option
|
||||
|
||||
/**
|
||||
* Enable select all option, disabled by default
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableTickAll = Boolean(f.enable_tick_all);
|
||||
//text preceding columns list
|
||||
this.tickAllText = f.tick_all_text || 'Select all:';
|
||||
|
||||
//array containing hidden columns indexes
|
||||
/**
|
||||
* Text for select all option, defaults to 'Select all:'
|
||||
* @type {String}
|
||||
*/
|
||||
this.tickAllText = defaultsStr(f.tick_all_text, 'Select all:');
|
||||
|
||||
/**
|
||||
* Default position in toolbar ('left'|'center'|'right')
|
||||
* @type {String}
|
||||
*/
|
||||
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
|
||||
|
||||
/**
|
||||
* List of indexes of hidden columns
|
||||
* @private
|
||||
*/
|
||||
this.hiddenCols = [];
|
||||
this.tblHasColTag = (Dom.tag(tf.tbl,'col').length > 0);
|
||||
|
||||
//callback invoked just after cols manager is loaded
|
||||
this.onLoaded = Types.isFn(f.on_loaded) ? f.on_loaded : null;
|
||||
//calls function before cols manager is opened
|
||||
this.onBeforeOpen = Types.isFn(f.on_before_open) ?
|
||||
f.on_before_open : null;
|
||||
//calls function after cols manager is opened
|
||||
this.onAfterOpen = Types.isFn(f.on_after_open) ? f.on_after_open : null;
|
||||
//calls function before cols manager is closed
|
||||
this.onBeforeClose = Types.isFn(f.on_before_close) ?
|
||||
f.on_before_close : null;
|
||||
//calls function after cols manager is closed
|
||||
this.onAfterClose = Types.isFn(f.on_after_close) ?
|
||||
f.on_after_close : null;
|
||||
/**
|
||||
* Bound mouseup wrapper
|
||||
* @private
|
||||
*/
|
||||
this.boundMouseup = null;
|
||||
|
||||
//callback before col is hidden
|
||||
this.onBeforeColHidden = Types.isFn(f.on_before_col_hidden) ?
|
||||
f.on_before_col_hidden : null;
|
||||
//callback after col is hidden
|
||||
this.onAfterColHidden = Types.isFn(f.on_after_col_hidden) ?
|
||||
f.on_after_col_hidden : null;
|
||||
//callback before col is displayed
|
||||
this.onBeforeColDisplayed = Types.isFn(f.on_before_col_displayed) ?
|
||||
f.on_before_col_displayed : null;
|
||||
//callback after col is displayed
|
||||
this.onAfterColDisplayed = Types.isFn(f.on_after_col_displayed) ?
|
||||
f.on_after_col_displayed : null;
|
||||
/**
|
||||
* Callback fired when the extension is initialized
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onLoaded = defaultsFn(f.on_loaded, EMPTY_FN);
|
||||
|
||||
//Grid layout compatibility
|
||||
if(tf.gridLayout){
|
||||
/**
|
||||
* Callback fired before the columns manager is opened
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeOpen = defaultsFn(f.on_before_open, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after the columns manager is opened
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterOpen = defaultsFn(f.on_after_open, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired before the columns manager is closed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeClose = defaultsFn(f.on_before_close, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after the columns manager is closed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterClose = defaultsFn(f.on_after_close, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired before a column is hidden
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeColHidden = defaultsFn(f.on_before_col_hidden, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after a column is hidden
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterColHidden = defaultsFn(f.on_after_col_hidden, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired before a column is displayed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeColDisplayed = defaultsFn(f.on_before_col_displayed,
|
||||
EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after a column is displayed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterColDisplayed = defaultsFn(f.on_after_col_displayed,
|
||||
EMPTY_FN);
|
||||
|
||||
//Grid layout support
|
||||
if (tf.gridLayout) {
|
||||
this.headersTbl = tf.feature('gridLayout').headTbl; //headers table
|
||||
this.headersIndex = 0; //headers index
|
||||
this.onAfterColDisplayed = function(){};
|
||||
this.onAfterColHidden = function(){};
|
||||
}
|
||||
|
||||
//Loads extension stylesheet
|
||||
tf.import(f.name+'Style', tf.stylePath + this.stylesheet, null, 'link');
|
||||
tf.import(f.name + 'Style', tf.getStylePath() + this.stylesheet, null,
|
||||
'link');
|
||||
|
||||
this.tf = tf;
|
||||
this.enable();
|
||||
}
|
||||
|
||||
toggle(){
|
||||
var contDisplay = this.contEl.style.display;
|
||||
var onBeforeOpen = this.onBeforeOpen;
|
||||
var onBeforeClose = this.onBeforeClose;
|
||||
var onAfterOpen = this.onAfterOpen;
|
||||
var onAfterClose = this.onAfterClose;
|
||||
/**
|
||||
* Mouse-up event handler handling popup auto-close behaviour
|
||||
* @private
|
||||
*/
|
||||
onMouseup(evt) {
|
||||
let targetElm = targetEvt(evt);
|
||||
|
||||
if(onBeforeOpen && contDisplay !== 'inline'){
|
||||
onBeforeOpen.call(null, this);
|
||||
while (targetElm && targetElm !== this.contEl
|
||||
&& targetElm !== this.btnEl) {
|
||||
targetElm = targetElm.parentNode;
|
||||
}
|
||||
if(onBeforeClose && contDisplay === 'inline'){
|
||||
onBeforeClose.call(null, this);
|
||||
|
||||
if (targetElm !== this.contEl && targetElm !== this.btnEl) {
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle columns manager UI
|
||||
*/
|
||||
toggle() {
|
||||
// ensure mouseup event handler is removed
|
||||
removeEvt(root, 'mouseup', this.boundMouseup);
|
||||
|
||||
let contDisplay = this.contEl.style.display;
|
||||
|
||||
if (contDisplay !== 'inline') {
|
||||
this.onBeforeOpen(this);
|
||||
}
|
||||
if (contDisplay === 'inline') {
|
||||
this.onBeforeClose(this);
|
||||
}
|
||||
|
||||
this.contEl.style.display = contDisplay === 'inline' ?
|
||||
'none' : 'inline';
|
||||
NONE : 'inline';
|
||||
|
||||
if(onAfterOpen && contDisplay !== 'inline'){
|
||||
onAfterOpen.call(null, this);
|
||||
if (contDisplay !== 'inline') {
|
||||
this.onAfterOpen(this);
|
||||
addEvt(root, 'mouseup', this.boundMouseup);
|
||||
}
|
||||
if(onAfterClose && contDisplay === 'inline'){
|
||||
onAfterClose.call(null, this);
|
||||
if (contDisplay === 'inline') {
|
||||
this.onAfterClose(this);
|
||||
}
|
||||
}
|
||||
|
||||
checkItem(lbl){
|
||||
var li = lbl.parentNode;
|
||||
if(!li || !lbl){
|
||||
/**
|
||||
* Check an item in columns manager UI
|
||||
* @private
|
||||
*/
|
||||
checkItem(lbl) {
|
||||
let li = lbl.parentNode;
|
||||
if (!li || !lbl) {
|
||||
return;
|
||||
}
|
||||
var isChecked = lbl.firstChild.checked;
|
||||
var colIndex = lbl.firstChild.getAttribute('id').split('_')[1];
|
||||
let isChecked = lbl.firstChild.checked;
|
||||
let colIndex = lbl.firstChild.getAttribute('id').split('_')[1];
|
||||
colIndex = parseInt(colIndex, 10);
|
||||
if(isChecked){
|
||||
Dom.addClass(li, this.listSlcItemCssClass);
|
||||
if (isChecked) {
|
||||
addClass(li, this.listSlcItemCssClass);
|
||||
} else {
|
||||
Dom.removeClass(li, this.listSlcItemCssClass);
|
||||
removeClass(li, this.listSlcItemCssClass);
|
||||
}
|
||||
|
||||
var hide = false;
|
||||
if((this.tickToHide && isChecked) || (!this.tickToHide && !isChecked)){
|
||||
let hide = false;
|
||||
if ((this.tickToHide && isChecked) ||
|
||||
(!this.tickToHide && !isChecked)) {
|
||||
hide = true;
|
||||
}
|
||||
this.setHidden(colIndex, hide);
|
||||
}
|
||||
|
||||
init(){
|
||||
if(!this.manager){
|
||||
/**
|
||||
* Initializes ColsVisibility instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized || !this.manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.emit('initializing-extension', this,
|
||||
!isNull(this.btnTgtId));
|
||||
|
||||
this.emitter.on(['hide-column'],
|
||||
(tf, colIndex) => this.hideCol(colIndex));
|
||||
|
||||
this.buildBtn();
|
||||
this.buildManager();
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
|
||||
this.boundMouseup = this.onMouseup.bind(this);
|
||||
|
||||
this.emitter.emit('columns-visibility-initialized', this.tf, this);
|
||||
this.emitter.emit('extension-initialized', this);
|
||||
|
||||
// Hide columns at start at very end of initialization, do not move
|
||||
// as order is important
|
||||
this._hideAtStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build main button UI
|
||||
*/
|
||||
buildBtn(){
|
||||
if(this.btnEl){
|
||||
buildBtn() {
|
||||
if (this.btnEl) {
|
||||
return;
|
||||
}
|
||||
var tf = this.tf;
|
||||
var span = Dom.create('span', ['id', this.prfx+tf.id]);
|
||||
let tf = this.tf;
|
||||
let span = createElm('span');
|
||||
span.className = this.spanCssClass;
|
||||
|
||||
//Container element (rdiv or custom element)
|
||||
if(!this.btnTgtId){
|
||||
tf.setToolbar();
|
||||
}
|
||||
var targetEl = !this.btnTgtId ? tf.rDiv : Dom.id(this.btnTgtId);
|
||||
// Container element (rdiv or custom element)
|
||||
let targetEl = !this.btnTgtId ?
|
||||
tf.feature('toolbar').container(this.toolbarPosition) :
|
||||
elm(this.btnTgtId);
|
||||
|
||||
if(!this.btnTgtId){
|
||||
var firstChild = targetEl.firstChild;
|
||||
if (!this.btnTgtId) {
|
||||
let firstChild = targetEl.firstChild;
|
||||
firstChild.parentNode.insertBefore(span, firstChild);
|
||||
} else {
|
||||
targetEl.appendChild(span);
|
||||
}
|
||||
|
||||
if(!this.btnHtml){
|
||||
var btn = Dom.create('a', ['href','javascript:;']);
|
||||
if (!this.btnHtml) {
|
||||
let btn = createElm('a', ['href', 'javascript:;']);
|
||||
btn.className = this.btnCssClass;
|
||||
btn.title = this.desc;
|
||||
|
||||
btn.innerHTML = this.btnText;
|
||||
span.appendChild(btn);
|
||||
if(!this.enableHover){
|
||||
Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
|
||||
if (!this.enableHover) {
|
||||
addEvt(btn, 'click', (evt) => this.toggle(evt));
|
||||
} else {
|
||||
Event.add(btn, 'mouseover', (evt)=> { this.toggle(evt); });
|
||||
addEvt(btn, 'mouseover', (evt) => this.toggle(evt));
|
||||
}
|
||||
} else { //Custom html
|
||||
} else { // Custom html
|
||||
span.innerHTML = this.btnHtml;
|
||||
var colVisEl = span.firstChild;
|
||||
if(!this.enableHover){
|
||||
Event.add(colVisEl, 'click', (evt)=> { this.toggle(evt); });
|
||||
let colVisEl = span.firstChild;
|
||||
if (!this.enableHover) {
|
||||
addEvt(colVisEl, 'click', (evt) => this.toggle(evt));
|
||||
} else {
|
||||
Event.add(colVisEl, 'mouseover', (evt)=> { this.toggle(evt); });
|
||||
addEvt(colVisEl, 'mouseover', (evt) => this.toggle(evt));
|
||||
}
|
||||
}
|
||||
|
||||
this.spanEl = span;
|
||||
this.btnEl = this.spanEl.firstChild;
|
||||
|
||||
if(this.onLoaded){
|
||||
this.onLoaded.call(null, this);
|
||||
}
|
||||
this.onLoaded(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build columns manager UI
|
||||
*/
|
||||
buildManager(){
|
||||
var tf = this.tf;
|
||||
buildManager() {
|
||||
let tf = this.tf;
|
||||
|
||||
var container = !this.contElTgtId ?
|
||||
Dom.create('div', ['id', this.prfxCont+tf.id]) :
|
||||
Dom.id(this.contElTgtId);
|
||||
let container = !this.contElTgtId ?
|
||||
createElm('div') :
|
||||
elm(this.contElTgtId);
|
||||
container.className = this.contCssClass;
|
||||
|
||||
//Extension description
|
||||
var extNameLabel = Dom.create('p');
|
||||
let extNameLabel = createElm('p');
|
||||
extNameLabel.innerHTML = this.text;
|
||||
container.appendChild(extNameLabel);
|
||||
|
||||
//Headers list
|
||||
var ul = Dom.create('ul' ,['id', 'ul'+this.name+'_'+tf.id]);
|
||||
let ul = createElm('ul');
|
||||
ul.className = this.listCssClass;
|
||||
|
||||
var tbl = this.headersTbl ? this.headersTbl : tf.tbl;
|
||||
var headerIndex = this.headersTbl ?
|
||||
let tbl = this.headersTbl || tf.dom();
|
||||
let headerIndex = this.headersTbl ?
|
||||
this.headersIndex : tf.getHeadersRowIndex();
|
||||
var headerRow = tbl.rows[headerIndex];
|
||||
let headerRow = tbl.rows[headerIndex];
|
||||
|
||||
//Tick all option
|
||||
if(this.enableTickAll){
|
||||
var li = Dom.createCheckItem(
|
||||
'col__'+tf.id, this.tickAllText, this.tickAllText);
|
||||
Dom.addClass(li, this.listItemCssClass);
|
||||
if (this.enableTickAll) {
|
||||
let li = createCheckItem('col__' + tf.id, this.tickAllText,
|
||||
this.tickAllText);
|
||||
addClass(li, this.listItemCssClass);
|
||||
ul.appendChild(li);
|
||||
li.check.checked = !this.tickToHide;
|
||||
|
||||
Event.add(li.check, 'click', ()=> {
|
||||
for(var h = 0; h < headerRow.cells.length; h++){
|
||||
var itm = Dom.id('col_'+h+'_'+tf.id);
|
||||
if(itm && li.check.checked !== itm.checked){
|
||||
addEvt(li.check, 'click', () => {
|
||||
for (let h = 0; h < headerRow.cells.length; h++) {
|
||||
let itm = elm('col_' + h + '_' + tf.id);
|
||||
if (itm && li.check.checked !== itm.checked) {
|
||||
itm.click();
|
||||
itm.checked = li.check.checked;
|
||||
}
|
||||
|
@ -276,42 +493,41 @@ export default class ColsVisibility{
|
|||
});
|
||||
}
|
||||
|
||||
for(var i = 0; i < headerRow.cells.length; i++){
|
||||
var cell = headerRow.cells[i];
|
||||
var cellText = this.headersText && this.headersText[i] ?
|
||||
this.headersText[i] : this._getHeaderText(cell);
|
||||
var liElm = Dom.createCheckItem(
|
||||
'col_'+i+'_'+tf.id, cellText, cellText);
|
||||
Dom.addClass(liElm, this.listItemCssClass);
|
||||
if(!this.tickToHide){
|
||||
Dom.addClass(liElm, this.listSlcItemCssClass);
|
||||
for (let i = 0; i < headerRow.cells.length; i++) {
|
||||
let cell = headerRow.cells[i];
|
||||
let cellText = this.headersText[i] || this._getHeaderText(cell);
|
||||
let liElm = createCheckItem('col_' + i + '_' + tf.id, cellText,
|
||||
cellText);
|
||||
addClass(liElm, this.listItemCssClass);
|
||||
if (!this.tickToHide) {
|
||||
addClass(liElm, this.listSlcItemCssClass);
|
||||
}
|
||||
ul.appendChild(liElm);
|
||||
if(!this.tickToHide){
|
||||
if (!this.tickToHide) {
|
||||
liElm.check.checked = true;
|
||||
}
|
||||
|
||||
Event.add(liElm.check, 'click', (evt)=> {
|
||||
var elm = Event.target(evt);
|
||||
var lbl = elm.parentNode;
|
||||
addEvt(liElm.check, 'click', (evt) => {
|
||||
let elm = targetEvt(evt);
|
||||
let lbl = elm.parentNode;
|
||||
this.checkItem(lbl);
|
||||
});
|
||||
}
|
||||
|
||||
//separator
|
||||
var p = Dom.create('p', ['align','center']);
|
||||
var btn;
|
||||
let p = createElm('p', ['align', 'center']);
|
||||
let btn;
|
||||
//Close link
|
||||
if(!this.btnCloseHtml){
|
||||
btn = Dom.create('a', ['href','javascript:;']);
|
||||
if (!this.btnCloseHtml) {
|
||||
btn = createElm('a', ['href', 'javascript:;']);
|
||||
btn.className = this.btnCloseCssClass;
|
||||
btn.innerHTML = this.btnCloseText;
|
||||
Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
|
||||
addEvt(btn, 'click', (evt) => this.toggle(evt));
|
||||
p.appendChild(btn);
|
||||
} else {
|
||||
p.innerHTML = this.btnCloseHtml;
|
||||
btn = p.firstChild;
|
||||
Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
|
||||
addEvt(btn, 'click', (evt) => this.toggle(evt));
|
||||
}
|
||||
|
||||
container.appendChild(ul);
|
||||
|
@ -319,88 +535,48 @@ export default class ColsVisibility{
|
|||
|
||||
this.btnEl.parentNode.insertBefore(container, this.btnEl);
|
||||
this.contEl = container;
|
||||
|
||||
if(this.atStart){
|
||||
var a = this.atStart;
|
||||
for(var k=0; k<a.length; k++){
|
||||
var itm = Dom.id('col_'+a[k]+'_'+tf.id);
|
||||
if(itm){
|
||||
itm.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide or show specified columns
|
||||
* @param {Numner} colIndex Column index
|
||||
* @param {Boolean} hide hide column if true or show if false
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} hide Hide column if true or show if false
|
||||
*/
|
||||
setHidden(colIndex, hide){
|
||||
var tf = this.tf;
|
||||
var tbl = tf.tbl;
|
||||
setHidden(colIndex, hide) {
|
||||
let tf = this.tf;
|
||||
let tbl = tf.dom();
|
||||
|
||||
if(this.onBeforeColHidden && hide){
|
||||
this.onBeforeColHidden.call(null, this, colIndex);
|
||||
}
|
||||
if(this.onBeforeColDisplayed && !hide){
|
||||
this.onBeforeColDisplayed.call(null, this, colIndex);
|
||||
if (hide) {
|
||||
this.onBeforeColHidden(this, colIndex);
|
||||
} else {
|
||||
this.onBeforeColDisplayed(this, colIndex);
|
||||
}
|
||||
|
||||
this._hideCells(tbl, colIndex, hide);
|
||||
if(this.headersTbl){
|
||||
this._hideCells(this.headersTbl, colIndex, hide);
|
||||
this._hideElements(tbl, colIndex, hide);
|
||||
if (this.headersTbl) {
|
||||
this._hideElements(this.headersTbl, colIndex, hide);
|
||||
}
|
||||
|
||||
var hiddenCols = this.hiddenCols;
|
||||
if(hide){
|
||||
if(hiddenCols.indexOf(colIndex) === -1){
|
||||
let hiddenCols = this.hiddenCols;
|
||||
let itemIndex = hiddenCols.indexOf(colIndex);
|
||||
if (hide) {
|
||||
if (itemIndex === -1) {
|
||||
this.hiddenCols.push(colIndex);
|
||||
}
|
||||
} else {
|
||||
var itemIndex = Arr.indexByValue(hiddenCols, colIndex, true);
|
||||
if(hiddenCols.indexOf(colIndex) !== -1){
|
||||
if (itemIndex !== -1) {
|
||||
this.hiddenCols.splice(itemIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
var gridLayout;
|
||||
var headTbl;
|
||||
var gridColElms;
|
||||
if(this.onAfterColHidden && hide){
|
||||
//This event is fired just after a column is displayed for
|
||||
//grid_layout support
|
||||
//TODO: grid layout module should be responsible for those
|
||||
//calculations
|
||||
if(tf.gridLayout){
|
||||
gridLayout = tf.feature('gridLayout');
|
||||
headTbl = gridLayout.headTbl;
|
||||
gridColElms = gridLayout.gridColElms;
|
||||
var hiddenWidth = parseInt(
|
||||
gridColElms[colIndex].style.width, 10);
|
||||
|
||||
var headTblW = parseInt(headTbl.style.width, 10);
|
||||
headTbl.style.width = headTblW - hiddenWidth + 'px';
|
||||
tbl.style.width = headTbl.style.width;
|
||||
}
|
||||
this.onAfterColHidden.call(null, this, colIndex);
|
||||
}
|
||||
|
||||
if(this.onAfterColDisplayed && !hide){
|
||||
//This event is fired just after a column is displayed for
|
||||
//grid_layout support
|
||||
//TODO: grid layout module should be responsible for those
|
||||
//calculations
|
||||
if(tf.gridLayout){
|
||||
gridLayout = tf.feature('gridLayout');
|
||||
headTbl = gridLayout.headTbl;
|
||||
gridColElms = gridLayout.gridColElms;
|
||||
var width = parseInt(gridColElms[colIndex].style.width, 10);
|
||||
headTbl.style.width =
|
||||
(parseInt(headTbl.style.width, 10) + width) + 'px';
|
||||
tf.tbl.style.width = headTbl.style.width;
|
||||
}
|
||||
this.onAfterColDisplayed.call(null, this, colIndex);
|
||||
if (hide) {
|
||||
this.onAfterColHidden(this, colIndex);
|
||||
this.emitter.emit('column-hidden', tf, this, colIndex,
|
||||
this.hiddenCols);
|
||||
} else {
|
||||
this.onAfterColDisplayed(this, colIndex);
|
||||
this.emitter.emit('column-shown', tf, this, colIndex,
|
||||
this.hiddenCols);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,13 +584,15 @@ export default class ColsVisibility{
|
|||
* Show specified column
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
showCol(colIndex){
|
||||
if(colIndex === undefined || !this.isColHidden(colIndex)){
|
||||
showCol(colIndex) {
|
||||
if (isUndef(colIndex) || !this.isColHidden(colIndex)) {
|
||||
return;
|
||||
}
|
||||
if(this.manager && this.contEl){
|
||||
var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
|
||||
if(itm){ itm.click(); }
|
||||
if (this.manager && this.contEl) {
|
||||
let itm = elm('col_' + colIndex + '_' + this.tf.id);
|
||||
if (itm) {
|
||||
itm.click();
|
||||
}
|
||||
} else {
|
||||
this.setHidden(colIndex, false);
|
||||
}
|
||||
|
@ -424,13 +602,15 @@ export default class ColsVisibility{
|
|||
* Hide specified column
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
hideCol(colIndex){
|
||||
if(colIndex === undefined || this.isColHidden(colIndex)){
|
||||
hideCol(colIndex) {
|
||||
if (isUndef(colIndex) || this.isColHidden(colIndex)) {
|
||||
return;
|
||||
}
|
||||
if(this.manager && this.contEl){
|
||||
var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
|
||||
if(itm){ itm.click(); }
|
||||
if (this.manager && this.contEl) {
|
||||
let itm = elm('col_' + colIndex + '_' + this.tf.id);
|
||||
if (itm) {
|
||||
itm.click();
|
||||
}
|
||||
} else {
|
||||
this.setHidden(colIndex, true);
|
||||
}
|
||||
|
@ -440,8 +620,8 @@ export default class ColsVisibility{
|
|||
* Determine if specified column is hidden
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
isColHidden(colIndex){
|
||||
if(this.hiddenCols.indexOf(colIndex) !== -1){
|
||||
isColHidden(colIndex) {
|
||||
if (this.hiddenCols.indexOf(colIndex) !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -451,8 +631,8 @@ export default class ColsVisibility{
|
|||
* Toggle visibility of specified column
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
toggleCol(colIndex){
|
||||
if(colIndex === undefined || this.isColHidden(colIndex)){
|
||||
toggleCol(colIndex) {
|
||||
if (isUndef(colIndex) || this.isColHidden(colIndex)) {
|
||||
this.showCol(colIndex);
|
||||
} else {
|
||||
this.hideCol(colIndex);
|
||||
|
@ -460,47 +640,53 @@ export default class ColsVisibility{
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the indexes of the columns currently hidden
|
||||
* Return the indexes of the columns currently hidden
|
||||
* @return {Array} column indexes
|
||||
*/
|
||||
getHiddenCols(){
|
||||
getHiddenCols() {
|
||||
return this.hiddenCols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the columns manager
|
||||
*/
|
||||
destroy(){
|
||||
if(!this.btnEl && !this.contEl){
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
if(Dom.id(this.contElTgtId)){
|
||||
Dom.id(this.contElTgtId).innerHTML = '';
|
||||
if (elm(this.contElTgtId)) {
|
||||
elm(this.contElTgtId).innerHTML = '';
|
||||
} else {
|
||||
this.contEl.innerHTML = '';
|
||||
this.contEl.parentNode.removeChild(this.contEl);
|
||||
removeElm(this.contEl);
|
||||
this.contEl = null;
|
||||
}
|
||||
this.btnEl.innerHTML = '';
|
||||
this.btnEl.parentNode.removeChild(this.btnEl);
|
||||
removeElm(this.btnEl);
|
||||
this.btnEl = null;
|
||||
|
||||
this.emitter.off(['hide-column'],
|
||||
(tf, colIndex) => this.hideCol(colIndex));
|
||||
|
||||
this.boundMouseup = null;
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
_getHeaderText(cell){
|
||||
if(!cell.hasChildNodes){
|
||||
_getHeaderText(cell) {
|
||||
if (!cell.hasChildNodes) {
|
||||
return '';
|
||||
}
|
||||
|
||||
for(var i=0; i<cell.childNodes.length; i++){
|
||||
var n = cell.childNodes[i];
|
||||
if(n.nodeType === 3){
|
||||
for (let i = 0; i < cell.childNodes.length; i++) {
|
||||
let n = cell.childNodes[i];
|
||||
if (n.nodeType === 3) {
|
||||
return n.nodeValue;
|
||||
} else if(n.nodeType === 1){
|
||||
if(n.id && n.id.indexOf('popUp') !== -1){
|
||||
} else if (n.nodeType === 1) {
|
||||
if (n.id && n.id.indexOf('popUp') !== -1) {
|
||||
continue;
|
||||
} else {
|
||||
return Dom.getText(n);
|
||||
return getText(n);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
|
@ -508,14 +694,32 @@ export default class ColsVisibility{
|
|||
return '';
|
||||
}
|
||||
|
||||
_hideCells(tbl, colIndex, hide){
|
||||
for(var i=0; i<tbl.rows.length; i++){
|
||||
var row = tbl.rows[i];
|
||||
var cell = row.cells[colIndex];
|
||||
if(cell){
|
||||
cell.style.display = hide ? 'none' : '';
|
||||
_hideElements(tbl, colIdx, hide) {
|
||||
this._hideCells(tbl, colIdx, hide);
|
||||
this._hideCol(tbl, colIdx, hide);
|
||||
}
|
||||
|
||||
_hideCells(tbl, colIdx, hide) {
|
||||
for (let i = 0; i < tbl.rows.length; i++) {
|
||||
let row = tbl.rows[i];
|
||||
let cell = row.cells[colIdx];
|
||||
if (cell) {
|
||||
cell.style.display = hide ? NONE : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_hideCol(tbl, colIdx, hide) {
|
||||
let colElms = tag(tbl, 'col');
|
||||
if (colElms.length === 0) {
|
||||
return;
|
||||
}
|
||||
colElms[colIdx].style.display = hide ? NONE : '';
|
||||
}
|
||||
|
||||
_hideAtStart() {
|
||||
this.atStart.forEach((colIdx) => {
|
||||
this.hideCol(colIdx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,110 +1,225 @@
|
|||
import Dom from '../../dom';
|
||||
import Types from '../../types';
|
||||
import Event from '../../event';
|
||||
import {Feature} from '../../feature';
|
||||
import {createElm, removeElm, elm} from '../../dom';
|
||||
import {EMPTY_FN, isNull} from '../../types';
|
||||
import {addEvt} from '../../event';
|
||||
import {
|
||||
defaultsBool, defaultsStr, defaultsFn, defaultsNb,
|
||||
} from '../../settings';
|
||||
import {RIGHT} from '../../modules/toolbar';
|
||||
|
||||
export default class FiltersVisibility{
|
||||
/**
|
||||
* Filters Visibility extension
|
||||
*/
|
||||
export default class FiltersVisibility extends Feature {
|
||||
|
||||
/**
|
||||
* Filters Row Visibility extension
|
||||
* @param {Object} tf TableFilter instance
|
||||
* @param {Object} f Config
|
||||
* Creates an instance of FiltersVisibility
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
* @param {Object} Configuration object
|
||||
*/
|
||||
constructor(tf, f){
|
||||
constructor(tf, f) {
|
||||
super(tf, FiltersVisibility);
|
||||
|
||||
this.initialized = false;
|
||||
/**
|
||||
* Module name
|
||||
* @type {String}
|
||||
*/
|
||||
this.name = f.name;
|
||||
this.desc = f.description || 'Filters row visibility manager';
|
||||
|
||||
// Path and image filenames
|
||||
this.stylesheet = f.stylesheet || 'filtersVisibility.css';
|
||||
this.icnExpand = f.expand_icon_name || 'icn_exp.png';
|
||||
this.icnCollapse = f.collapse_icon_name || 'icn_clp.png';
|
||||
/**
|
||||
* Module description
|
||||
* @type {String}
|
||||
*/
|
||||
this.desc = defaultsStr(f.description,
|
||||
'Filters row visibility manager');
|
||||
|
||||
//expand/collapse filters span element
|
||||
/**
|
||||
* Extension's stylesheet filename
|
||||
* @type {String}
|
||||
*/
|
||||
this.stylesheet = defaultsStr(f.stylesheet , 'filtersVisibility.css');
|
||||
|
||||
/**
|
||||
* Expand icon filename
|
||||
* @type {String}
|
||||
*/
|
||||
this.icnExpand = defaultsStr(f.expand_icon_name, 'icn_exp.png');
|
||||
|
||||
/**
|
||||
* Collapse icon filename
|
||||
* @type {String}
|
||||
*/
|
||||
this.icnCollapse = defaultsStr(f.collapse_icon_name, 'icn_clp.png');
|
||||
|
||||
/**
|
||||
* Main container element
|
||||
* @private
|
||||
*/
|
||||
this.contEl = null;
|
||||
//expand/collapse filters btn element
|
||||
|
||||
/**
|
||||
* Button element
|
||||
* @private
|
||||
*/
|
||||
this.btnEl = null;
|
||||
|
||||
this.icnExpandHtml = '<img src="'+ tf.themesPath + this.icnExpand +
|
||||
/**
|
||||
* Expand icon HTML
|
||||
* @private
|
||||
*/
|
||||
this.icnExpandHtml = '<img src="' + tf.themesPath + this.icnExpand +
|
||||
'" alt="Expand filters" >';
|
||||
this.icnCollapseHtml = '<img src="'+ tf.themesPath + this.icnCollapse +
|
||||
|
||||
/**
|
||||
* Collapse icon HTML
|
||||
* @private
|
||||
*/
|
||||
this.icnCollapseHtml = '<img src="' + tf.themesPath + this.icnCollapse +
|
||||
'" alt="Collapse filters" >';
|
||||
|
||||
/**
|
||||
* Default text
|
||||
* @private
|
||||
*/
|
||||
this.defaultText = 'Toggle filters';
|
||||
|
||||
//id of container element
|
||||
this.targetId = f.target_id || null;
|
||||
//enables/disables expand/collapse icon
|
||||
this.enableIcon = f.enable_icon===false ? false : true;
|
||||
this.btnText = f.btn_text || '';
|
||||
/**
|
||||
* ID of main container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.targetId = f.target_id || null;
|
||||
|
||||
//defines expand/collapse filters text
|
||||
/**
|
||||
* Enable expand/collapse icon, defaults to true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableIcon = defaultsBool(f.enable_icon, true);
|
||||
|
||||
/**
|
||||
* Custom text for button
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnText = defaultsStr(f.btn_text, '');
|
||||
|
||||
/**
|
||||
* Collapse button HTML
|
||||
* @private
|
||||
*/
|
||||
this.collapseBtnHtml = this.enableIcon ?
|
||||
this.icnCollapseHtml + this.btnText :
|
||||
this.btnText || this.defaultText;
|
||||
this.expandBtnHtml = this.enableIcon ?
|
||||
|
||||
/**
|
||||
* Expand button HTML
|
||||
* @private
|
||||
*/
|
||||
this.expandBtnHtml = this.enableIcon ?
|
||||
this.icnExpandHtml + this.btnText :
|
||||
this.btnText || this.defaultText;
|
||||
|
||||
//defines expand/collapse filters button innerHtml
|
||||
this.btnHtml = f.btn_html || null;
|
||||
//defines css class for expand/collapse filters button
|
||||
this.btnCssClass = f.btn_css_class || 'btnExpClpFlt';
|
||||
//defines css class span containing expand/collapse filters
|
||||
this.contCssClass = f.cont_css_class || 'expClpFlt';
|
||||
this.filtersRowIndex = !Types.isUndef(f.filters_row_index) ?
|
||||
f.filters_row_index : tf.getFiltersRowIndex();
|
||||
/**
|
||||
* Button's custom HTML
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnHtml = defaultsStr(f.btn_html, null);
|
||||
|
||||
this.visibleAtStart = !Types.isUndef(f.visible_at_start) ?
|
||||
Boolean(f.visible_at_start) : true;
|
||||
/**
|
||||
* Css class for expand/collapse filters button
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnCssClass = defaultsStr(f.btn_css_class, 'btnExpClpFlt');
|
||||
|
||||
// Prefix
|
||||
this.prfx = 'fltsVis_';
|
||||
/**
|
||||
* Css class for main container
|
||||
* @type {String}
|
||||
*/
|
||||
this.contCssClass = defaultsStr(f.cont_css_class, 'expClpFlt');
|
||||
|
||||
//callback before filters row is shown
|
||||
this.onBeforeShow = Types.isFn(f.on_before_show) ?
|
||||
f.on_before_show : null;
|
||||
//callback after filters row is shown
|
||||
this.onAfterShow = Types.isFn(f.on_after_show) ?
|
||||
f.on_after_show : null;
|
||||
//callback before filters row is hidden
|
||||
this.onBeforeHide = Types.isFn(f.on_before_hide) ?
|
||||
f.on_before_hide : null;
|
||||
//callback after filters row is hidden
|
||||
this.onAfterHide = Types.isFn(f.on_after_hide) ? f.on_after_hide : null;
|
||||
/**
|
||||
* Filters row index
|
||||
* @type {Number}
|
||||
*/
|
||||
this.filtersRowIndex = defaultsNb(f.filters_row_index,
|
||||
tf.getFiltersRowIndex());
|
||||
|
||||
//Loads extension stylesheet
|
||||
tf.import(f.name+'Style', tf.stylePath + this.stylesheet, null, 'link');
|
||||
/**
|
||||
* Make filters visible at initialization, defaults to true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.visibleAtStart = defaultsNb(f.visible_at_start, true);
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Default position in toolbar ('left'|'center'|'right')
|
||||
* @type {String}
|
||||
*/
|
||||
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
|
||||
|
||||
/**
|
||||
* Callback fired before filters row is shown
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeShow = defaultsFn(f.on_before_show, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after filters row is shown
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterShow = defaultsFn(f.on_after_show, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired before filters row is hidden
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeHide = defaultsFn(f.on_before_hide, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after filters row is hidden
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterHide = defaultsFn(f.on_after_hide, EMPTY_FN);
|
||||
|
||||
//Import extension's stylesheet
|
||||
tf.import(f.name + 'Style', tf.getStylePath() + this.stylesheet, null,
|
||||
'link');
|
||||
|
||||
this.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise extension
|
||||
*/
|
||||
init(){
|
||||
if(this.initialized){
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.emit('initializing-extension', this,
|
||||
!isNull(this.targetId));
|
||||
|
||||
this.buildUI();
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
|
||||
this.emitter.on(['show-filters'], (tf, visible) => this.show(visible));
|
||||
this.emitter.emit('filters-visibility-initialized', this.tf, this);
|
||||
this.emitter.emit('extension-initialized', this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build UI elements
|
||||
*/
|
||||
buildUI(){
|
||||
buildUI() {
|
||||
let tf = this.tf;
|
||||
let span = Dom.create('span',['id', this.prfx+tf.id]);
|
||||
let span = createElm('span');
|
||||
span.className = this.contCssClass;
|
||||
|
||||
//Container element (rdiv or custom element)
|
||||
if(!this.targetId){
|
||||
tf.setToolbar();
|
||||
}
|
||||
let targetEl = !this.targetId ? tf.rDiv : Dom.id(this.targetId);
|
||||
// Container element (rdiv or custom element)
|
||||
let targetEl = !this.targetId ?
|
||||
tf.feature('toolbar').container(this.toolbarPosition) :
|
||||
elm(this.targetId);
|
||||
|
||||
if(!this.targetId){
|
||||
if (!this.targetId) {
|
||||
let firstChild = targetEl.firstChild;
|
||||
firstChild.parentNode.insertBefore(span, firstChild);
|
||||
} else {
|
||||
|
@ -112,23 +227,23 @@ export default class FiltersVisibility{
|
|||
}
|
||||
|
||||
let btn;
|
||||
if(!this.btnHtml){
|
||||
btn = Dom.create('a', ['href', 'javascript:void(0);']);
|
||||
if (!this.btnHtml) {
|
||||
btn = createElm('a', ['href', 'javascript:void(0);']);
|
||||
btn.className = this.btnCssClass;
|
||||
btn.title = this.btnText || this.defaultText;
|
||||
btn.innerHTML = this.collapseBtnHtml;
|
||||
span.appendChild(btn);
|
||||
Event.add(btn, 'click', ()=> this.toggle());
|
||||
} else { //Custom html
|
||||
} else { // Custom html
|
||||
span.innerHTML = this.btnHtml;
|
||||
btn = span.firstChild;
|
||||
Event.add(btn, 'click', ()=> this.toggle());
|
||||
}
|
||||
|
||||
addEvt(btn, 'click', () => this.toggle());
|
||||
|
||||
this.contEl = span;
|
||||
this.btnEl = btn;
|
||||
|
||||
if(!this.visibleAtStart){
|
||||
if (!this.visibleAtStart) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
|
@ -136,47 +251,64 @@ export default class FiltersVisibility{
|
|||
/**
|
||||
* Toggle filters visibility
|
||||
*/
|
||||
toggle(){
|
||||
toggle() {
|
||||
let tf = this.tf;
|
||||
let tbl = tf.gridLayout? tf.feature('gridLayout').headTbl : tf.tbl;
|
||||
let tbl = tf.gridLayout ? tf.feature('gridLayout').headTbl : tf.dom();
|
||||
let fltRow = tbl.rows[this.filtersRowIndex];
|
||||
let fltRowDisplay = fltRow.style.display;
|
||||
let isDisplayed = fltRow.style.display === '';
|
||||
|
||||
if(this.onBeforeShow && fltRowDisplay !== ''){
|
||||
this.onBeforeShow.call(this, this);
|
||||
this.show(!isDisplayed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide filters
|
||||
*
|
||||
* @param {boolean} [visible=true] Visibility flag
|
||||
*/
|
||||
show(visible = true) {
|
||||
let tf = this.tf;
|
||||
let tbl = tf.gridLayout ? tf.feature('gridLayout').headTbl : tf.dom();
|
||||
let fltRow = tbl.rows[this.filtersRowIndex];
|
||||
|
||||
if (visible) {
|
||||
this.onBeforeShow(this);
|
||||
}
|
||||
if(this.onBeforeHide && fltRowDisplay === ''){
|
||||
this.onBeforeHide.call(null, this);
|
||||
if (!visible) {
|
||||
this.onBeforeHide(this);
|
||||
}
|
||||
|
||||
fltRow.style.display = fltRowDisplay==='' ? 'none' : '';
|
||||
if(this.enableIcon && !this.btnHtml){
|
||||
this.btnEl.innerHTML = fltRowDisplay === '' ?
|
||||
this.expandBtnHtml : this.collapseBtnHtml;
|
||||
fltRow.style.display = visible ? '' : 'none';
|
||||
if (this.enableIcon && !this.btnHtml) {
|
||||
this.btnEl.innerHTML = visible ?
|
||||
this.collapseBtnHtml : this.expandBtnHtml;
|
||||
}
|
||||
|
||||
if(this.onAfterShow && fltRowDisplay !== ''){
|
||||
this.onAfterShow.call(null, this);
|
||||
if (visible) {
|
||||
this.onAfterShow(this);
|
||||
}
|
||||
if(this.onAfterHide && fltRowDisplay === ''){
|
||||
this.onAfterHide.call(null, this);
|
||||
if (!visible) {
|
||||
this.onAfterHide(this);
|
||||
}
|
||||
|
||||
this.emitter.emit('filters-toggled', tf, this, visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the UI
|
||||
*/
|
||||
destroy(){
|
||||
if(!this.btnEl && !this.contEl){
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.off(['show-filters'], (tf, visible) => this.show(visible));
|
||||
|
||||
this.btnEl.innerHTML = '';
|
||||
this.btnEl.parentNode.removeChild(this.btnEl);
|
||||
removeElm(this.btnEl);
|
||||
this.btnEl = null;
|
||||
|
||||
this.contEl.innerHTML = '';
|
||||
this.contEl.parentNode.removeChild(this.contEl);
|
||||
removeElm(this.contEl);
|
||||
this.contEl = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
|
|
|
@ -1,170 +1,220 @@
|
|||
import Types from '../../types';
|
||||
import Dom from '../../dom';
|
||||
import Arr from '../../array';
|
||||
import Event from '../../event';
|
||||
import DateHelper from '../../date';
|
||||
import Helpers from '../../helpers';
|
||||
import {Feature} from '../../feature';
|
||||
import {isUndef, isObj, EMPTY_FN} from '../../types';
|
||||
import {createElm, elm, tag} from '../../dom';
|
||||
import {addEvt, bound} from '../../event';
|
||||
import {parse as parseNb} from '../../number';
|
||||
import {
|
||||
NONE, CELL_TAG, HEADER_TAG, STRING, NUMBER, DATE, FORMATTED_NUMBER,
|
||||
IP_ADDRESS
|
||||
} from '../../const';
|
||||
import {defaultsStr, defaultsFn, defaultsArr} from '../../settings';
|
||||
|
||||
export default class AdapterSortableTable{
|
||||
/**
|
||||
* SortableTable Adapter module
|
||||
*/
|
||||
export default class AdapterSortableTable extends Feature {
|
||||
|
||||
/**
|
||||
* SortableTable Adapter module
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of AdapterSortableTable
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
* @param {Object} opts Configuration object
|
||||
*/
|
||||
constructor(tf, opts){
|
||||
// Configuration object
|
||||
let f = tf.config();
|
||||
constructor(tf, opts) {
|
||||
super(tf, AdapterSortableTable);
|
||||
|
||||
this.initialized = false;
|
||||
/**
|
||||
* Module name
|
||||
* @type {String}
|
||||
*/
|
||||
this.name = opts.name;
|
||||
this.desc = opts.description || 'Sortable table';
|
||||
|
||||
//indicates if paging is enabled
|
||||
this.isPaged = false;
|
||||
/**
|
||||
* Module description
|
||||
* @type {String}
|
||||
*/
|
||||
this.desc = defaultsStr(opts.description, 'Sortable table');
|
||||
|
||||
//indicates if tables was sorted
|
||||
/**
|
||||
* Indicate whether table previously sorted
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.sorted = false;
|
||||
|
||||
this.sortTypes = Types.isArray(opts.types) ? opts.types : [];
|
||||
this.sortColAtStart = Types.isArray(opts.sort_col_at_start) ?
|
||||
opts.sort_col_at_start : null;
|
||||
/**
|
||||
* List of sort type per column basis
|
||||
* @type {Array}
|
||||
*/
|
||||
this.sortTypes = defaultsArr(opts.types, tf.colTypes);
|
||||
|
||||
/**
|
||||
* Column to be sorted at initialization, ie:
|
||||
* sort_col_at_start: [1, true]
|
||||
* @type {Array}
|
||||
*/
|
||||
this.sortColAtStart = defaultsArr(opts.sort_col_at_start, null);
|
||||
|
||||
/**
|
||||
* Enable asynchronous sort, if triggers are external
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.asyncSort = Boolean(opts.async_sort);
|
||||
this.triggerIds = Types.isArray(opts.trigger_ids) ?
|
||||
opts.trigger_ids : [];
|
||||
|
||||
/**
|
||||
* List of element IDs triggering sort on a per column basis
|
||||
* @type {Array}
|
||||
*/
|
||||
this.triggerIds = defaultsArr(opts.trigger_ids, []);
|
||||
|
||||
// edit .sort-arrow.descending / .sort-arrow.ascending in
|
||||
// tablefilter.css to reflect any path change
|
||||
this.imgPath = opts.images_path || tf.themesPath;
|
||||
this.imgBlank = opts.image_blank || 'blank.png';
|
||||
this.imgClassName = opts.image_class_name || 'sort-arrow';
|
||||
this.imgAscClassName = opts.image_asc_class_name || 'ascending';
|
||||
this.imgDescClassName = opts.image_desc_class_name ||'descending';
|
||||
//cell attribute storing custom key
|
||||
this.customKey = opts.custom_key || 'data-tf-sortKey';
|
||||
/**
|
||||
* Path to images
|
||||
* @type {String}
|
||||
*/
|
||||
this.imgPath = defaultsStr(opts.images_path, tf.themesPath);
|
||||
|
||||
/*** TF additional events ***/
|
||||
//additional paging events for alternating background
|
||||
// o.Evt._Paging.nextEvt = function(){
|
||||
// if(o.sorted && o.alternateBgs) o.Filter();
|
||||
// }
|
||||
// o.Evt._Paging.prevEvt = o.Evt._Paging.nextEvt;
|
||||
// o.Evt._Paging.firstEvt = o.Evt._Paging.nextEvt;
|
||||
// o.Evt._Paging.lastEvt = o.Evt._Paging.nextEvt;
|
||||
// o.Evt._OnSlcPagesChangeEvt = o.Evt._Paging.nextEvt;
|
||||
/**
|
||||
* Blank image file name
|
||||
* @type {String}
|
||||
*/
|
||||
this.imgBlank = defaultsStr(opts.image_blank, 'blank.png');
|
||||
|
||||
// callback invoked after sort is loaded and instanciated
|
||||
this.onSortLoaded = Types.isFn(opts.on_sort_loaded) ?
|
||||
opts.on_sort_loaded : null;
|
||||
// callback invoked before table is sorted
|
||||
this.onBeforeSort = Types.isFn(opts.on_before_sort) ?
|
||||
opts.on_before_sort : null;
|
||||
// callback invoked after table is sorted
|
||||
this.onAfterSort = Types.isFn(opts.on_after_sort) ?
|
||||
f.on_after_sort : null;
|
||||
/**
|
||||
* Css class for sort indicator image
|
||||
* @type {String}
|
||||
*/
|
||||
this.imgClassName = defaultsStr(opts.image_class_name, 'sort-arrow');
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Css class for ascending sort indicator image
|
||||
* @type {String}
|
||||
*/
|
||||
this.imgAscClassName = defaultsStr(opts.image_asc_class_name,
|
||||
'ascending');
|
||||
|
||||
/**
|
||||
* Css class for descending sort indicator image
|
||||
* @type {String}
|
||||
*/
|
||||
this.imgDescClassName = defaultsStr(opts.image_desc_class_name,
|
||||
'descending');
|
||||
|
||||
/**
|
||||
* Cell attribute key storing custom value used for sorting
|
||||
* @type {String}
|
||||
*/
|
||||
this.customKey = defaultsStr(opts.custom_key, 'data-tf-sortKey');
|
||||
|
||||
/**
|
||||
* Callback fired when sort extension is instanciated
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onSortLoaded = defaultsFn(opts.on_sort_loaded, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired before a table column is sorted
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeSort = defaultsFn(opts.on_before_sort, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after a table column is sorted
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterSort = defaultsFn(opts.on_after_sort, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* SortableTable instance
|
||||
* @private
|
||||
*/
|
||||
this.stt = null;
|
||||
|
||||
this.enable();
|
||||
}
|
||||
|
||||
init(){
|
||||
/**
|
||||
* Initializes AdapterSortableTable instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
let tf = this.tf;
|
||||
let adpt = this;
|
||||
|
||||
// SortableTable class sanity check (sortabletable.js)
|
||||
if(Types.isUndef(SortableTable)){
|
||||
if (isUndef(SortableTable)) {
|
||||
throw new Error('SortableTable class not found.');
|
||||
}
|
||||
|
||||
// Add any date format if needed
|
||||
this.emitter.emit('add-date-type-formats', this.tf, this.sortTypes);
|
||||
|
||||
this.overrideSortableTable();
|
||||
this.setSortTypes();
|
||||
|
||||
//Column sort at start
|
||||
this.onSortLoaded(tf, this);
|
||||
|
||||
/*** SortableTable callbacks ***/
|
||||
this.stt.onbeforesort = function () {
|
||||
adpt.onBeforeSort(tf, adpt.stt.sortColumn);
|
||||
|
||||
/*** sort behaviour for paging ***/
|
||||
if (tf.paging) {
|
||||
tf.feature('paging').disable();
|
||||
}
|
||||
};
|
||||
|
||||
this.stt.onsort = function () {
|
||||
adpt.sorted = true;
|
||||
|
||||
//sort behaviour for paging
|
||||
if (tf.paging) {
|
||||
let paginator = tf.feature('paging');
|
||||
// recalculate valid rows index as sorting may have change it
|
||||
tf.getValidRows(true);
|
||||
paginator.enable();
|
||||
paginator.setPage(paginator.getPage());
|
||||
}
|
||||
|
||||
adpt.onAfterSort(tf, adpt.stt.sortColumn, adpt.stt.descending);
|
||||
adpt.emitter.emit('column-sorted', tf, adpt.stt.sortColumn,
|
||||
adpt.stt.descending);
|
||||
};
|
||||
|
||||
// Column sort at start
|
||||
let sortColAtStart = adpt.sortColAtStart;
|
||||
if(sortColAtStart){
|
||||
if (sortColAtStart) {
|
||||
this.stt.sort(sortColAtStart[0], sortColAtStart[1]);
|
||||
}
|
||||
|
||||
if(this.onSortLoaded){
|
||||
this.onSortLoaded.call(null, tf, this);
|
||||
}
|
||||
|
||||
/*** SortableTable callbacks ***/
|
||||
this.stt.onbeforesort = function(){
|
||||
if(this.onBeforeSort){
|
||||
this.onBeforeSort.call(null, tf, this.stt.sortColumn);
|
||||
}
|
||||
|
||||
/*** sort behaviour for paging ***/
|
||||
if(tf.paging){
|
||||
adpt.isPaged = true;
|
||||
tf.paging = false;
|
||||
tf.feature('paging').destroy();
|
||||
}
|
||||
};
|
||||
|
||||
this.stt.onsort = function(){
|
||||
adpt.sorted = true;
|
||||
|
||||
//rows alternating bg issue
|
||||
// TODO: move into AlternateRows component
|
||||
if(tf.alternateBgs){
|
||||
let rows = tf.tbl.rows, c = 0;
|
||||
|
||||
let setClass = function(row, i, removeOnly){
|
||||
if(Types.isUndef(removeOnly)){
|
||||
removeOnly = false;
|
||||
}
|
||||
let altRows = tf.feature('alternateRows'),
|
||||
oddCls = altRows.oddCss,
|
||||
evenCls = altRows.evenCss;
|
||||
Dom.removeClass(row, oddCls);
|
||||
Dom.removeClass(row, evenCls);
|
||||
|
||||
if(!removeOnly){
|
||||
Dom.addClass(row, i % 2 ? oddCls : evenCls);
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = tf.refRow; i < tf.nbRows; i++){
|
||||
let isRowValid = rows[i].getAttribute('validRow');
|
||||
if(tf.paging && rows[i].style.display === ''){
|
||||
setClass(rows[i], c);
|
||||
c++;
|
||||
} else {
|
||||
if((isRowValid==='true' || isRowValid===null) &&
|
||||
rows[i].style.display === ''){
|
||||
setClass(rows[i], c);
|
||||
c++;
|
||||
} else {
|
||||
setClass(rows[i], c, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//sort behaviour for paging
|
||||
if(adpt.isPaged){
|
||||
let paginator = tf.feature('paging');
|
||||
paginator.reset(false);
|
||||
paginator.setPage(paginator.getPage());
|
||||
adpt.isPaged = false;
|
||||
}
|
||||
|
||||
if(adpt.onAfterSort){
|
||||
adpt.onAfterSort.call(null, tf, tf.stt.sortColumn);
|
||||
}
|
||||
};
|
||||
this.emitter.on(['sort'], bound(this.sortByColumnIndexHandler, this));
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
|
||||
this.emitter.emit('sort-initialized', tf, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort specified column
|
||||
* @param {Number} colIdx Column index
|
||||
* @param {Number} colIdx Column index
|
||||
* @param {Boolean} desc Optional: descending manner
|
||||
*/
|
||||
sortByColumnIndex(colIdx){
|
||||
this.stt.sort(colIdx);
|
||||
sortByColumnIndex(colIdx, desc) {
|
||||
this.stt.sort(colIdx, desc);
|
||||
}
|
||||
|
||||
overrideSortableTable(){
|
||||
/** @private */
|
||||
sortByColumnIndexHandler(tf, colIdx, desc) {
|
||||
this.sortByColumnIndex(colIdx, desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SortableTable overrides for TableFilter integration
|
||||
*/
|
||||
overrideSortableTable() {
|
||||
let adpt = this,
|
||||
tf = this.tf;
|
||||
|
||||
|
@ -172,15 +222,15 @@ export default class AdapterSortableTable{
|
|||
* Overrides headerOnclick method in order to handle th event
|
||||
* @param {Object} e [description]
|
||||
*/
|
||||
SortableTable.prototype.headerOnclick = function(evt){
|
||||
if(!adpt.initialized){
|
||||
SortableTable.prototype.headerOnclick = function (evt) {
|
||||
if (!adpt.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find Header element
|
||||
let el = evt.target || evt.srcElement;
|
||||
|
||||
while(el.tagName !== 'TD' && el.tagName !== 'TH'){
|
||||
while (el.tagName !== CELL_TAG && el.tagName !== HEADER_TAG) {
|
||||
el = el.parentNode;
|
||||
}
|
||||
|
||||
|
@ -196,10 +246,10 @@ export default class AdapterSortableTable{
|
|||
* @param {Object} oTd TD element
|
||||
* @return {Number} Cell index
|
||||
*/
|
||||
SortableTable.getCellIndex = function(oTd){
|
||||
SortableTable.getCellIndex = function (oTd) {
|
||||
let cells = oTd.parentNode.cells,
|
||||
l = cells.length, i;
|
||||
for (i = 0; cells[i] != oTd && i < l; i++){}
|
||||
for (i = 0; cells[i] !== oTd && i < l; i++) { }
|
||||
return i;
|
||||
};
|
||||
|
||||
|
@ -207,10 +257,10 @@ export default class AdapterSortableTable{
|
|||
* Overrides initHeader in order to handle filters row position
|
||||
* @param {Array} oSortTypes
|
||||
*/
|
||||
SortableTable.prototype.initHeader = function(oSortTypes){
|
||||
SortableTable.prototype.initHeader = function (oSortTypes) {
|
||||
let stt = this;
|
||||
if (!stt.tHead){
|
||||
if(tf.gridLayout){
|
||||
if (!stt.tHead) {
|
||||
if (tf.gridLayout) {
|
||||
stt.tHead = tf.feature('gridLayout').headTbl.tHead;
|
||||
} else {
|
||||
return;
|
||||
|
@ -225,15 +275,15 @@ export default class AdapterSortableTable{
|
|||
|
||||
for (let i = 0; i < l; i++) {
|
||||
c = cells[i];
|
||||
if (stt.sortTypes[i] !== null && stt.sortTypes[i] !== 'None'){
|
||||
if (stt.sortTypes[i] !== null && stt.sortTypes[i] !== 'None') {
|
||||
c.style.cursor = 'pointer';
|
||||
img = Dom.create('img',
|
||||
img = createElm('img',
|
||||
['src', adpt.imgPath + adpt.imgBlank]);
|
||||
c.appendChild(img);
|
||||
if (stt.sortTypes[i] !== null){
|
||||
c.setAttribute( '_sortType', stt.sortTypes[i]);
|
||||
if (stt.sortTypes[i] !== null) {
|
||||
c.setAttribute('_sortType', stt.sortTypes[i]);
|
||||
}
|
||||
Event.add(c, 'click', stt._headerOnclick);
|
||||
addEvt(c, 'click', stt._headerOnclick);
|
||||
} else {
|
||||
c.setAttribute('_sortType', oSortTypes[i]);
|
||||
c._sortType = 'None';
|
||||
|
@ -245,40 +295,44 @@ export default class AdapterSortableTable{
|
|||
/**
|
||||
* Overrides updateHeaderArrows in order to handle arrows indicators
|
||||
*/
|
||||
SortableTable.prototype.updateHeaderArrows = function(){
|
||||
SortableTable.prototype.updateHeaderArrows = function () {
|
||||
let stt = this;
|
||||
let cells, l, img;
|
||||
|
||||
// external headers
|
||||
if(adpt.asyncSort && adpt.triggerIds.length > 0){
|
||||
if (adpt.asyncSort && adpt.triggerIds.length > 0) {
|
||||
let triggers = adpt.triggerIds;
|
||||
cells = [];
|
||||
l = triggers.length;
|
||||
for(let j=0; j<triggers.length; j++){
|
||||
cells.push(Dom.id(triggers[j]));
|
||||
for (let j = 0; j < l; j++) {
|
||||
cells.push(elm(triggers[j]));
|
||||
}
|
||||
} else {
|
||||
if(!this.tHead){
|
||||
if (!this.tHead) {
|
||||
return;
|
||||
}
|
||||
cells = stt.tHead.rows[stt.headersRow].cells;
|
||||
l = cells.length;
|
||||
}
|
||||
for(let i = 0; i < l; i++){
|
||||
let cellAttr = cells[i].getAttribute('_sortType');
|
||||
if(cellAttr !== null && cellAttr !== 'None'){
|
||||
img = cells[i].lastChild || cells[i];
|
||||
if(img.nodeName.toLowerCase() !== 'img'){
|
||||
img = Dom.create('img',
|
||||
for (let i = 0; i < l; i++) {
|
||||
let cell = cells[i];
|
||||
if (!cell) {
|
||||
continue;
|
||||
}
|
||||
let cellAttr = cell.getAttribute('_sortType');
|
||||
if (cellAttr !== null && cellAttr !== 'None') {
|
||||
img = cell.lastChild || cell;
|
||||
if (img.nodeName.toLowerCase() !== 'img') {
|
||||
img = createElm('img',
|
||||
['src', adpt.imgPath + adpt.imgBlank]);
|
||||
cells[i].appendChild(img);
|
||||
cell.appendChild(img);
|
||||
}
|
||||
if (i === stt.sortColumn){
|
||||
img.className = adpt.imgClassName +' '+
|
||||
if (i === stt.sortColumn) {
|
||||
img.className = adpt.imgClassName + ' ' +
|
||||
(this.descending ?
|
||||
adpt.imgDescClassName :
|
||||
adpt.imgAscClassName);
|
||||
} else{
|
||||
} else {
|
||||
img.className = adpt.imgClassName;
|
||||
}
|
||||
}
|
||||
|
@ -292,11 +346,11 @@ export default class AdapterSortableTable{
|
|||
* @param {Number} nColumn
|
||||
* @return {String}
|
||||
*/
|
||||
SortableTable.prototype.getRowValue = function(oRow, sType, nColumn){
|
||||
SortableTable.prototype.getRowValue = function (oRow, sType, nColumn) {
|
||||
let stt = this;
|
||||
// if we have defined a custom getRowValue use that
|
||||
let sortTypeInfo = stt._sortTypeInfo[sType];
|
||||
if (sortTypeInfo && sortTypeInfo.getRowValue){
|
||||
if (sortTypeInfo && sortTypeInfo.getRowValue) {
|
||||
return sortTypeInfo.getRowValue(oRow, nColumn);
|
||||
}
|
||||
let c = oRow.cells[nColumn];
|
||||
|
@ -307,87 +361,97 @@ export default class AdapterSortableTable{
|
|||
/**
|
||||
* Overrides getInnerText in order to avoid Firefox unexpected sorting
|
||||
* behaviour with untrimmed text elements
|
||||
* @param {Object} oNode DOM element
|
||||
* @param {Object} cell DOM element
|
||||
* @return {String} DOM element inner text
|
||||
*/
|
||||
SortableTable.getInnerText = function(oNode){
|
||||
if(!oNode){
|
||||
SortableTable.getInnerText = function (cell) {
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
if(oNode.getAttribute(adpt.customKey)){
|
||||
return oNode.getAttribute(adpt.customKey);
|
||||
if (cell.getAttribute(adpt.customKey)) {
|
||||
return cell.getAttribute(adpt.customKey);
|
||||
} else {
|
||||
return Dom.getText(oNode);
|
||||
return tf.getCellValue(cell);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
addSortType(){
|
||||
var args = arguments;
|
||||
SortableTable.prototype.addSortType(args[0], args[1], args[2], args[3]);
|
||||
/**
|
||||
* Adds a sort type
|
||||
*/
|
||||
addSortType(...args) {
|
||||
// Extract the arguments
|
||||
let [id, caster, sorter, getRowValue] = args;
|
||||
SortableTable.prototype.addSortType(id, caster, sorter, getRowValue);
|
||||
}
|
||||
|
||||
setSortTypes(){
|
||||
/**
|
||||
* Sets the sort types on a column basis
|
||||
* @private
|
||||
*/
|
||||
setSortTypes() {
|
||||
let tf = this.tf,
|
||||
sortTypes = this.sortTypes,
|
||||
_sortTypes = [];
|
||||
|
||||
for(let i=0; i<tf.nbCells; i++){
|
||||
tf.eachCol((i) => {
|
||||
let colType;
|
||||
|
||||
if(sortTypes[i]){
|
||||
colType = sortTypes[i].toLowerCase();
|
||||
if(colType === 'none'){
|
||||
colType = 'None';
|
||||
}
|
||||
} else { // resolve column types
|
||||
if(tf.hasColNbFormat && tf.colNbFormat[i] !== null){
|
||||
colType = tf.colNbFormat[i].toLowerCase();
|
||||
} else if(tf.hasColDateType && tf.colDateType[i] !== null){
|
||||
colType = tf.colDateType[i].toLowerCase()+'date';
|
||||
if (sortTypes[i]) {
|
||||
colType = sortTypes[i];
|
||||
if (isObj(colType)) {
|
||||
if (colType.type === DATE) {
|
||||
colType = this._addDateType(i, sortTypes);
|
||||
}
|
||||
else if (colType.type === FORMATTED_NUMBER) {
|
||||
let decimal = colType.decimal || tf.decimalSeparator;
|
||||
colType = this._addNumberType(i, decimal);
|
||||
}
|
||||
} else {
|
||||
colType = 'String';
|
||||
colType = colType.toLowerCase();
|
||||
if (colType === DATE) {
|
||||
colType = this._addDateType(i, sortTypes);
|
||||
}
|
||||
else if (colType === FORMATTED_NUMBER ||
|
||||
colType === NUMBER) {
|
||||
colType = this._addNumberType(i, tf.decimalSeparator);
|
||||
}
|
||||
else if (colType === NONE) {
|
||||
// TODO: normalise 'none' vs 'None'
|
||||
colType = 'None';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
colType = STRING;
|
||||
}
|
||||
_sortTypes.push(colType);
|
||||
}
|
||||
});
|
||||
|
||||
//Public TF method to add sort type
|
||||
|
||||
//Custom sort types
|
||||
this.addSortType('number', Number);
|
||||
this.addSortType('caseinsensitivestring', SortableTable.toUpperCase);
|
||||
this.addSortType('date', SortableTable.toDate);
|
||||
this.addSortType('string');
|
||||
this.addSortType('us', usNumberConverter);
|
||||
this.addSortType('eu', euNumberConverter);
|
||||
this.addSortType('dmydate', dmyDateConverter );
|
||||
this.addSortType('ymddate', ymdDateConverter);
|
||||
this.addSortType('mdydate', mdyDateConverter);
|
||||
this.addSortType('ddmmmyyyydate', ddmmmyyyyDateConverter);
|
||||
this.addSortType('ipaddress', ipAddress, sortIP);
|
||||
this.addSortType(STRING);
|
||||
this.addSortType(IP_ADDRESS, ipAddress, sortIP);
|
||||
|
||||
this.stt = new SortableTable(tf.tbl, _sortTypes);
|
||||
this.stt = new SortableTable(tf.dom(), _sortTypes);
|
||||
|
||||
/*** external table headers adapter ***/
|
||||
if(this.asyncSort && this.triggerIds.length > 0){
|
||||
if (this.asyncSort && this.triggerIds.length > 0) {
|
||||
let triggers = this.triggerIds;
|
||||
for(let j=0; j<triggers.length; j++){
|
||||
if(triggers[j] === null){
|
||||
for (let j = 0; j < triggers.length; j++) {
|
||||
if (triggers[j] === null) {
|
||||
continue;
|
||||
}
|
||||
let trigger = Dom.id(triggers[j]);
|
||||
if(trigger){
|
||||
let trigger = elm(triggers[j]);
|
||||
if (trigger) {
|
||||
trigger.style.cursor = 'pointer';
|
||||
|
||||
Event.add(trigger, 'click', (evt) => {
|
||||
addEvt(trigger, 'click', (evt) => {
|
||||
let elm = evt.target;
|
||||
if(!this.tf.sort){
|
||||
if (!this.tf.sort) {
|
||||
return;
|
||||
}
|
||||
this.stt.asyncSort(
|
||||
Arr.indexByValue(triggers, elm.id, true)
|
||||
);
|
||||
this.stt.asyncSort(triggers.indexOf(elm.id));
|
||||
});
|
||||
trigger.setAttribute('_sortType', _sortTypes[j]);
|
||||
}
|
||||
|
@ -395,69 +459,77 @@ export default class AdapterSortableTable{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy sort
|
||||
*/
|
||||
destroy(){
|
||||
_addDateType(colIndex, types) {
|
||||
let tf = this.tf;
|
||||
let dateType = tf.feature('dateType');
|
||||
let locale = dateType.getOptions(colIndex, types).locale || tf.locale;
|
||||
let colType = `${DATE}-${locale}`;
|
||||
|
||||
this.addSortType(colType, (value) => {
|
||||
let parsedDate = dateType.parse(value, locale);
|
||||
// Invalid date defaults to Wed Feb 04 -768 11:00:00
|
||||
return isNaN(+parsedDate) ? new Date(-86400000000000) : parsedDate;
|
||||
});
|
||||
return colType;
|
||||
}
|
||||
|
||||
_addNumberType(colIndex, decimal) {
|
||||
let colType = `${FORMATTED_NUMBER}${decimal === '.' ? '' : '-custom'}`;
|
||||
|
||||
this.addSortType(colType, (value) => {
|
||||
return parseNb(value, decimal);
|
||||
});
|
||||
return colType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove extension
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
let tf = this.tf;
|
||||
this.emitter.off(['sort'], bound(this.sortByColumnIndexHandler, this));
|
||||
this.sorted = false;
|
||||
this.initialized = false;
|
||||
this.stt.destroy();
|
||||
|
||||
let ids = tf.getFiltersId();
|
||||
for (let idx = 0; idx < ids.length; idx++){
|
||||
for (let idx = 0; idx < ids.length; idx++) {
|
||||
let header = tf.getHeaderElement(idx);
|
||||
let img = Dom.tag(header, 'img');
|
||||
let img = tag(header, 'img');
|
||||
|
||||
if(img.length === 1){
|
||||
if (img.length === 1) {
|
||||
header.removeChild(img[0]);
|
||||
}
|
||||
}
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Converters
|
||||
function usNumberConverter(s){
|
||||
return Helpers.removeNbFormat(s, 'us');
|
||||
}
|
||||
function euNumberConverter(s){
|
||||
return Helpers.removeNbFormat(s, 'eu');
|
||||
}
|
||||
function dateConverter(s, format){
|
||||
return DateHelper.format(s, format);
|
||||
}
|
||||
function dmyDateConverter(s){
|
||||
return dateConverter(s, 'DMY');
|
||||
}
|
||||
function mdyDateConverter(s){
|
||||
return dateConverter(s, 'MDY');
|
||||
}
|
||||
function ymdDateConverter(s){
|
||||
return dateConverter(s, 'YMD');
|
||||
}
|
||||
function ddmmmyyyyDateConverter(s){
|
||||
return dateConverter(s, 'DDMMMYYYY');
|
||||
}
|
||||
AdapterSortableTable.meta = {altName: 'sort'};
|
||||
|
||||
function ipAddress(value){
|
||||
//Converters
|
||||
function ipAddress(value) {
|
||||
let vals = value.split('.');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let x in vals) {
|
||||
let val = vals[x];
|
||||
while (3 > val.length){
|
||||
val = '0'+val;
|
||||
while (3 > val.length) {
|
||||
val = '0' + val;
|
||||
}
|
||||
vals[x] = val;
|
||||
}
|
||||
return vals.join('.');
|
||||
}
|
||||
|
||||
function sortIP(a,b){
|
||||
function sortIP(a, b) {
|
||||
let aa = ipAddress(a.value.toLowerCase());
|
||||
let bb = ipAddress(b.value.toLowerCase());
|
||||
if (aa==bb){
|
||||
if (aa === bb) {
|
||||
return 0;
|
||||
} else if (aa<bb){
|
||||
} else if (aa < bb) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// import 'script!sortabletable';
|
||||
import AdapterSortableTable from './adapterSortabletable';
|
||||
import {root} from '../../root';
|
||||
|
||||
if(!window.SortableTable){
|
||||
require('script!sortabletable');
|
||||
if (!root.SortableTable) {
|
||||
require('script-loader!sortabletable');
|
||||
}
|
||||
|
||||
export default AdapterSortableTable;
|
||||
|
|
102
src/feature.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
import {toCamelCase} from './string';
|
||||
|
||||
const NOT_IMPLEMENTED = 'Not implemented.';
|
||||
|
||||
/**
|
||||
* Base class defining the interface of a TableFilter feature
|
||||
*/
|
||||
export class Feature {
|
||||
/**
|
||||
* Creates an instance of Feature
|
||||
* @param {Object} tf TableFilter instance
|
||||
* @param {Class} feature Feature class for TableFilter registration
|
||||
*/
|
||||
constructor(tf, cls) {
|
||||
cls.meta = cls.meta || {};
|
||||
|
||||
/**
|
||||
* TableFilter instance
|
||||
* @type {TableFilter}
|
||||
*/
|
||||
this.tf = tf;
|
||||
|
||||
/**
|
||||
* Feature name is the camelised class name as per TableFilter's
|
||||
* convention
|
||||
* @type {String}
|
||||
*/
|
||||
this.feature = cls.meta.altName || cls.meta.name
|
||||
|| toCamelCase(cls.name);
|
||||
|
||||
/**
|
||||
* TableFilter feature setting
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enabled = tf[this.feature];
|
||||
|
||||
/**
|
||||
* TableFilter configuration
|
||||
* @type {Object}
|
||||
*/
|
||||
this.config = tf.config();
|
||||
|
||||
/**
|
||||
* TableFilter emitter instance
|
||||
* @type {Emitter}
|
||||
*/
|
||||
this.emitter = tf.emitter;
|
||||
|
||||
/**
|
||||
* Field indicating whether Feature is initialized
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.initialized = false;
|
||||
|
||||
/** Subscribe to destroy event */
|
||||
this.emitter.on(['destroy'], () => this.destroy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the feature
|
||||
*/
|
||||
init() {
|
||||
throw new Error(NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the feature after being disabled
|
||||
*/
|
||||
reset() {
|
||||
this.enable();
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the feature
|
||||
*/
|
||||
destroy() {
|
||||
throw new Error(NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the feature
|
||||
*/
|
||||
enable() {
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the feature
|
||||
*/
|
||||
disable() {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the feature is enabled or not
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isEnabled() {
|
||||
return this.enabled === true;
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* Misc helpers
|
||||
*/
|
||||
|
||||
import Str from './string';
|
||||
|
||||
export default {
|
||||
removeNbFormat(data, format){
|
||||
if(!data){
|
||||
return;
|
||||
}
|
||||
if(!format){
|
||||
format = 'us';
|
||||
}
|
||||
let n = data;
|
||||
if(Str.lower(format) === 'us'){
|
||||
n =+ n.replace(/[^\d\.-]/g,'');
|
||||
} else {
|
||||
n =+ n.replace(/[^\d\,-]/g,'').replace(',','.');
|
||||
}
|
||||
return n;
|
||||
}
|
||||
};
|
|
@ -1,99 +1,145 @@
|
|||
import Dom from '../dom';
|
||||
import {Feature} from '../feature';
|
||||
import {addClass, removeClass} from '../dom';
|
||||
import {defaultsStr} from '../settings';
|
||||
import {bound} from '../event';
|
||||
|
||||
export class AlternateRows{
|
||||
/**
|
||||
* Rows with alternating background color for improved readability
|
||||
*/
|
||||
export class AlternateRows extends Feature {
|
||||
|
||||
/**
|
||||
* Alternating rows color
|
||||
* Creates an instance of AlternateRows.
|
||||
*
|
||||
* @param {Object} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf) {
|
||||
var f = tf.config();
|
||||
//defines css class for even rows
|
||||
this.evenCss = f.even_row_css_class || 'even';
|
||||
//defines css class for odd rows
|
||||
this.oddCss = f.odd_row_css_class || 'odd';
|
||||
super(tf, AlternateRows);
|
||||
|
||||
this.tf = tf;
|
||||
let config = this.config;
|
||||
|
||||
/**
|
||||
* Css class for even rows (default: 'even')
|
||||
* @type {String}
|
||||
*/
|
||||
this.evenCss = defaultsStr(config.even_row_css_class, 'even');
|
||||
|
||||
/**
|
||||
* Css class for odd rows (default: 'odd')
|
||||
* @type {String}
|
||||
*/
|
||||
this.oddCss = defaultsStr(config.odd_row_css_class, 'odd');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets alternating rows color
|
||||
*/
|
||||
init() {
|
||||
var tf = this.tf;
|
||||
if(!tf.hasGrid() && !tf.isFirstLoad){
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
var validRowsIndex = tf.validRowsIndex;
|
||||
var noValidRowsIndex = validRowsIndex===null;
|
||||
//1st index
|
||||
var beginIndex = noValidRowsIndex ? tf.refRow : 0;
|
||||
// nb indexes
|
||||
var indexLen = noValidRowsIndex ?
|
||||
tf.nbFilterableRows+beginIndex :
|
||||
validRowsIndex.length;
|
||||
var idx = 0;
|
||||
|
||||
this.processAll();
|
||||
|
||||
// Subscribe to events
|
||||
this.emitter.on(['row-processed', 'row-paged'],
|
||||
bound(this.processRowHandler, this));
|
||||
this.emitter.on(['column-sorted', 'rows-changed'],
|
||||
bound(this.processAll, this));
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply background to all valid rows
|
||||
*/
|
||||
processAll() {
|
||||
if (!this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
let tf = this.tf;
|
||||
let validRowsIndex = tf.getValidRows(true);
|
||||
let indexLen = validRowsIndex.length;
|
||||
let idx = 0;
|
||||
|
||||
//alternates bg color
|
||||
for(var j=beginIndex; j<indexLen; j++){
|
||||
var rowIdx = noValidRowsIndex ? j : validRowsIndex[j];
|
||||
for (let j = 0; j < indexLen; j++) {
|
||||
let rowIdx = validRowsIndex[j];
|
||||
this.setRowBg(rowIdx, idx);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/remove row background based on row validation
|
||||
* @param {Number} rowIdx Row index
|
||||
* @param {Number} arrIdx Array index
|
||||
* @param {Boolean} isValid Valid row flag
|
||||
*/
|
||||
processRow(rowIdx, arrIdx, isValid) {
|
||||
if (isValid) {
|
||||
this.setRowBg(rowIdx, arrIdx);
|
||||
} else {
|
||||
this.removeRowBg(rowIdx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets row background color
|
||||
* @param {Number} rowIdx Row index
|
||||
* @param {Number} idx Valid rows collection index needed to calculate bg
|
||||
* color
|
||||
* @private
|
||||
*/
|
||||
setRowBg(rowIdx, idx) {
|
||||
if(!this.tf.alternateBgs || isNaN(rowIdx)){
|
||||
if (!this.isEnabled() || isNaN(rowIdx)) {
|
||||
return;
|
||||
}
|
||||
var rows = this.tf.tbl.rows;
|
||||
var i = isNaN(idx) ? rowIdx : idx;
|
||||
let rows = this.tf.dom().rows;
|
||||
let i = isNaN(idx) ? rowIdx : idx;
|
||||
this.removeRowBg(rowIdx);
|
||||
|
||||
Dom.addClass(
|
||||
rows[rowIdx],
|
||||
(i%2) ? this.evenCss : this.oddCss
|
||||
);
|
||||
addClass(rows[rowIdx], (i % 2) ? this.evenCss : this.oddCss);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes row background color
|
||||
* @param {Number} idx Row index
|
||||
* @private
|
||||
*/
|
||||
removeRowBg(idx) {
|
||||
if(isNaN(idx)){
|
||||
if (isNaN(idx)) {
|
||||
return;
|
||||
}
|
||||
var rows = this.tf.tbl.rows;
|
||||
Dom.removeClass(rows[idx], this.oddCss);
|
||||
Dom.removeClass(rows[idx], this.evenCss);
|
||||
let rows = this.tf.dom().rows;
|
||||
removeClass(rows[idx], this.oddCss);
|
||||
removeClass(rows[idx], this.evenCss);
|
||||
}
|
||||
|
||||
/** @private */
|
||||
processRowHandler(tf, rowIndex, arrIndex, isValid) {
|
||||
this.processRow(rowIndex, arrIndex, isValid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all alternating backgrounds
|
||||
*/
|
||||
remove() {
|
||||
if(!this.tf.hasGrid()){
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
for(var i=this.tf.refRow; i<this.tf.nbRows; i++){
|
||||
this.removeRowBg(i);
|
||||
}
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.tf.alternateBgs = true;
|
||||
}
|
||||
let eachRow = this.tf.eachRow(0);
|
||||
eachRow((row, i) => this.removeRowBg(i));
|
||||
|
||||
disable() {
|
||||
this.tf.alternateBgs = false;
|
||||
// Unsubscribe to events
|
||||
this.emitter.off(['row-processed', 'row-paged'],
|
||||
bound(this.processRowHandler, this));
|
||||
this.emitter.off(['column-sorted', 'rows-changed'],
|
||||
bound(this.processAll, this));
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
160
src/modules/baseDropdown.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
import {Feature} from '../feature';
|
||||
import {
|
||||
ignoreCase, numSortAsc, numSortDesc,
|
||||
dateSortAsc, dateSortDesc, sortNumberStr, sortDateStr
|
||||
} from '../sort';
|
||||
import {isArray, isObj, isEmpty} from '../types';
|
||||
import {NUMBER, FORMATTED_NUMBER, DATE} from '../const';
|
||||
|
||||
/**
|
||||
* Base class for Dropdown and CheckList UI components
|
||||
* @export
|
||||
* @class BaseDropdown
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class BaseDropdown extends Feature {
|
||||
|
||||
/**
|
||||
* Creates an instance of BaseDropdown
|
||||
* @param {TableFilter} tf
|
||||
*/
|
||||
constructor(tf, cls) {
|
||||
super(tf, cls);
|
||||
|
||||
let f = this.config;
|
||||
|
||||
/**
|
||||
* Filter options custom sorter on a column basis
|
||||
* @type {Object}
|
||||
*/
|
||||
this.customSorter = isObj(f.filter_options_sorter) &&
|
||||
isArray(f.filter_options_sorter.col) &&
|
||||
isArray(f.filter_options_sorter.comparer) ?
|
||||
f.filter_options_sorter :
|
||||
null;
|
||||
|
||||
// TODO: move here all properties shared by Dropdown and CheckList
|
||||
|
||||
/**
|
||||
* Has custom options
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isCustom = false;
|
||||
|
||||
/**
|
||||
* List of options values
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.opts = [];
|
||||
|
||||
/**
|
||||
* List of options texts for custom values
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.optsTxt = [];
|
||||
|
||||
/**
|
||||
* List of options to be excluded from the checklist filter
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.excludedOpts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort passed options based on the type of the specified column
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Array} [options=[]] Collection of values
|
||||
* @return {Array} Sorted values
|
||||
* @private
|
||||
*/
|
||||
sortOptions(colIndex, options = []) {
|
||||
let {tf} = this;
|
||||
|
||||
if (tf.isCustomOptions(colIndex) || !tf.sortSlc ||
|
||||
(isArray(tf.sortSlc) && tf.sortSlc.indexOf(colIndex) === -1)) {
|
||||
return options;
|
||||
}
|
||||
|
||||
let { caseSensitive, sortFilterOptionsDesc } = tf;
|
||||
let isSortDesc = sortFilterOptionsDesc.indexOf(colIndex) !== -1;
|
||||
let compareFn;
|
||||
|
||||
if (this.customSorter &&
|
||||
this.customSorter.col.indexOf(colIndex) !== -1) {
|
||||
var idx = this.customSorter.col.indexOf(colIndex);
|
||||
compareFn = this.customSorter.comparer[idx];
|
||||
}
|
||||
else if (tf.hasType(colIndex, [NUMBER, FORMATTED_NUMBER])) {
|
||||
let decimal = tf.getDecimal(colIndex);
|
||||
let comparer = isSortDesc ? numSortDesc : numSortAsc;
|
||||
compareFn = sortNumberStr(comparer, decimal);
|
||||
}
|
||||
else if (tf.hasType(colIndex, [DATE])) {
|
||||
let locale = tf.feature('dateType').getLocale(colIndex);
|
||||
let comparer = isSortDesc ? dateSortDesc : dateSortAsc;
|
||||
compareFn = sortDateStr(comparer, locale);
|
||||
} else { // string
|
||||
compareFn = caseSensitive ? undefined : ignoreCase;
|
||||
if (isSortDesc) {
|
||||
return options.sort(compareFn).reverse();
|
||||
}
|
||||
}
|
||||
|
||||
return options.sort(compareFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate filters of specified columns and maintain selection if any
|
||||
* @param {Array} colIndexes Collection of column indexes
|
||||
* @private
|
||||
*/
|
||||
refreshFilters(colIndexes) {
|
||||
colIndexes.forEach((colIdx) => {
|
||||
let values = this.getValues(colIdx);
|
||||
this.build(colIdx, this.tf.linkedFilters);
|
||||
this.selectOptions(colIdx, values);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check passed row contains a valid linked value
|
||||
* @param {Number} rowIdx Row index
|
||||
* @param {Number} activeFilterIdx Current active filter index
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isValidLinkedValue(rowIdx, activeFilterIdx) {
|
||||
let tf = this.tf;
|
||||
|
||||
if (tf.disableExcludedOptions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tf.paging) {
|
||||
if (!isEmpty(activeFilterIdx) && tf.isRowValid(rowIdx)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (tf.isRowDisplayed(rowIdx)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh linked filters to offer only selected options
|
||||
*/
|
||||
linkFilters() {
|
||||
let tf = this.tf;
|
||||
if (!tf.linkedFilters || !tf.activeFilterId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.refreshAll();
|
||||
}
|
||||
}
|
|
@ -1,290 +1,326 @@
|
|||
import Dom from '../dom';
|
||||
import Arr from '../array';
|
||||
import Str from '../string';
|
||||
import Sort from '../sort';
|
||||
import Event from '../event';
|
||||
import {BaseDropdown} from './baseDropdown';
|
||||
import {
|
||||
addClass, createCheckItem, createText, createElm, elm, removeClass, tag
|
||||
} from '../dom';
|
||||
import {has} from '../array';
|
||||
import {matchCase, trim, rgxEsc} from '../string';
|
||||
import {addEvt, removeEvt, targetEvt} from '../event';
|
||||
import {isEmpty} from '../types';
|
||||
import {CHECKLIST, NONE} from '../const';
|
||||
import {defaultsStr, defaultsBool} from '../settings';
|
||||
|
||||
export class CheckList{
|
||||
/**
|
||||
* Checklist filter UI component
|
||||
* @export
|
||||
* @class CheckList
|
||||
* @extends {BaseDropdown}
|
||||
*/
|
||||
export class CheckList extends BaseDropdown {
|
||||
|
||||
/**
|
||||
* Checklist UI component
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of CheckList
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
// Configuration object
|
||||
var f = tf.config();
|
||||
constructor(tf) {
|
||||
super(tf, CheckList);
|
||||
|
||||
this.checkListDiv = []; //checklist container div
|
||||
//defines css class for div containing checklist filter
|
||||
this.checkListDivCssClass = f.div_checklist_css_class ||
|
||||
'div_checklist';
|
||||
//defines css class for checklist filters
|
||||
this.checkListCssClass = f.checklist_css_class || 'flt_checklist';
|
||||
//defines css class for checklist item (li)
|
||||
this.checkListItemCssClass = f.checklist_item_css_class ||
|
||||
'flt_checklist_item';
|
||||
//defines css class for selected checklist item (li)
|
||||
this.checkListSlcItemCssClass = f.checklist_selected_item_css_class ||
|
||||
'flt_checklist_slc_item';
|
||||
//Load on demand text
|
||||
this.activateCheckListTxt = f.activate_checklist_text ||
|
||||
'Click to load filter data';
|
||||
//defines css class for checklist filters
|
||||
this.checkListItemDisabledCssClass =
|
||||
f.checklist_item_disabled_css_class ||
|
||||
'flt_checklist_item_disabled';
|
||||
this.enableCheckListResetFilter =
|
||||
f.enable_checklist_reset_filter===false ? false : true;
|
||||
//checklist filter container div
|
||||
this.prfxCheckListDiv = 'chkdiv_';
|
||||
let f = this.config;
|
||||
|
||||
this.isCustom = null;
|
||||
this.opts = null;
|
||||
this.optsTxt = null;
|
||||
this.excludedOpts = null;
|
||||
/**
|
||||
* List of container DOM elements
|
||||
* @type {Array}
|
||||
*/
|
||||
this.containers = [];
|
||||
|
||||
this.tf = tf;
|
||||
}
|
||||
/**
|
||||
* Css class for the container of the checklist filter (div)
|
||||
* @type {String}
|
||||
*/
|
||||
this.containerCssClass = defaultsStr(f.div_checklist_css_class,
|
||||
'div_checklist');
|
||||
|
||||
// TODO: move event here
|
||||
onChange(evt){
|
||||
let elm = evt.target;
|
||||
this.tf.activeFilterId = elm.getAttribute('id');
|
||||
this.tf.activeFlt = Dom.id(this.tf.activeFilterId);
|
||||
this.tf.Evt.onSlcChange.call(this.tf, evt);
|
||||
}
|
||||
/**
|
||||
* Css class for the checklist filter element (ul)
|
||||
* @type {String}
|
||||
*/
|
||||
this.filterCssClass = defaultsStr(f.checklist_css_class,
|
||||
'flt_checklist');
|
||||
|
||||
optionClick(evt){
|
||||
this.setCheckListValues(evt.target);
|
||||
this.onChange(evt);
|
||||
}
|
||||
/**
|
||||
* Css class for the item of a checklist (li)
|
||||
* @type {String}
|
||||
*/
|
||||
this.itemCssClass = defaultsStr(f.checklist_item_css_class,
|
||||
'flt_checklist_item');
|
||||
|
||||
/**
|
||||
* Build checklist UI asynchronously
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} isExternal Render in external container
|
||||
* @param {String} extFltId External container id
|
||||
*/
|
||||
build(colIndex, isExternal, extFltId){
|
||||
var tf = this.tf;
|
||||
tf.EvtManager(
|
||||
tf.Evt.name.checklist,
|
||||
{ slcIndex:colIndex, slcExternal:isExternal, slcId:extFltId }
|
||||
/**
|
||||
* Css class for a selected item of a checklist (li)
|
||||
* @type {String}
|
||||
*/
|
||||
this.selectedItemCssClass = defaultsStr(
|
||||
f.checklist_selected_item_css_class,
|
||||
'flt_checklist_slc_item'
|
||||
);
|
||||
|
||||
/**
|
||||
* Text placed in the filter's container when load filter on demand
|
||||
* feature is enabled
|
||||
* @type {String}
|
||||
*/
|
||||
this.activateText = defaultsStr(
|
||||
f.activate_checklist_text,
|
||||
'Click to load filter data'
|
||||
);
|
||||
|
||||
/**
|
||||
* Css class for a disabled item of a checklist (li)
|
||||
* @type {String}
|
||||
*/
|
||||
this.disabledItemCssClass = defaultsStr(
|
||||
f.checklist_item_disabled_css_class,
|
||||
'flt_checklist_item_disabled'
|
||||
);
|
||||
|
||||
/**
|
||||
* Enable the reset filter option as first item
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableResetOption = defaultsBool(f.enable_checklist_reset_filter,
|
||||
true);
|
||||
|
||||
/**
|
||||
* Prefix for container element ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.prfx = 'chkdiv_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checklist option click event handler
|
||||
* @param {Event} evt
|
||||
* @private
|
||||
*/
|
||||
optionClick(evt) {
|
||||
let elm = targetEvt(evt);
|
||||
let tf = this.tf;
|
||||
|
||||
this.emitter.emit('filter-focus', tf, elm);
|
||||
this.setItemOption(elm);
|
||||
tf.filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checklist container click event handler for load-on-demand feature
|
||||
* @param {Event} evt
|
||||
* @private
|
||||
*/
|
||||
onCheckListClick(evt) {
|
||||
let elm = targetEvt(evt);
|
||||
if (this.tf.loadFltOnDemand && elm.getAttribute('filled') === '0') {
|
||||
let ct = elm.getAttribute('ct');
|
||||
let div = this.containers[ct];
|
||||
this.build(ct);
|
||||
removeEvt(div, 'click', (evt) => this.onCheckListClick(evt));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh all checklist filters
|
||||
*/
|
||||
refreshAll() {
|
||||
let colIdxs = this.tf.getFiltersByType(CHECKLIST, true);
|
||||
this.refreshFilters(colIdxs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize checklist filter
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} isExternal External filter flag
|
||||
* @param {DOMElement} container Dom element containing the filter
|
||||
*/
|
||||
init(colIndex, isExternal, container) {
|
||||
let tf = this.tf;
|
||||
let externalFltTgtId = isExternal ?
|
||||
tf.externalFltIds[colIndex] : null;
|
||||
|
||||
let divCont = createElm('div',
|
||||
['id', `${this.prfx}${colIndex}_${tf.id}`],
|
||||
['ct', colIndex], ['filled', '0']);
|
||||
divCont.className = this.containerCssClass;
|
||||
|
||||
//filter is appended in desired element
|
||||
if (externalFltTgtId) {
|
||||
elm(externalFltTgtId).appendChild(divCont);
|
||||
} else {
|
||||
container.appendChild(divCont);
|
||||
}
|
||||
|
||||
this.containers[colIndex] = divCont;
|
||||
tf.fltIds.push(tf.buildFilterId(colIndex));
|
||||
|
||||
if (!tf.loadFltOnDemand) {
|
||||
this.build(colIndex);
|
||||
} else {
|
||||
addEvt(divCont, 'click', (evt) => this.onCheckListClick(evt));
|
||||
divCont.appendChild(createText(this.activateText));
|
||||
}
|
||||
|
||||
this.emitter.on(
|
||||
['build-checklist-filter'],
|
||||
(tf, colIndex, isLinked) => this.build(colIndex, isLinked)
|
||||
);
|
||||
|
||||
this.emitter.on(
|
||||
['select-checklist-options'],
|
||||
(tf, colIndex, values) => this.selectOptions(colIndex, values)
|
||||
);
|
||||
|
||||
this.emitter.on(['rows-changed'], () => this.refreshAll());
|
||||
|
||||
this.emitter.on(['after-filtering'], () => this.linkFilters());
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build checklist UI
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} isExternal Render in external container
|
||||
* @param {String} extFltId External container id
|
||||
* @param {Boolean} isLinked Enable linked filters behaviour
|
||||
*/
|
||||
_build(colIndex, isExternal=false, extFltId=null){
|
||||
var tf = this.tf;
|
||||
colIndex = parseInt(colIndex, 10);
|
||||
build(colIndex, isLinked = false) {
|
||||
let tf = this.tf;
|
||||
colIndex = Number(colIndex);
|
||||
|
||||
this.emitter.emit('before-populating-filter', tf, colIndex);
|
||||
|
||||
/** @inherited */
|
||||
this.opts = [];
|
||||
/** @inherited */
|
||||
this.optsTxt = [];
|
||||
|
||||
var divFltId = this.prfxCheckListDiv+colIndex+'_'+tf.id;
|
||||
if((!Dom.id(divFltId) && !isExternal) ||
|
||||
(!Dom.id(extFltId) && isExternal)){
|
||||
return;
|
||||
}
|
||||
let flt = this.containers[colIndex];
|
||||
let ul = createElm('ul',
|
||||
['id', tf.fltIds[colIndex]],
|
||||
['colIndex', colIndex]);
|
||||
ul.className = this.filterCssClass;
|
||||
|
||||
var flt = !isExternal ? this.checkListDiv[colIndex] : Dom.id(extFltId);
|
||||
var ul = Dom.create(
|
||||
'ul', ['id', tf.fltIds[colIndex]], ['colIndex', colIndex]);
|
||||
ul.className = this.checkListCssClass;
|
||||
Event.add(ul, 'change', (evt) => { this.onChange(evt); });
|
||||
|
||||
var rows = tf.tbl.rows;
|
||||
let caseSensitive = tf.caseSensitive;
|
||||
/** @inherited */
|
||||
this.isCustom = tf.isCustomOptions(colIndex);
|
||||
|
||||
var activeFlt;
|
||||
if(tf.linkedFilters && tf.activeFilterId){
|
||||
activeFlt = tf.activeFilterId.split('_')[0];
|
||||
activeFlt = activeFlt.split(tf.prfxFlt)[1];
|
||||
}
|
||||
|
||||
var filteredDataCol = [];
|
||||
if(tf.linkedFilters && tf.disableExcludedOptions){
|
||||
this.excludedOpts = [];
|
||||
}
|
||||
|
||||
for(var k=tf.refRow; k<tf.nbRows; k++){
|
||||
// always visible rows don't need to appear on selects as always
|
||||
// valid
|
||||
if(tf.hasVisibleRows && tf.visibleRows.indexOf(k) !== -1){
|
||||
continue;
|
||||
}
|
||||
|
||||
var cells = rows[k].cells;
|
||||
var ncells = cells.length;
|
||||
|
||||
// checks if row has exact cell #
|
||||
if(ncells !== tf.nbCells || this.isCustom){
|
||||
continue;
|
||||
}
|
||||
|
||||
// this loop retrieves cell data
|
||||
for(var j=0; j<ncells; j++){
|
||||
// WTF: cyclomatic complexity hell :)
|
||||
if((colIndex===j && (!tf.linkedFilters ||
|
||||
(tf.linkedFilters && tf.disableExcludedOptions)))||
|
||||
(colIndex===j && tf.linkedFilters &&
|
||||
((rows[k].style.display === '' && !tf.paging) ||
|
||||
(tf.paging && ((!activeFlt || activeFlt===colIndex )||
|
||||
(activeFlt!=colIndex &&
|
||||
Arr.has(tf.validRowsIndex, k))) )))){
|
||||
var cell_data = tf.getCellData(j, cells[j]);
|
||||
//Vary Peter's patch
|
||||
var cell_string = Str.matchCase(
|
||||
cell_data, tf.matchCase);
|
||||
// checks if celldata is already in array
|
||||
if(!Arr.has(this.opts, cell_string, tf.matchCase)){
|
||||
this.opts.push(cell_data);
|
||||
}
|
||||
var filteredCol = filteredDataCol[j];
|
||||
if(tf.linkedFilters && tf.disableExcludedOptions){
|
||||
if(!filteredCol){
|
||||
filteredCol = tf.getFilteredDataCol(j);
|
||||
}
|
||||
if(!Arr.has(filteredCol,
|
||||
cell_string, tf.matchCase) &&
|
||||
!Arr.has(this.excludedOpts,
|
||||
cell_string, tf.matchCase) &&
|
||||
!tf.isFirstLoad){
|
||||
this.excludedOpts.push(cell_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Retrieves custom values
|
||||
if(this.isCustom){
|
||||
var customValues = tf.getCustomOptions(colIndex);
|
||||
if (this.isCustom) {
|
||||
let customValues = tf.getCustomOptions(colIndex);
|
||||
this.opts = customValues[0];
|
||||
this.optsTxt = customValues[1];
|
||||
}
|
||||
|
||||
if(tf.sortSlc && !this.isCustom){
|
||||
if (!tf.matchCase){
|
||||
this.opts.sort(Sort.ignoreCase);
|
||||
if(this.excludedOpts){
|
||||
this.excludedOpts.sort(Sort.ignoreCase);
|
||||
let activeIdx;
|
||||
let activeFilterId = tf.getActiveFilterId();
|
||||
|
||||
if (isLinked && activeFilterId) {
|
||||
activeIdx = tf.getColumnIndexFromFilterId(activeFilterId);
|
||||
}
|
||||
|
||||
let filteredDataCol = [];
|
||||
if (isLinked && tf.disableExcludedOptions) {
|
||||
/** @inherited */
|
||||
this.excludedOpts = [];
|
||||
}
|
||||
|
||||
flt.innerHTML = '';
|
||||
|
||||
let eachRow = tf.eachRow();
|
||||
eachRow(
|
||||
(row) => {
|
||||
let cellValue = tf.getCellValue(row.cells[colIndex]);
|
||||
//Vary Peter's patch
|
||||
let cellString = matchCase(cellValue, caseSensitive);
|
||||
// checks if celldata is already in array
|
||||
if (!has(this.opts, cellString, caseSensitive)) {
|
||||
this.opts.push(cellValue);
|
||||
}
|
||||
} else {
|
||||
this.opts.sort();
|
||||
if(this.excludedOpts){
|
||||
this.excludedOpts.sort();
|
||||
let filteredCol = filteredDataCol[colIndex];
|
||||
if (isLinked && tf.disableExcludedOptions) {
|
||||
if (!filteredCol) {
|
||||
filteredCol = tf.getVisibleColumnValues(colIndex);
|
||||
}
|
||||
if (!has(filteredCol, cellString, caseSensitive) &&
|
||||
!has(this.excludedOpts, cellString, caseSensitive)) {
|
||||
this.excludedOpts.push(cellValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
// continue conditions function
|
||||
(row, k) => {
|
||||
// excluded rows don't need to appear on selects as always valid
|
||||
if (tf.excludeRows.indexOf(k) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// checks if row has expected number of cells
|
||||
if (row.cells.length !== tf.nbCells || this.isCustom) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isLinked && !this.isValidLinkedValue(k, activeIdx)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
//asc sort
|
||||
if(tf.sortNumAsc && Arr.has(tf.sortNumAsc, colIndex)){
|
||||
try{
|
||||
this.opts.sort(numSortAsc);
|
||||
if(this.excludedOpts){
|
||||
this.excludedOpts.sort(numSortAsc);
|
||||
}
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort(numSortAsc);
|
||||
}
|
||||
} catch(e) {
|
||||
this.opts.sort();
|
||||
if(this.excludedOpts){
|
||||
this.excludedOpts.sort();
|
||||
}
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort();
|
||||
}
|
||||
}//in case there are alphanumeric values
|
||||
}
|
||||
//desc sort
|
||||
if(tf.sortNumDesc && Arr.has(tf.sortNumDesc, colIndex)){
|
||||
try{
|
||||
this.opts.sort(numSortDesc);
|
||||
if(this.excludedOpts){
|
||||
this.excludedOpts.sort(numSortDesc);
|
||||
}
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort(numSortDesc);
|
||||
}
|
||||
} catch(e) {
|
||||
this.opts.sort();
|
||||
if(this.excludedOpts){
|
||||
this.excludedOpts.sort(); }
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort();
|
||||
}
|
||||
}//in case there are alphanumeric values
|
||||
);
|
||||
|
||||
//sort options
|
||||
this.opts = this.sortOptions(colIndex, this.opts);
|
||||
if (this.excludedOpts) {
|
||||
this.excludedOpts = this.sortOptions(colIndex, this.excludedOpts);
|
||||
}
|
||||
|
||||
this.addChecks(colIndex, ul, tf.separator);
|
||||
this.addChecks(colIndex, ul);
|
||||
|
||||
if(tf.loadFltOnDemand){
|
||||
if (tf.loadFltOnDemand) {
|
||||
flt.innerHTML = '';
|
||||
}
|
||||
flt.appendChild(ul);
|
||||
flt.setAttribute('filled', '1');
|
||||
|
||||
this.emitter.emit('after-populating-filter', tf, colIndex, flt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add checklist options
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Object} ul Ul element
|
||||
* @private
|
||||
*/
|
||||
addChecks(colIndex, ul){
|
||||
var tf = this.tf;
|
||||
var chkCt = this.addTChecks(colIndex, ul);
|
||||
var fltArr = []; //remember grid values
|
||||
var store = tf.feature('store');
|
||||
var tmpVal = store ?
|
||||
store.getFilterValues(tf.fltsValuesCookie)[colIndex] : null;
|
||||
if(tmpVal && Str.trim(tmpVal).length > 0){
|
||||
if(tf.hasCustomSlcOptions &&
|
||||
Arr.has(tf.customSlcOptions.cols, colIndex)){
|
||||
fltArr.push(tmpVal);
|
||||
} else {
|
||||
fltArr = tmpVal.split(' '+tf.orOperator+' ');
|
||||
}
|
||||
}
|
||||
addChecks(colIndex, ul) {
|
||||
let tf = this.tf;
|
||||
let chkCt = this.addTChecks(colIndex, ul);
|
||||
|
||||
for(var y=0; y<this.opts.length; y++){
|
||||
var val = this.opts[y]; //item value
|
||||
var lbl = this.isCustom ? this.optsTxt[y] : val; //item text
|
||||
var li = Dom.createCheckItem(
|
||||
tf.fltIds[colIndex]+'_'+(y+chkCt), val, lbl);
|
||||
li.className = this.checkListItemCssClass;
|
||||
if(tf.linkedFilters && tf.disableExcludedOptions &&
|
||||
Arr.has(this.excludedOpts,
|
||||
Str.matchCase(val, tf.matchCase), tf.matchCase)){
|
||||
Dom.addClass(li, this.checkListItemDisabledCssClass);
|
||||
li.check.disabled = true;
|
||||
li.disabled = true;
|
||||
for (let y = 0; y < this.opts.length; y++) {
|
||||
let val = this.opts[y]; //item value
|
||||
let lbl = this.isCustom ? this.optsTxt[y] : val; //item text
|
||||
let fltId = tf.fltIds[colIndex];
|
||||
let lblIdx = y + chkCt;
|
||||
let li = createCheckItem(`${fltId}_${lblIdx}`, val, lbl,
|
||||
['data-idx', lblIdx]);
|
||||
li.className = this.itemCssClass;
|
||||
|
||||
if (tf.linkedFilters && tf.disableExcludedOptions &&
|
||||
has(this.excludedOpts, matchCase(val, tf.caseSensitive),
|
||||
tf.caseSensitive)) {
|
||||
addClass(li, this.disabledItemCssClass);
|
||||
li.check.disabled = true;
|
||||
li.disabled = true;
|
||||
} else {
|
||||
Event.add(li.check, 'click',
|
||||
(evt) => { this.optionClick(evt); });
|
||||
addEvt(li.check, 'click', evt => this.optionClick(evt));
|
||||
}
|
||||
ul.appendChild(li);
|
||||
|
||||
if(val===''){
|
||||
if (val === '') {
|
||||
//item is hidden
|
||||
li.style.display = 'none';
|
||||
}
|
||||
|
||||
/*** remember grid values ***/
|
||||
if(tf.rememberGridValues){
|
||||
if((tf.hasCustomSlcOptions &&
|
||||
Arr.has(tf.customSlcOptions.cols, colIndex) &&
|
||||
fltArr.toString().indexOf(val)!= -1) ||
|
||||
Arr.has(fltArr,
|
||||
Str.matchCase(val, tf.matchCase), tf.matchCase)){
|
||||
li.check.checked = true;
|
||||
this.setCheckListValues(li.check);
|
||||
}
|
||||
li.style.display = NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -293,129 +329,191 @@ export class CheckList{
|
|||
* Add checklist header option
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Object} ul Ul element
|
||||
* @private
|
||||
*/
|
||||
addTChecks(colIndex, ul){
|
||||
var tf = this.tf;
|
||||
var chkCt = 1;
|
||||
var li0 = Dom.createCheckItem(
|
||||
tf.fltIds[colIndex]+'_0', '', tf.displayAllText);
|
||||
li0.className = this.checkListItemCssClass;
|
||||
addTChecks(colIndex, ul) {
|
||||
let tf = this.tf;
|
||||
let chkCt = 1;
|
||||
let fltId = tf.fltIds[colIndex];
|
||||
let li0 = createCheckItem(`${fltId}_0`, '',
|
||||
tf.getClearFilterText(colIndex), ['data-idx', 0]);
|
||||
li0.className = this.itemCssClass;
|
||||
ul.appendChild(li0);
|
||||
|
||||
Event.add(li0.check, 'click', (evt) => {
|
||||
this.optionClick(evt);
|
||||
});
|
||||
addEvt(li0.check, 'click', evt => this.optionClick(evt));
|
||||
|
||||
if(!this.enableCheckListResetFilter){
|
||||
li0.style.display = 'none';
|
||||
if (!this.enableResetOption) {
|
||||
li0.style.display = NONE;
|
||||
}
|
||||
|
||||
if(tf.enableEmptyOption){
|
||||
var li1 = Dom.createCheckItem(
|
||||
tf.fltIds[colIndex]+'_1', tf.emOperator, tf.emptyText);
|
||||
li1.className = this.checkListItemCssClass;
|
||||
if (tf.enableEmptyOption) {
|
||||
let li1 = createCheckItem(`${fltId}_1`, tf.emOperator,
|
||||
tf.emptyText, ['data-idx', 1]);
|
||||
li1.className = this.itemCssClass;
|
||||
ul.appendChild(li1);
|
||||
Event.add(li1.check, 'click', (evt) => {
|
||||
this.optionClick(evt);
|
||||
});
|
||||
addEvt(li1.check, 'click', evt => this.optionClick(evt));
|
||||
chkCt++;
|
||||
}
|
||||
|
||||
if(tf.enableNonEmptyOption){
|
||||
var li2 = Dom.createCheckItem(
|
||||
tf.fltIds[colIndex]+'_2',
|
||||
tf.nmOperator,
|
||||
tf.nonEmptyText
|
||||
);
|
||||
li2.className = this.checkListItemCssClass;
|
||||
if (tf.enableNonEmptyOption) {
|
||||
let li2 = createCheckItem(`${fltId}_2`, tf.nmOperator,
|
||||
tf.nonEmptyText, ['data-idx', 2]);
|
||||
li2.className = this.itemCssClass;
|
||||
ul.appendChild(li2);
|
||||
Event.add(li2.check, 'click', (evt) => {
|
||||
this.optionClick(evt);
|
||||
});
|
||||
addEvt(li2.check, 'click', evt => this.optionClick(evt));
|
||||
chkCt++;
|
||||
}
|
||||
return chkCt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store checked options in DOM element attribute
|
||||
* Set/unset value of passed item option in filter's DOM element attribute
|
||||
* @param {Object} o checklist option DOM element
|
||||
* @private
|
||||
*/
|
||||
setCheckListValues(o){
|
||||
if(!o){
|
||||
setItemOption(o) {
|
||||
if (!o) {
|
||||
return;
|
||||
}
|
||||
var tf = this.tf;
|
||||
var chkValue = o.value; //checked item value
|
||||
var chkIndex = parseInt(o.id.split('_')[2], 10);
|
||||
var filterTag = 'ul', itemTag = 'li';
|
||||
var n = o;
|
||||
|
||||
//ul tag search
|
||||
while(Str.lower(n.nodeName)!==filterTag){
|
||||
n = n.parentNode;
|
||||
}
|
||||
let tf = this.tf;
|
||||
let chkValue = o.value; //checked item value
|
||||
let chkIndex = o.dataset.idx;
|
||||
let colIdx = tf.getColumnIndexFromFilterId(o.id);
|
||||
let n = tf.getFilterElement(parseInt(colIdx, 10));
|
||||
let items = n.childNodes;
|
||||
let li = items[chkIndex];
|
||||
//selected values (ul tag)
|
||||
let slcValues = n.getAttribute('value') || '';
|
||||
//selected items indexes (ul tag)
|
||||
let slcIndexes = n.getAttribute('indexes') || '';
|
||||
|
||||
var li = n.childNodes[chkIndex];
|
||||
var colIndex = n.getAttribute('colIndex');
|
||||
var fltValue = n.getAttribute('value'); //filter value (ul tag)
|
||||
var fltIndexes = n.getAttribute('indexes'); //selected items (ul tag)
|
||||
|
||||
if(o.checked){
|
||||
if (o.checked) {
|
||||
//show all item
|
||||
if(chkValue===''){
|
||||
if((fltIndexes && fltIndexes!=='')){
|
||||
//items indexes
|
||||
var indSplit = fltIndexes.split(tf.separator);
|
||||
//checked items loop
|
||||
for(var u=0; u<indSplit.length; u++){
|
||||
//checked item
|
||||
var cChk = Dom.id(tf.fltIds[colIndex]+'_'+indSplit[u]);
|
||||
if(cChk){
|
||||
cChk.checked = false;
|
||||
Dom.removeClass(
|
||||
n.childNodes[indSplit[u]],
|
||||
this.checkListSlcItemCssClass
|
||||
);
|
||||
}
|
||||
if (chkValue === '') {
|
||||
//items indexes
|
||||
let indexes = slcIndexes.split(tf.separator);
|
||||
indexes.forEach(idx => {
|
||||
idx = Number(idx);
|
||||
let li = items[idx];
|
||||
let chx = tag(li, 'input')[0];
|
||||
if (chx && idx > 0) {
|
||||
chx.checked = false;
|
||||
removeClass(li, this.selectedItemCssClass);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
n.setAttribute('value', '');
|
||||
n.setAttribute('indexes', '');
|
||||
|
||||
} else {
|
||||
fltValue = (fltValue) ? fltValue : '';
|
||||
chkValue = Str.trim(
|
||||
fltValue+' '+chkValue+' '+tf.orOperator);
|
||||
chkIndex = fltIndexes + chkIndex + tf.separator;
|
||||
n.setAttribute('value', chkValue );
|
||||
n.setAttribute('indexes', chkIndex);
|
||||
//1st option unchecked
|
||||
if(Dom.id(tf.fltIds[colIndex]+'_0')){
|
||||
Dom.id(tf.fltIds[colIndex]+'_0').checked = false;
|
||||
let indexes = slcIndexes + chkIndex + tf.separator;
|
||||
let values =
|
||||
trim(slcValues + ' ' + chkValue + ' ' + tf.orOperator);
|
||||
|
||||
n.setAttribute('value', values);
|
||||
n.setAttribute('indexes', indexes);
|
||||
|
||||
//uncheck first option
|
||||
let chx0 = tag(items[0], 'input')[0];
|
||||
if (chx0) {
|
||||
chx0.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(Str.lower(li.nodeName) === itemTag){
|
||||
Dom.removeClass(
|
||||
n.childNodes[0], this.checkListSlcItemCssClass);
|
||||
Dom.addClass(li, this.checkListSlcItemCssClass);
|
||||
}
|
||||
removeClass(items[0], this.selectedItemCssClass);
|
||||
addClass(li, this.selectedItemCssClass);
|
||||
} else { //removes values and indexes
|
||||
if(chkValue!==''){
|
||||
var replaceValue = new RegExp(
|
||||
Str.rgxEsc(chkValue+' '+tf.orOperator));
|
||||
fltValue = fltValue.replace(replaceValue,'');
|
||||
n.setAttribute('value', Str.trim(fltValue));
|
||||
let replaceValue =
|
||||
new RegExp(rgxEsc(chkValue + ' ' + tf.orOperator));
|
||||
let values = slcValues.replace(replaceValue, '');
|
||||
let replaceIndex = new RegExp(rgxEsc(chkIndex + tf.separator));
|
||||
let indexes = slcIndexes.replace(replaceIndex, '');
|
||||
|
||||
var replaceIndex = new RegExp(
|
||||
Str.rgxEsc(chkIndex + tf.separator));
|
||||
fltIndexes = fltIndexes.replace(replaceIndex, '');
|
||||
n.setAttribute('indexes', fltIndexes);
|
||||
}
|
||||
if(Str.lower(li.nodeName)===itemTag){
|
||||
Dom.removeClass(li, this.checkListSlcItemCssClass);
|
||||
}
|
||||
n.setAttribute('value', trim(values));
|
||||
n.setAttribute('indexes', indexes);
|
||||
|
||||
removeClass(li, this.selectedItemCssClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select filter options programmatically
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Array} values Array of option values to select
|
||||
*/
|
||||
selectOptions(colIndex, values = []) {
|
||||
let tf = this.tf;
|
||||
let flt = tf.getFilterElement(colIndex);
|
||||
if (!flt || values.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lis = tag(flt, 'li');
|
||||
|
||||
flt.setAttribute('value', '');
|
||||
flt.setAttribute('indexes', '');
|
||||
|
||||
[].forEach.call(lis, (li) => {
|
||||
let chk = tag(li, 'input')[0];
|
||||
let chkVal = matchCase(chk.value, tf.caseSensitive);
|
||||
|
||||
if (chkVal !== '' && has(values, chkVal, tf.caseSensitive)) {
|
||||
chk.checked = true;
|
||||
} else {
|
||||
// Check non-empty-text or empty-text option
|
||||
if (values.indexOf(tf.nmOperator) !== -1 &&
|
||||
chkVal === matchCase(tf.nonEmptyText, tf.caseSensitive)) {
|
||||
chk.checked = true;
|
||||
}
|
||||
else if (values.indexOf(tf.emOperator) !== -1 &&
|
||||
chkVal === matchCase(tf.emptyText, tf.caseSensitive)) {
|
||||
chk.checked = true;
|
||||
} else {
|
||||
chk.checked = false;
|
||||
}
|
||||
}
|
||||
this.setItemOption(chk);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter values for a given column index
|
||||
* @param {Number} colIndex Column index
|
||||
* @returns {Array} values Collection of selected values
|
||||
*/
|
||||
getValues(colIndex) {
|
||||
let tf = this.tf;
|
||||
let flt = tf.getFilterElement(colIndex);
|
||||
if (!flt) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let fltAttr = flt.getAttribute('value');
|
||||
let values = isEmpty(fltAttr) ? '' : fltAttr;
|
||||
//removes last operator ||
|
||||
values = values.substr(0, values.length - 3);
|
||||
//turn || separated values into array
|
||||
values = values.split(' ' + tf.orOperator + ' ');
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy CheckList instance
|
||||
*/
|
||||
destroy() {
|
||||
this.emitter.off(
|
||||
['build-checklist-filter'],
|
||||
(tf, colIndex, isLinked) => this.build(colIndex, isLinked)
|
||||
);
|
||||
this.emitter.off(
|
||||
['select-checklist-options'],
|
||||
(tf, colIndex, values) => this.selectOptions(colIndex, values)
|
||||
);
|
||||
this.emitter.off(['rows-changed'], () => this.refreshAll());
|
||||
this.emitter.off(['after-filtering'], () => this.linkFilters());
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,89 +1,142 @@
|
|||
import Dom from '../dom';
|
||||
import Event from '../event';
|
||||
import {Feature} from '../feature';
|
||||
import {createElm, createText, elm, removeElm} from '../dom';
|
||||
import {addEvt} from '../event';
|
||||
import {defaultsStr} from '../settings';
|
||||
import {isNull} from '../types';
|
||||
import {RIGHT} from './toolbar';
|
||||
|
||||
export class ClearButton{
|
||||
/**
|
||||
* Clear button UI component
|
||||
*/
|
||||
export class ClearButton extends Feature {
|
||||
|
||||
/**
|
||||
* Clear button component
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of ClearButton
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
// Configuration object
|
||||
var f = tf.config();
|
||||
constructor(tf) {
|
||||
super(tf, ClearButton);
|
||||
|
||||
//id of container element
|
||||
this.btnResetTgtId = f.btn_reset_target_id || null;
|
||||
//reset button element
|
||||
this.btnResetEl = null;
|
||||
//defines reset text
|
||||
this.btnResetText = f.btn_reset_text || 'Reset';
|
||||
//defines reset button tooltip
|
||||
this.btnResetTooltip = f.btn_reset_tooltip || 'Clear filters';
|
||||
//defines reset button innerHtml
|
||||
this.btnResetHtml = f.btn_reset_html ||
|
||||
(!tf.enableIcons ? null :
|
||||
'<input type="button" value="" class="'+tf.btnResetCssClass+'" ' +
|
||||
'title="'+this.btnResetTooltip+'" />');
|
||||
//span containing reset button
|
||||
this.prfxResetSpan = 'resetspan_';
|
||||
let f = this.config.btn_reset || {};
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Container element ID
|
||||
* @type {String}
|
||||
*/
|
||||
this.targetId = defaultsStr(f.target_id, null);
|
||||
|
||||
/**
|
||||
* Text for the clear button
|
||||
* @type {String}
|
||||
*/
|
||||
this.text = defaultsStr(f.text, null);
|
||||
|
||||
/**
|
||||
* Css class for reset button
|
||||
* @type {String}
|
||||
*/
|
||||
this.cssClass = defaultsStr(f.css_class, 'reset');
|
||||
|
||||
/**
|
||||
* Tooltip text for the clear button
|
||||
* @type {String}
|
||||
*/
|
||||
this.tooltip = f.tooltip || 'Clear filters';
|
||||
|
||||
/**
|
||||
* Custom Html string for the clear button
|
||||
* @type {String}
|
||||
*/
|
||||
this.html = defaultsStr(f.html,
|
||||
(!tf.enableIcons || this.text ? null :
|
||||
'<input type="button" value="" class="' + this.cssClass +
|
||||
'" ' + 'title="' + this.tooltip + '" />'));
|
||||
|
||||
/**
|
||||
* Default position in toolbar ('left'|'center'|'right')
|
||||
* @type {String}
|
||||
*/
|
||||
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
|
||||
|
||||
/**
|
||||
* Clear button container element
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.container = null;
|
||||
|
||||
/**
|
||||
* Clear button element
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.element = null;
|
||||
}
|
||||
|
||||
onClick(){
|
||||
/**
|
||||
* Click event handler for clear button
|
||||
* @private
|
||||
*/
|
||||
onClick() {
|
||||
if (!this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
this.tf.clearFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build DOM elements
|
||||
* Initialize clear button component
|
||||
*/
|
||||
init(){
|
||||
var tf = this.tf;
|
||||
init() {
|
||||
let tf = this.tf;
|
||||
|
||||
if(!tf.hasGrid() && !tf.isFirstLoad && tf.btnResetEl){
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
var resetspan = Dom.create('span', ['id', this.prfxResetSpan+tf.id]);
|
||||
this.emitter.emit('initializing-feature', this, !isNull(this.targetId));
|
||||
|
||||
// reset button is added to defined element
|
||||
if(!this.btnResetTgtId){
|
||||
tf.setToolbar();
|
||||
}
|
||||
var targetEl = !this.btnResetTgtId ?
|
||||
tf.rDiv : Dom.id(this.btnResetTgtId);
|
||||
targetEl.appendChild(resetspan);
|
||||
let cont = createElm('span');
|
||||
|
||||
if(!this.btnResetHtml){
|
||||
var fltreset = Dom.create('a', ['href', 'javascript:void(0);']);
|
||||
fltreset.className = tf.btnResetCssClass;
|
||||
fltreset.appendChild(Dom.text(this.btnResetText));
|
||||
resetspan.appendChild(fltreset);
|
||||
// fltreset.onclick = this.Evt._Clear;
|
||||
Event.add(fltreset, 'click', () => { this.onClick(); });
|
||||
let targetEl = !this.targetId ?
|
||||
tf.feature('toolbar').container(this.toolbarPosition) :
|
||||
elm(this.targetId);
|
||||
targetEl.appendChild(cont);
|
||||
|
||||
if (!this.html) {
|
||||
let fltReset = createElm('a', ['href', 'javascript:void(0);']);
|
||||
fltReset.className = this.cssClass;
|
||||
fltReset.appendChild(createText(this.text));
|
||||
cont.appendChild(fltReset);
|
||||
addEvt(fltReset, 'click', () => this.onClick());
|
||||
} else {
|
||||
resetspan.innerHTML = this.btnResetHtml;
|
||||
var resetEl = resetspan.firstChild;
|
||||
// resetEl.onclick = this.Evt._Clear;
|
||||
Event.add(resetEl, 'click', () => { this.onClick(); });
|
||||
cont.innerHTML = this.html;
|
||||
let resetEl = cont.firstChild;
|
||||
addEvt(resetEl, 'click', () => this.onClick());
|
||||
}
|
||||
this.btnResetEl = resetspan.firstChild;
|
||||
this.element = cont.firstChild;
|
||||
this.container = cont;
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
|
||||
this.emitter.emit('feature-initialized', this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove clear button UI
|
||||
* Destroy ClearButton instance
|
||||
*/
|
||||
destroy(){
|
||||
var tf = this.tf;
|
||||
|
||||
if(!tf.hasGrid() || !this.btnResetEl){
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
var resetspan = Dom.id(tf.prfxResetSpan+tf.id);
|
||||
if(resetspan){
|
||||
resetspan.parentNode.removeChild(resetspan);
|
||||
}
|
||||
this.btnResetEl = null;
|
||||
removeElm(this.element);
|
||||
removeElm(this.container);
|
||||
this.element = null;
|
||||
this.container = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove as soon as feature name is fixed
|
||||
ClearButton.meta = {altName: 'btnReset'};
|
||||
|
|
157
src/modules/dateType.js
Normal file
|
@ -0,0 +1,157 @@
|
|||
import {Date as SugarDate} from 'sugar-date';
|
||||
import 'sugar-date/locales';
|
||||
import {Feature} from '../feature';
|
||||
import {isObj, isArray} from '../types';
|
||||
import {DATE} from '../const';
|
||||
import {root} from '../root';
|
||||
|
||||
/**
|
||||
* Wrapper for Sugar Date module providing datetime helpers and locales
|
||||
* @export
|
||||
* @class DateType
|
||||
*/
|
||||
export class DateType extends Feature {
|
||||
|
||||
/**
|
||||
* Creates an instance of DateType
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf) {
|
||||
super(tf, DateType);
|
||||
|
||||
/**
|
||||
* Global locale
|
||||
* @type {String}
|
||||
*/
|
||||
this.locale = tf.locale;
|
||||
|
||||
/**
|
||||
* Sugar Date instance
|
||||
* @type {Object}
|
||||
*/
|
||||
this.datetime = SugarDate;
|
||||
|
||||
this.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize DateType instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set global locale
|
||||
this.datetime.setLocale(this.locale);
|
||||
|
||||
// Add formats from column types configuration if any
|
||||
this.addConfigFormats(this.tf.colTypes);
|
||||
|
||||
this.emitter.on(
|
||||
['add-date-type-formats'],
|
||||
(tf, types) => this.addConfigFormats(types)
|
||||
);
|
||||
|
||||
// Broadcast date-type initialization
|
||||
this.emitter.emit('date-type-initialized', this.tf, this);
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string representation of a date for a specified locale and return
|
||||
* a date object
|
||||
* @param {String} dateStr String representation of a date
|
||||
* @param {String} localeCode Locale code (ie 'en-us')
|
||||
* @returns {Date}
|
||||
*/
|
||||
parse(dateStr, localeCode) {
|
||||
return this.datetime.create(dateStr, localeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check string representation of a date for a specified locale is valid
|
||||
* @param {any} dateStr String representation of a date
|
||||
* @param {any} localeCode Locale code (ie 'en-us')
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isValid(dateStr, localeCode) {
|
||||
return this.datetime.isValid(this.parse(dateStr, localeCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type object of a specified column as per configuration or
|
||||
* passed collection
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Array} types Collection of column types, optional
|
||||
* @returns {Object}
|
||||
*/
|
||||
getOptions(colIndex, types) {
|
||||
types = types || this.tf.colTypes;
|
||||
let colType = types[colIndex];
|
||||
return isObj(colType) ? colType : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the locale code for supplied column index as per configuration
|
||||
* or global setting
|
||||
* @param {Number} colIndex Column index
|
||||
* @returns {String} Locale code (ie: 'en-us')
|
||||
*/
|
||||
getLocale(colIndex) {
|
||||
return this.getOptions(colIndex).locale || this.locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add date time format(s) to a locale as specified by the passed
|
||||
* collection of column types, ie:
|
||||
* [
|
||||
* 'string',
|
||||
* 'number',
|
||||
* { type: 'date', locale: 'en', format: ['{dd}/{MM}/{yyyy}']}
|
||||
* ]
|
||||
*
|
||||
* @param {Array} [types=[]] Collection of column types
|
||||
*/
|
||||
addConfigFormats(types=[]) {
|
||||
types.forEach((type, idx) => {
|
||||
let options = this.getOptions(idx, types);
|
||||
if (options.type === DATE && options.hasOwnProperty('format')) {
|
||||
let locale = this.datetime.getLocale(
|
||||
options.locale || this.locale
|
||||
);
|
||||
let formats = isArray(options.format) ?
|
||||
options.format : [options.format];
|
||||
|
||||
// Sugar date module throws exceptions with locale.addFormat
|
||||
try {
|
||||
formats.forEach((format) => {
|
||||
locale.addFormat(format);
|
||||
});
|
||||
} catch (ex) {
|
||||
root.console.error(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove DateType instance
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: remove added formats
|
||||
|
||||
this.emitter.off(
|
||||
['add-date-type-formats'],
|
||||
(tf, types) => this.addConfigFormats(types)
|
||||
);
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
|
@ -1,231 +1,242 @@
|
|||
import Dom from '../dom';
|
||||
import Arr from '../array';
|
||||
import Str from '../string';
|
||||
import Sort from '../sort';
|
||||
import {BaseDropdown} from './baseDropdown';
|
||||
import {createElm, createOpt, elm} from '../dom';
|
||||
import {has} from '../array';
|
||||
import {matchCase} from '../string';
|
||||
import {addEvt, targetEvt} from '../event';
|
||||
import {SELECT, MULTIPLE, NONE} from '../const';
|
||||
import {defaultsStr, defaultsBool} from '../settings';
|
||||
|
||||
export class Dropdown{
|
||||
/**
|
||||
* Dropdown filter UI component
|
||||
* @export
|
||||
* @class Dropdown
|
||||
* @extends {BaseDropdown}
|
||||
*/
|
||||
export class Dropdown extends BaseDropdown {
|
||||
|
||||
/**
|
||||
* Dropdown UI component
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of Dropdown
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
constructor(tf) {
|
||||
super(tf, Dropdown);
|
||||
|
||||
// Configuration object
|
||||
var f = tf.config();
|
||||
let f = this.config;
|
||||
|
||||
this.enableSlcResetFilter = f.enable_slc_reset_filter===false ?
|
||||
false : true;
|
||||
//defines empty option text
|
||||
this.nonEmptyText = f.non_empty_text || '(Non empty)';
|
||||
//sets select filling method: 'innerHTML' or 'createElement'
|
||||
this.slcFillingMethod = f.slc_filling_method || 'createElement';
|
||||
//IE only, tooltip text appearing on select before it is populated
|
||||
this.activateSlcTooltip = f.activate_slc_tooltip ||
|
||||
'Click to activate';
|
||||
//tooltip text appearing on multiple select
|
||||
this.multipleSlcTooltip = f.multiple_slc_tooltip ||
|
||||
'Use Ctrl key for multiple selections';
|
||||
/**
|
||||
* Enable the reset filter option as first item
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableSlcResetFilter =
|
||||
defaultsBool(f.enable_slc_reset_filter, true);
|
||||
|
||||
this.isCustom = null;
|
||||
this.opts = null;
|
||||
this.optsTxt = null;
|
||||
this.slcInnerHtml = null;
|
||||
/**
|
||||
* Non empty option text
|
||||
* @type {String}
|
||||
*/
|
||||
this.nonEmptyText = defaultsStr(f.non_empty_text, '(Non empty)');
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Tooltip text appearing on multiple select
|
||||
* @type {String}
|
||||
*/
|
||||
this.multipleSlcTooltip = defaultsStr(f.multiple_slc_tooltip,
|
||||
'Use Ctrl/Cmd key for multiple selections');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Drop-down filter focus event handler
|
||||
* @param {Event} e DOM Event
|
||||
* @private
|
||||
*/
|
||||
onSlcFocus(e) {
|
||||
let elm = targetEvt(e);
|
||||
let tf = this.tf;
|
||||
// select is populated when element has focus
|
||||
if (tf.loadFltOnDemand && elm.getAttribute('filled') === '0') {
|
||||
let ct = elm.getAttribute('ct');
|
||||
this.build(ct);
|
||||
}
|
||||
this.emitter.emit('filter-focus', tf, elm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build drop-down filter UI asynchronously
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} isLinked Enable linked refresh behaviour
|
||||
* @param {Boolean} isExternal Render in external container
|
||||
* @param {String} extSlcId External container id
|
||||
* Drop-down filter change event handler
|
||||
* @private
|
||||
*/
|
||||
build(colIndex, isLinked, isExternal, extSlcId){
|
||||
var tf = this.tf;
|
||||
tf.EvtManager(
|
||||
tf.Evt.name.dropdown,
|
||||
{
|
||||
slcIndex: colIndex,
|
||||
slcRefreshed: isLinked,
|
||||
slcExternal: isExternal,
|
||||
slcId: extSlcId
|
||||
}
|
||||
onSlcChange() {
|
||||
if (this.tf.onSlcChange) {
|
||||
this.tf.filter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh all drop-down filters
|
||||
*/
|
||||
refreshAll() {
|
||||
let selectFlts = this.tf.getFiltersByType(SELECT, true);
|
||||
let multipleFlts = this.tf.getFiltersByType(MULTIPLE, true);
|
||||
let colIdxs = selectFlts.concat(multipleFlts);
|
||||
this.refreshFilters(colIdxs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize drop-down filter
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} isExternal External filter flag
|
||||
* @param {DOMElement} container Dom element containing the filter
|
||||
*/
|
||||
init(colIndex, isExternal, container) {
|
||||
let tf = this.tf;
|
||||
let col = tf.getFilterType(colIndex);
|
||||
let externalFltTgtId = isExternal ?
|
||||
tf.externalFltIds[colIndex] : null;
|
||||
|
||||
let slc = createElm(SELECT,
|
||||
['id', tf.buildFilterId(colIndex)],
|
||||
['ct', colIndex], ['filled', '0']
|
||||
);
|
||||
|
||||
if (col === MULTIPLE) {
|
||||
slc.multiple = MULTIPLE;
|
||||
slc.title = this.multipleSlcTooltip;
|
||||
}
|
||||
slc.className = col.toLowerCase() === SELECT ?
|
||||
tf.fltCssClass : tf.fltMultiCssClass;
|
||||
|
||||
//filter is appended in container element
|
||||
if (externalFltTgtId) {
|
||||
elm(externalFltTgtId).appendChild(slc);
|
||||
} else {
|
||||
container.appendChild(slc);
|
||||
}
|
||||
|
||||
tf.fltIds.push(slc.id);
|
||||
|
||||
if (!tf.loadFltOnDemand) {
|
||||
this.build(colIndex);
|
||||
} else {
|
||||
//1st option is created here since build isn't invoked
|
||||
let opt0 = createOpt(tf.getClearFilterText(colIndex), '');
|
||||
slc.appendChild(opt0);
|
||||
}
|
||||
|
||||
addEvt(slc, 'change', () => this.onSlcChange());
|
||||
addEvt(slc, 'focus', (e) => this.onSlcFocus(e));
|
||||
|
||||
this.emitter.on(
|
||||
['build-select-filter'],
|
||||
(tf, colIndex, isLinked, isExternal) =>
|
||||
this.build(colIndex, isLinked, isExternal)
|
||||
);
|
||||
this.emitter.on(
|
||||
['select-options'],
|
||||
(tf, colIndex, values) => this.selectOptions(colIndex, values)
|
||||
);
|
||||
this.emitter.on(['rows-changed'], () => this.refreshAll());
|
||||
|
||||
this.emitter.on(['after-filtering'], () => this.linkFilters());
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build drop-down filter UI
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} isLinked Enable linked refresh behaviour
|
||||
* @param {Boolean} isExternal Render in external container
|
||||
* @param {String} extSlcId External container id
|
||||
* @param {Boolean} isLinked Enable linked filters behaviour
|
||||
*/
|
||||
_build(colIndex, isLinked=false, isExternal=false, extSlcId=null){
|
||||
var tf = this.tf;
|
||||
colIndex = parseInt(colIndex, 10);
|
||||
build(colIndex, isLinked = false) {
|
||||
let tf = this.tf;
|
||||
colIndex = Number(colIndex);
|
||||
|
||||
this.emitter.emit('before-populating-filter', tf, colIndex);
|
||||
|
||||
/** @inherited */
|
||||
this.opts = [];
|
||||
/** @inherited */
|
||||
this.optsTxt = [];
|
||||
this.slcInnerHtml = '';
|
||||
|
||||
var slcId = tf.fltIds[colIndex];
|
||||
if((!Dom.id(slcId) && !isExternal) ||
|
||||
(!Dom.id(extSlcId) && isExternal)){
|
||||
return;
|
||||
}
|
||||
var slc = !isExternal ? Dom.id(slcId) : Dom.id(extSlcId),
|
||||
rows = tf.tbl.rows,
|
||||
matchCase = tf.matchCase;
|
||||
let slc = tf.getFilterElement(colIndex);
|
||||
|
||||
//custom select test
|
||||
/** @inherited */
|
||||
this.isCustom = tf.isCustomOptions(colIndex);
|
||||
|
||||
//custom selects text
|
||||
var activeFlt;
|
||||
if(isLinked && tf.activeFilterId){
|
||||
activeFlt = tf.activeFilterId.split('_')[0];
|
||||
activeFlt = activeFlt.split(tf.prfxFlt)[1];
|
||||
}
|
||||
|
||||
/*** remember grid values ***/
|
||||
var fltsValues = [], fltArr = [];
|
||||
if(tf.rememberGridValues){
|
||||
fltsValues =
|
||||
tf.feature('store').getFilterValues(tf.fltsValuesCookie);
|
||||
if(fltsValues && !Str.isEmpty(fltsValues.toString())){
|
||||
if(this.isCustom){
|
||||
fltArr.push(fltsValues[colIndex]);
|
||||
} else {
|
||||
fltArr = fltsValues[colIndex].split(' '+tf.orOperator+' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var excludedOpts = null,
|
||||
filteredDataCol = null;
|
||||
if(isLinked && tf.disableExcludedOptions){
|
||||
excludedOpts = [];
|
||||
filteredDataCol = [];
|
||||
}
|
||||
|
||||
for(var k=tf.refRow; k<tf.nbRows; k++){
|
||||
// always visible rows don't need to appear on selects as always
|
||||
// valid
|
||||
if(tf.hasVisibleRows && tf.visibleRows.indexOf(k) !== -1){
|
||||
continue;
|
||||
}
|
||||
|
||||
var cell = rows[k].cells,
|
||||
nchilds = cell.length;
|
||||
|
||||
// checks if row has exact cell #
|
||||
if(nchilds !== tf.nbCells || this.isCustom){
|
||||
continue;
|
||||
}
|
||||
|
||||
// this loop retrieves cell data
|
||||
for(var j=0; j<nchilds; j++){
|
||||
// WTF: cyclomatic complexity hell
|
||||
if((colIndex===j &&
|
||||
(!isLinked ||
|
||||
(isLinked && tf.disableExcludedOptions))) ||
|
||||
(colIndex==j && isLinked &&
|
||||
((rows[k].style.display === '' && !tf.paging) ||
|
||||
(tf.paging && (!tf.validRowsIndex ||
|
||||
(tf.validRowsIndex &&
|
||||
Arr.has(tf.validRowsIndex, k))) &&
|
||||
((activeFlt===undefined || activeFlt==colIndex) ||
|
||||
(activeFlt!=colIndex &&
|
||||
Arr.has(tf.validRowsIndex, k) ))) ))){
|
||||
var cell_data = tf.getCellData(j, cell[j]),
|
||||
//Vary Peter's patch
|
||||
cell_string = Str.matchCase(cell_data, matchCase);
|
||||
|
||||
// checks if celldata is already in array
|
||||
if(!Arr.has(this.opts, cell_string, matchCase)){
|
||||
this.opts.push(cell_data);
|
||||
}
|
||||
|
||||
if(isLinked && tf.disableExcludedOptions){
|
||||
var filteredCol = filteredDataCol[j];
|
||||
if(!filteredCol){
|
||||
filteredCol = tf.getFilteredDataCol(j);
|
||||
}
|
||||
if(!Arr.has(filteredCol, cell_string, matchCase) &&
|
||||
!Arr.has(
|
||||
excludedOpts, cell_string, matchCase) &&
|
||||
!this.isFirstLoad){
|
||||
excludedOpts.push(cell_data);
|
||||
}
|
||||
}
|
||||
}//if colIndex==j
|
||||
}//for j
|
||||
}//for k
|
||||
|
||||
//Retrieves custom values
|
||||
if(this.isCustom){
|
||||
var customValues = tf.getCustomOptions(colIndex);
|
||||
if (this.isCustom) {
|
||||
let customValues = tf.getCustomOptions(colIndex);
|
||||
this.opts = customValues[0];
|
||||
this.optsTxt = customValues[1];
|
||||
}
|
||||
|
||||
if(tf.sortSlc && !this.isCustom){
|
||||
if (!matchCase){
|
||||
this.opts.sort(Sort.ignoreCase);
|
||||
if(excludedOpts){
|
||||
excludedOpts.sort(Sort.ignoreCase);
|
||||
}
|
||||
} else {
|
||||
this.opts.sort();
|
||||
if(excludedOpts){ excludedOpts.sort(); }
|
||||
}
|
||||
//custom selects text
|
||||
let activeIdx;
|
||||
let activeFilterId = tf.getActiveFilterId();
|
||||
if (isLinked && activeFilterId) {
|
||||
activeIdx = tf.getColumnIndexFromFilterId(activeFilterId);
|
||||
}
|
||||
|
||||
//asc sort
|
||||
if(tf.sortNumAsc && Arr.has(tf.sortNumAsc, colIndex)){
|
||||
try{
|
||||
this.opts.sort( numSortAsc );
|
||||
if(excludedOpts){
|
||||
excludedOpts.sort(numSortAsc);
|
||||
}
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort(numSortAsc);
|
||||
}
|
||||
} catch(e) {
|
||||
this.opts.sort();
|
||||
if(excludedOpts){
|
||||
excludedOpts.sort();
|
||||
}
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort();
|
||||
}
|
||||
}//in case there are alphanumeric values
|
||||
let excludedOpts = null,
|
||||
filteredDataCol = null;
|
||||
if (isLinked && tf.disableExcludedOptions) {
|
||||
excludedOpts = [];
|
||||
filteredDataCol = [];
|
||||
}
|
||||
//desc sort
|
||||
if(tf.sortNumDesc && Arr.has(tf.sortNumDesc, colIndex)){
|
||||
try{
|
||||
this.opts.sort(numSortDesc);
|
||||
if(excludedOpts){
|
||||
excludedOpts.sort(numSortDesc);
|
||||
|
||||
let eachRow = tf.eachRow();
|
||||
eachRow(
|
||||
(row) => {
|
||||
let cellValue = tf.getCellValue(row.cells[colIndex]);
|
||||
//Vary Peter's patch
|
||||
let cellString = matchCase(cellValue, tf.caseSensitive);
|
||||
|
||||
// checks if celldata is already in array
|
||||
if (!has(this.opts, cellString, tf.caseSensitive)) {
|
||||
this.opts.push(cellValue);
|
||||
}
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort(numSortDesc);
|
||||
|
||||
if (isLinked && tf.disableExcludedOptions) {
|
||||
let filteredCol = filteredDataCol[colIndex];
|
||||
if (!filteredCol) {
|
||||
filteredCol = tf.getVisibleColumnValues(colIndex);
|
||||
}
|
||||
if (!has(filteredCol, cellString, tf.caseSensitive) &&
|
||||
!has(excludedOpts, cellString, tf.caseSensitive)) {
|
||||
excludedOpts.push(cellValue);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
this.opts.sort();
|
||||
if(excludedOpts){
|
||||
excludedOpts.sort();
|
||||
},
|
||||
// continue conditions function
|
||||
(row, k) => {
|
||||
// excluded rows don't need to appear on selects as always valid
|
||||
if (tf.excludeRows.indexOf(k) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if(this.isCustom){
|
||||
this.optsTxt.sort();
|
||||
|
||||
// checks if row has expected number of cells
|
||||
if (row.cells.length !== tf.nbCells || this.isCustom) {
|
||||
return true;
|
||||
}
|
||||
}//in case there are alphanumeric values
|
||||
|
||||
if (isLinked && !this.isValidLinkedValue(k, activeIdx)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//sort options
|
||||
this.opts = this.sortOptions(colIndex, this.opts);
|
||||
if (excludedOpts) {
|
||||
excludedOpts = this.sortOptions(colIndex, excludedOpts);
|
||||
}
|
||||
|
||||
//populates drop-down
|
||||
this.addOptions(
|
||||
colIndex, slc, isLinked, excludedOpts, fltsValues, fltArr);
|
||||
this.addOptions(colIndex, slc, isLinked, excludedOpts);
|
||||
|
||||
this.emitter.emit('after-populating-filter', tf, colIndex, slc);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,77 +245,41 @@ export class Dropdown{
|
|||
* @param {Object} slc Select Dom element
|
||||
* @param {Boolean} isLinked Enable linked refresh behaviour
|
||||
* @param {Array} excludedOpts Array of excluded options
|
||||
* @param {Array} fltsValues Collection of persisted filter values
|
||||
* @param {Array} fltArr Collection of persisted filter values
|
||||
*/
|
||||
addOptions(colIndex, slc, isLinked, excludedOpts, fltsValues, fltArr){
|
||||
var tf = this.tf,
|
||||
fillMethod = Str.lower(this.slcFillingMethod),
|
||||
addOptions(colIndex, slc, isLinked, excludedOpts) {
|
||||
let tf = this.tf,
|
||||
slcValue = slc.value;
|
||||
|
||||
slc.innerHTML = '';
|
||||
slc = this.addFirstOption(slc);
|
||||
|
||||
for(var y=0; y<this.opts.length; y++){
|
||||
if(this.opts[y]===''){
|
||||
for (let y = 0; y < this.opts.length; y++) {
|
||||
if (this.opts[y] === '') {
|
||||
continue;
|
||||
}
|
||||
var val = this.opts[y]; //option value
|
||||
var lbl = this.isCustom ? this.optsTxt[y] : val; //option text
|
||||
var isDisabled = false;
|
||||
if(isLinked && tf.disableExcludedOptions &&
|
||||
Arr.has(
|
||||
excludedOpts,
|
||||
Str.matchCase(val, tf.matchCase),
|
||||
tf.matchCase
|
||||
)){
|
||||
let val = this.opts[y]; //option value
|
||||
let lbl = this.isCustom ? this.optsTxt[y] : val; //option text
|
||||
let isDisabled = false;
|
||||
if (isLinked && tf.disableExcludedOptions &&
|
||||
has(excludedOpts, matchCase(val, tf.caseSensitive),
|
||||
tf.caseSensitive)) {
|
||||
isDisabled = true;
|
||||
}
|
||||
|
||||
if(fillMethod === 'innerhtml'){
|
||||
var slcAttr = '';
|
||||
if(tf.loadFltOnDemand && slcValue===this.opts[y]){
|
||||
slcAttr = 'selected="selected"';
|
||||
}
|
||||
this.slcInnerHtml += '<option value="'+val+'" ' + slcAttr +
|
||||
(isDisabled ? 'disabled="disabled"' : '')+ '>' +
|
||||
lbl+'</option>';
|
||||
let opt;
|
||||
//fill select on demand
|
||||
if (tf.loadFltOnDemand && slcValue === this.opts[y] &&
|
||||
tf.getFilterType(colIndex) === SELECT) {
|
||||
opt = createOpt(lbl, val, true);
|
||||
} else {
|
||||
var opt;
|
||||
//fill select on demand
|
||||
if(tf.loadFltOnDemand && slcValue===this.opts[y] &&
|
||||
tf.getFilterType(colIndex) === tf.fltTypeSlc){
|
||||
opt = Dom.createOpt(lbl, val, true);
|
||||
} else {
|
||||
if(tf.getFilterType(colIndex) !== tf.fltTypeMulti){
|
||||
opt = Dom.createOpt(
|
||||
lbl,
|
||||
val,
|
||||
(fltsValues[colIndex]!==' ' &&
|
||||
val===fltsValues[colIndex]) ? true : false
|
||||
);
|
||||
} else {
|
||||
opt = Dom.createOpt(
|
||||
lbl,
|
||||
val,
|
||||
(Arr.has(fltArr,
|
||||
Str.matchCase(this.opts[y], tf.matchCase),
|
||||
tf.matchCase) ||
|
||||
fltArr.toString().indexOf(val)!== -1) ?
|
||||
true : false
|
||||
);
|
||||
}
|
||||
}
|
||||
if(isDisabled){
|
||||
opt.disabled = true;
|
||||
}
|
||||
slc.appendChild(opt);
|
||||
opt = createOpt(lbl, val, false);
|
||||
}
|
||||
if (isDisabled) {
|
||||
opt.disabled = true;
|
||||
}
|
||||
slc.appendChild(opt);
|
||||
}// for y
|
||||
|
||||
if(fillMethod === 'innerhtml'){
|
||||
slc.innerHTML += this.slcInnerHtml;
|
||||
}
|
||||
slc.setAttribute('filled', '1');
|
||||
}
|
||||
|
||||
|
@ -312,31 +287,90 @@ export class Dropdown{
|
|||
* Add drop-down header option
|
||||
* @param {Object} slc Select DOM element
|
||||
*/
|
||||
addFirstOption(slc){
|
||||
var tf = this.tf,
|
||||
fillMethod = Str.lower(this.slcFillingMethod);
|
||||
|
||||
if(fillMethod === 'innerhtml'){
|
||||
this.slcInnerHtml += '<option value="">'+ tf.displayAllText +
|
||||
'</option>';
|
||||
addFirstOption(slc) {
|
||||
let tf = this.tf;
|
||||
let colIdx = tf.getColumnIndexFromFilterId(slc.id);
|
||||
let opt0 = createOpt((!this.enableSlcResetFilter ?
|
||||
'' : tf.getClearFilterText(colIdx)), '');
|
||||
if (!this.enableSlcResetFilter) {
|
||||
opt0.style.display = NONE;
|
||||
}
|
||||
else {
|
||||
var opt0 = Dom.createOpt(
|
||||
(!this.enableSlcResetFilter ? '' : tf.displayAllText),'');
|
||||
if(!this.enableSlcResetFilter){
|
||||
opt0.style.display = 'none';
|
||||
}
|
||||
slc.appendChild(opt0);
|
||||
if(tf.enableEmptyOption){
|
||||
var opt1 = Dom.createOpt(tf.emptyText, tf.emOperator);
|
||||
slc.appendChild(opt1);
|
||||
}
|
||||
if(tf.enableNonEmptyOption){
|
||||
var opt2 = Dom.createOpt(tf.nonEmptyText, tf.nmOperator);
|
||||
slc.appendChild(opt2);
|
||||
}
|
||||
slc.appendChild(opt0);
|
||||
if (tf.enableEmptyOption) {
|
||||
let opt1 = createOpt(tf.emptyText, tf.emOperator);
|
||||
slc.appendChild(opt1);
|
||||
}
|
||||
if (tf.enableNonEmptyOption) {
|
||||
let opt2 = createOpt(tf.nonEmptyText, tf.nmOperator);
|
||||
slc.appendChild(opt2);
|
||||
}
|
||||
return slc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select filter options programmatically
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Array} values Array of option values to select
|
||||
*/
|
||||
selectOptions(colIndex, values = []) {
|
||||
let tf = this.tf;
|
||||
if (values.length === 0) {
|
||||
return;
|
||||
}
|
||||
let slc = tf.getFilterElement(colIndex);
|
||||
[].forEach.call(slc.options, (option) => {
|
||||
// Empty value means clear all selections and first option is the
|
||||
// clear all option
|
||||
if (values[0] === '' || option.value === '') {
|
||||
option.selected = false;
|
||||
}
|
||||
|
||||
if (option.value !== '' && has(values, option.value, true)) {
|
||||
option.selected = true;
|
||||
}//if
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter values for a given column index
|
||||
* @param {Number} colIndex Column index
|
||||
* @returns {Array} values Array of selected values
|
||||
*/
|
||||
getValues(colIndex) {
|
||||
let tf = this.tf;
|
||||
let slc = tf.getFilterElement(colIndex);
|
||||
let values = [];
|
||||
|
||||
// IE >= 9 does not support the selectedOptions property :(
|
||||
if (slc.selectedOptions) {
|
||||
[].forEach.call(slc.selectedOptions,
|
||||
option => values.push(option.value));
|
||||
} else {
|
||||
[].forEach.call(slc.options, (option) => {
|
||||
if (option.selected) {
|
||||
values.push(option.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy Dropdown instance
|
||||
*/
|
||||
destroy() {
|
||||
this.emitter.off(
|
||||
['build-select-filter'],
|
||||
(colIndex, isLinked, isExternal) =>
|
||||
this.build(colIndex, isLinked, isExternal)
|
||||
);
|
||||
this.emitter.off(
|
||||
['select-options'],
|
||||
(tf, colIndex, values) => this.selectOptions(colIndex, values)
|
||||
);
|
||||
this.emitter.off(['rows-changed'], () => this.refreshAll());
|
||||
this.emitter.off(['after-filtering'], () => this.linkFilters());
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,192 +1,224 @@
|
|||
import Dom from '../dom';
|
||||
import Types from '../types';
|
||||
import Event from '../event';
|
||||
import {Feature} from '../feature';
|
||||
import {createElm, removeElm, elm, tag} from '../dom';
|
||||
import {addEvt, targetEvt} from '../event';
|
||||
import {contains} from '../string';
|
||||
import {NONE} from '../const';
|
||||
import {
|
||||
defaultsBool, defaultsStr, defaultsNb, defaultsArr
|
||||
} from '../settings';
|
||||
|
||||
export class GridLayout{
|
||||
/**
|
||||
* Grid layout, table with fixed headers
|
||||
*/
|
||||
export class GridLayout extends Feature {
|
||||
|
||||
/**
|
||||
* Grid layout, table with fixed headers
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of GridLayout
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf) {
|
||||
var f = tf.config();
|
||||
super(tf, GridLayout);
|
||||
|
||||
//defines grid width
|
||||
this.gridWidth = f.grid_width || null;
|
||||
//defines grid height
|
||||
this.gridHeight = f.grid_height || null;
|
||||
//defines css class for main container
|
||||
this.gridMainContCssClass = f.grid_cont_css_class || 'grd_Cont';
|
||||
//defines css class for div containing table
|
||||
this.gridContCssClass = f.grid_tbl_cont_css_class || 'grd_tblCont';
|
||||
//defines css class for div containing headers' table
|
||||
this.gridHeadContCssClass = f.grid_tblHead_cont_css_class ||
|
||||
'grd_headTblCont';
|
||||
//defines css class for div containing rows counter, paging etc.
|
||||
this.gridInfDivCssClass = f.grid_inf_grid_css_class || 'grd_inf';
|
||||
//defines which row contains column headers
|
||||
this.gridHeadRowIndex = f.grid_headers_row_index || 0;
|
||||
//array of headers row indexes to be placed in header table
|
||||
this.gridHeadRows = f.grid_headers_rows || [0];
|
||||
//generate filters in table headers
|
||||
this.gridEnableFilters = f.grid_enable_default_filters!==undefined ?
|
||||
f.grid_enable_default_filters : true;
|
||||
//default col width
|
||||
this.gridDefaultColWidth = f.grid_default_col_width || '100px';
|
||||
//enables/disables columns resizer
|
||||
// this.gridEnableColResizer = f.grid_enable_cols_resizer!==undefined ?
|
||||
// f.grid_enable_cols_resizer : false;
|
||||
// //defines col resizer script path
|
||||
// this.gridColResizerPath = f.grid_cont_col_resizer_path ||
|
||||
// this.basePath+'TFExt_ColsResizer/TFExt_ColsResizer.js';
|
||||
let f = this.config.grid_layout || {};
|
||||
|
||||
this.gridColElms = [];
|
||||
/**
|
||||
* Grid-layout container width as CSS string
|
||||
* @type {String}
|
||||
*/
|
||||
this.width = defaultsStr(f.width, null);
|
||||
|
||||
//div containing grid elements if grid_layout true
|
||||
this.prfxMainTblCont = 'gridCont_';
|
||||
//div containing table if grid_layout true
|
||||
this.prfxTblCont = 'tblCont_';
|
||||
//div containing headers table if grid_layout true
|
||||
this.prfxHeadTblCont = 'tblHeadCont_';
|
||||
//headers' table if grid_layout true
|
||||
this.prfxHeadTbl = 'tblHead_';
|
||||
//id of td containing the filter if grid_layout true
|
||||
/**
|
||||
* Grid-layout container height as CSS string
|
||||
* @type {String}
|
||||
*/
|
||||
this.height = defaultsStr(f.height, null);
|
||||
|
||||
/**
|
||||
* Css class for main container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.mainContCssClass = defaultsStr(f.cont_css_class, 'grd_Cont');
|
||||
|
||||
/**
|
||||
* Css class for body table container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.contCssClass = defaultsStr(f.tbl_cont_css_class, 'grd_tblCont');
|
||||
|
||||
/**
|
||||
* Css class for headers table container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.headContCssClass = defaultsStr(f.tbl_head_css_class,
|
||||
'grd_headTblCont');
|
||||
|
||||
/**
|
||||
* Css class for toolbar container element (rows counter, paging etc.)
|
||||
* @type {String}
|
||||
*/
|
||||
this.infDivCssClass = defaultsStr(f.inf_grid_css_class, 'grd_inf');
|
||||
|
||||
/**
|
||||
* Index of the headers row, default: 0
|
||||
* @type {Number}
|
||||
*/
|
||||
this.headRowIndex = defaultsNb(f.headers_row_index, 0);
|
||||
|
||||
/**
|
||||
* Collection of the header row indexes to be moved into headers table
|
||||
* @type {Array}
|
||||
*/
|
||||
this.headRows = defaultsArr(f.headers_rows, [0]);
|
||||
|
||||
/**
|
||||
* Enable or disable column filters generation, default: true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.filters = defaultsBool(f.filters, true);
|
||||
|
||||
/**
|
||||
* Enable or disable column headers, default: false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.noHeaders = Boolean(f.no_headers);
|
||||
|
||||
/**
|
||||
* Grid-layout default column widht as CSS string
|
||||
* @type {String}
|
||||
*/
|
||||
this.defaultColWidth = defaultsStr(f.default_col_width, '100px');
|
||||
|
||||
/**
|
||||
* List of column elements
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.colElms = [];
|
||||
|
||||
/**
|
||||
* Prefix for grid-layout filter's cell ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.prfxGridFltTd = '_td_';
|
||||
//id of th containing column header if grid_layout true
|
||||
|
||||
/**
|
||||
* Prefix for grid-layout header's cell ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.prfxGridTh = 'tblHeadTh_';
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Mark-up of original HTML table
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.sourceTblHtml = tf.dom().outerHTML;
|
||||
|
||||
/**
|
||||
* Indicates if working table has column elements
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.tblHasColTag = tag(tf.dom(), 'col').length > 0 ? true : false;
|
||||
|
||||
/**
|
||||
* Main container element
|
||||
* @private
|
||||
*/
|
||||
this.tblMainCont = null;
|
||||
|
||||
/**
|
||||
* Table container element
|
||||
* @private
|
||||
*/
|
||||
this.tblCont = null;
|
||||
|
||||
/**
|
||||
* Headers' table container element
|
||||
* @private
|
||||
*/
|
||||
this.headTblCont = null;
|
||||
|
||||
/**
|
||||
* Headers' table element
|
||||
* @private
|
||||
*/
|
||||
this.headTbl = null;
|
||||
|
||||
// filters flag at TF level
|
||||
tf.fltGrid = this.filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a grid with fixed headers
|
||||
* TODO: reduce size of init by extracting single purposed methods
|
||||
*/
|
||||
init(){
|
||||
var tf = this.tf;
|
||||
var f = tf.config();
|
||||
var tbl = tf.tbl;
|
||||
init() {
|
||||
let tf = this.tf;
|
||||
let tbl = tf.dom();
|
||||
|
||||
if(!tf.gridLayout){
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
tf.isExternalFlt = true;
|
||||
// Override relevant TableFilter properties
|
||||
this.setOverrides();
|
||||
|
||||
// default width of 100px if column widths not set
|
||||
if(!tf.hasColWidths){
|
||||
tf.colWidths = [];
|
||||
for(var k=0; k<tf.nbCells; k++){
|
||||
var colW,
|
||||
cell = tbl.rows[this.gridHeadRowIndex].cells[k];
|
||||
if(cell.width !== ''){
|
||||
colW = cell.width;
|
||||
} else if(cell.style.width !== ''){
|
||||
colW = parseInt(cell.style.width, 10);
|
||||
} else {
|
||||
colW = this.gridDefaultColWidth;
|
||||
}
|
||||
tf.colWidths[k] = colW;
|
||||
}
|
||||
tf.hasColWidths = true;
|
||||
}
|
||||
tf.setColWidths(this.gridHeadRowIndex);
|
||||
|
||||
var tblW;//initial table width
|
||||
if(tbl.width !== ''){
|
||||
tblW = tbl.width;
|
||||
}
|
||||
else if(tbl.style.width !== ''){
|
||||
tblW = parseInt(tbl.style.width, 10);
|
||||
} else {
|
||||
tblW = tbl.clientWidth;
|
||||
}
|
||||
// Assign default column widths
|
||||
this.setDefaultColWidths();
|
||||
|
||||
//Main container: it will contain all the elements
|
||||
this.tblMainCont = Dom.create('div',
|
||||
['id', this.prfxMainTblCont + tf.id]);
|
||||
this.tblMainCont.className = this.gridMainContCssClass;
|
||||
if(this.gridWidth){
|
||||
this.tblMainCont.style.width = this.gridWidth;
|
||||
this.tblMainCont = this.createContainer(
|
||||
'div', this.mainContCssClass);
|
||||
if (this.width) {
|
||||
this.tblMainCont.style.width = this.width;
|
||||
}
|
||||
tbl.parentNode.insertBefore(this.tblMainCont, tbl);
|
||||
|
||||
//Table container: div wrapping content table
|
||||
this.tblCont = Dom.create('div',['id', this.prfxTblCont + tf.id]);
|
||||
this.tblCont.className = this.gridContCssClass;
|
||||
if(this.gridWidth){
|
||||
if(this.gridWidth.indexOf('%') != -1){
|
||||
console.log(this.gridWidth);
|
||||
this.tblCont.style.width = '100%';
|
||||
} else {
|
||||
this.tblCont.style.width = this.gridWidth;
|
||||
}
|
||||
}
|
||||
if(this.gridHeight){
|
||||
this.tblCont.style.height = this.gridHeight;
|
||||
this.tblCont = this.createContainer('div', this.contCssClass);
|
||||
this.setConfigWidth(this.tblCont);
|
||||
if (this.height) {
|
||||
this.tblCont.style.height = this.height;
|
||||
}
|
||||
tbl.parentNode.insertBefore(this.tblCont, tbl);
|
||||
var t = tbl.parentNode.removeChild(tbl);
|
||||
let t = removeElm(tbl);
|
||||
this.tblCont.appendChild(t);
|
||||
|
||||
//In case table width is expressed in %
|
||||
if(tbl.style.width === ''){
|
||||
tbl.style.width = (tf._containsStr('%', tblW) ?
|
||||
if (tbl.style.width === '') {
|
||||
let tblW = this.initialTableWidth();
|
||||
tbl.style.width = (contains('%', tblW) ?
|
||||
tbl.clientWidth : tblW) + 'px';
|
||||
}
|
||||
|
||||
var d = this.tblCont.parentNode.removeChild(this.tblCont);
|
||||
let d = removeElm(this.tblCont);
|
||||
this.tblMainCont.appendChild(d);
|
||||
|
||||
//Headers table container: div wrapping headers table
|
||||
this.headTblCont = Dom.create(
|
||||
'div',['id', this.prfxHeadTblCont + tf.id]);
|
||||
this.headTblCont.className = this.gridHeadContCssClass;
|
||||
if(this.gridWidth){
|
||||
if(this.gridWidth.indexOf('%') != -1){
|
||||
console.log(this.gridWidth);
|
||||
this.headTblCont.style.width = '100%';
|
||||
} else {
|
||||
this.headTblCont.style.width = this.gridWidth;
|
||||
}
|
||||
}
|
||||
this.headTblCont = this.createContainer(
|
||||
'div', this.headContCssClass);
|
||||
|
||||
//Headers table
|
||||
this.headTbl = Dom.create('table', ['id', this.prfxHeadTbl + tf.id]);
|
||||
var tH = Dom.create('tHead'); //IE<7 needs it
|
||||
this.headTbl = createElm('table');
|
||||
let tH = createElm('tHead');
|
||||
|
||||
//1st row should be headers row, ids are added if not set
|
||||
//Those ids are used by the sort feature
|
||||
var hRow = tbl.rows[this.gridHeadRowIndex];
|
||||
var sortTriggers = [];
|
||||
for(var n=0; n<tf.nbCells; n++){
|
||||
var c = hRow.cells[n];
|
||||
var thId = c.getAttribute('id');
|
||||
if(!thId || thId===''){
|
||||
thId = this.prfxGridTh+n+'_'+tf.id;
|
||||
c.setAttribute('id', thId);
|
||||
}
|
||||
sortTriggers.push(thId);
|
||||
}
|
||||
let hRow = tbl.rows[this.headRowIndex];
|
||||
let sortTriggers = this.getSortTriggerIds(hRow);
|
||||
|
||||
//Filters row is created
|
||||
var filtersRow = Dom.create('tr');
|
||||
if(this.gridEnableFilters && tf.fltGrid){
|
||||
tf.externalFltTgtIds = [];
|
||||
for(var j=0; j<tf.nbCells; j++){
|
||||
var fltTdId = tf.prfxFlt+j+ this.prfxGridFltTd +tf.id;
|
||||
var cl = Dom.create(tf.fltCellTag, ['id', fltTdId]);
|
||||
filtersRow.appendChild(cl);
|
||||
tf.externalFltTgtIds[j] = fltTdId;
|
||||
}
|
||||
}
|
||||
let filtersRow = this.createFiltersRow();
|
||||
|
||||
//Headers row are moved from content table to headers table
|
||||
for(var i=0; i<this.gridHeadRows.length; i++){
|
||||
var headRow = tbl.rows[this.gridHeadRows[0]];
|
||||
tH.appendChild(headRow);
|
||||
}
|
||||
this.setHeadersRow(tH);
|
||||
|
||||
this.headTbl.appendChild(tH);
|
||||
if(tf.filtersRowIndex === 0){
|
||||
tH.insertBefore(filtersRow,hRow);
|
||||
if (tf.filtersRowIndex === 0) {
|
||||
tH.insertBefore(filtersRow, hRow);
|
||||
} else {
|
||||
tH.appendChild(filtersRow);
|
||||
}
|
||||
|
@ -195,40 +227,34 @@ export class GridLayout{
|
|||
this.tblCont.parentNode.insertBefore(this.headTblCont, this.tblCont);
|
||||
|
||||
//THead needs to be removed in content table for sort feature
|
||||
var thead = Dom.tag(tbl, 'thead');
|
||||
if(thead.length>0){
|
||||
let thead = tag(tbl, 'thead');
|
||||
if (thead.length > 0) {
|
||||
tbl.removeChild(thead[0]);
|
||||
}
|
||||
|
||||
//Headers table style
|
||||
// ensure table layout is always set even if already set in css
|
||||
// definitions, potentially with custom css class this could be lost
|
||||
this.headTbl.style.tableLayout = 'fixed';
|
||||
tbl.style.tableLayout = 'fixed';
|
||||
this.headTbl.cellPadding = tbl.cellPadding;
|
||||
this.headTbl.cellSpacing = tbl.cellSpacing;
|
||||
// this.headTbl.style.width = tbl.style.width;
|
||||
|
||||
//content table without headers needs col widths to be reset
|
||||
tf.setColWidths(0, this.headTbl);
|
||||
tf.setColWidths(this.headTbl);
|
||||
|
||||
//Headers container width
|
||||
// this.headTblCont.style.width = this.tblCont.clientWidth+'px';
|
||||
|
||||
tbl.style.width = '';
|
||||
//
|
||||
this.headTbl.style.width = tbl.clientWidth + 'px';
|
||||
this.headTbl.style.width = tbl.style.width;
|
||||
//
|
||||
|
||||
//scroll synchronisation
|
||||
Event.add(this.tblCont, 'scroll', (evt)=> {
|
||||
var elm = Event.target(evt);
|
||||
var scrollLeft = elm.scrollLeft;
|
||||
addEvt(this.tblCont, 'scroll', (evt) => {
|
||||
let elm = targetEvt(evt);
|
||||
let scrollLeft = elm.scrollLeft;
|
||||
this.headTblCont.scrollLeft = scrollLeft;
|
||||
//New pointerX calc taking into account scrollLeft
|
||||
// if(!o.isPointerXOverwritten){
|
||||
// try{
|
||||
// o.Evt.pointerX = function(evt){
|
||||
// var e = evt || global.event;
|
||||
// var bdScrollLeft = tf_StandardBody().scrollLeft +
|
||||
// let e = evt || global.event;
|
||||
// let bdScrollLeft = tf_StandardBody().scrollLeft +
|
||||
// scrollLeft;
|
||||
// return (e.pageX + scrollLeft) ||
|
||||
// (e.clientX + bdScrollLeft);
|
||||
|
@ -240,93 +266,218 @@ export class GridLayout{
|
|||
// }
|
||||
});
|
||||
|
||||
//Configure sort extension if any
|
||||
var sort = (f.extensions || []).filter(function(itm){
|
||||
return itm.name === 'sort';
|
||||
});
|
||||
if(sort.length === 1){
|
||||
sort[0].async_sort = true;
|
||||
sort[0].trigger_ids = sortTriggers;
|
||||
// TODO: Trigger a custom event handled by sort extension
|
||||
let sort = tf.extension('sort');
|
||||
if (sort) {
|
||||
sort.asyncSort = true;
|
||||
sort.triggerIds = sortTriggers;
|
||||
}
|
||||
|
||||
//Cols generation for all browsers excepted IE<=7
|
||||
this.tblHasColTag = Dom.tag(tbl, 'col').length > 0 ? true : false;
|
||||
|
||||
//Col elements are enough to keep column widths after sorting and
|
||||
//filtering
|
||||
var createColTags = function(){
|
||||
for(var k=(tf.nbCells-1); k>=0; k--){
|
||||
var col = Dom.create('col', ['id', tf.id+'_col_'+k]);
|
||||
tbl.insertBefore(col, tbl.firstChild);
|
||||
col.style.width = tf.colWidths[k];
|
||||
this.gridColElms[k] = col;
|
||||
}
|
||||
this.tblHasColTag = true;
|
||||
};
|
||||
this.setColumnElements();
|
||||
|
||||
if(!this.tblHasColTag){
|
||||
createColTags.call(this);
|
||||
if (tf.popupFilters) {
|
||||
filtersRow.style.display = NONE;
|
||||
}
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides TableFilter instance properties to adjust to grid layout mode
|
||||
* @private
|
||||
*/
|
||||
setOverrides() {
|
||||
let tf = this.tf;
|
||||
tf.refRow = 0;
|
||||
tf.headersRow = 0;
|
||||
tf.filtersRowIndex = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set grid-layout default column widths if column widths are not defined
|
||||
* @private
|
||||
*/
|
||||
setDefaultColWidths() {
|
||||
let tf = this.tf;
|
||||
if (tf.colWidths.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
tf.eachCol((k) => {
|
||||
let colW;
|
||||
let cell = tf.dom().rows[tf.getHeadersRowIndex()].cells[k];
|
||||
if (cell.width !== '') {
|
||||
colW = cell.width;
|
||||
} else if (cell.style.width !== '') {
|
||||
colW = parseInt(cell.style.width, 10);
|
||||
} else {
|
||||
colW = this.defaultColWidth;
|
||||
}
|
||||
tf.colWidths[k] = colW;
|
||||
});
|
||||
|
||||
tf.setColWidths();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial table width
|
||||
* @returns {Number}
|
||||
* @private
|
||||
*/
|
||||
initialTableWidth() {
|
||||
let tbl = this.tf.dom();
|
||||
let width; //initial table width
|
||||
|
||||
if (tbl.width !== '') {
|
||||
width = tbl.width;
|
||||
}
|
||||
else if (tbl.style.width !== '') {
|
||||
width = tbl.style.width;
|
||||
} else {
|
||||
var cols = Dom.tag(tbl, 'col');
|
||||
for(var ii=0; ii<tf.nbCells; ii++){
|
||||
cols[ii].setAttribute('id', tf.id+'_col_'+ii);
|
||||
cols[ii].style.width = tf.colWidths[ii];
|
||||
this.gridColElms.push(cols[ii]);
|
||||
width = tbl.clientWidth;
|
||||
}
|
||||
return parseInt(width, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates container element
|
||||
* @param {String} tag Tag name
|
||||
* @param {String} className Css class to assign to element
|
||||
* @returns {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
createContainer(tag, className) {
|
||||
let element = createElm(tag);
|
||||
element.className = className;
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates filters row with cells
|
||||
* @returns {HTMLTableRowElement}
|
||||
* @private
|
||||
*/
|
||||
createFiltersRow() {
|
||||
let tf = this.tf;
|
||||
let filtersRow = createElm('tr');
|
||||
if (this.filters && tf.fltGrid) {
|
||||
tf.externalFltIds = [];
|
||||
tf.eachCol((j) => {
|
||||
let fltTdId = `${tf.prfxFlt + j + this.prfxGridFltTd + tf.id}`;
|
||||
let cl = createElm(tf.fltCellTag, ['id', fltTdId]);
|
||||
filtersRow.appendChild(cl);
|
||||
tf.externalFltIds[j] = fltTdId;
|
||||
});
|
||||
}
|
||||
return filtersRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates column elements if necessary and assigns their widths
|
||||
* @private
|
||||
*/
|
||||
setColumnElements() {
|
||||
let tf = this.tf;
|
||||
let cols = tag(tf.dom(), 'col');
|
||||
this.tblHasColTag = cols.length > 0;
|
||||
|
||||
for (let k = (tf.getCellsNb() - 1); k >= 0; k--) {
|
||||
let col;
|
||||
|
||||
if (!this.tblHasColTag) {
|
||||
col = createElm('col');
|
||||
tf.dom().insertBefore(col, tf.dom().firstChild);
|
||||
} else {
|
||||
col = cols[k];
|
||||
}
|
||||
col.style.width = tf.colWidths[k];
|
||||
this.colElms[k] = col;
|
||||
}
|
||||
this.tblHasColTag = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets headers row in headers table
|
||||
* @param {HTMLHeadElement} tableHead Table head element
|
||||
* @private
|
||||
*/
|
||||
setHeadersRow(tableHead) {
|
||||
if (this.noHeaders) {
|
||||
// Handle table with no headers, assuming here headers do not
|
||||
// exist
|
||||
tableHead.appendChild(createElm('tr'));
|
||||
} else {
|
||||
// Headers row are moved from content table to headers table
|
||||
for (let i = 0; i < this.headRows.length; i++) {
|
||||
let row = this.tf.dom().rows[this.headRows[i]];
|
||||
tableHead.appendChild(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var afterColResizedFn = Types.isFn(f.on_after_col_resized) ?
|
||||
f.on_after_col_resized : null;
|
||||
f.on_after_col_resized = function(o, colIndex){
|
||||
if(!colIndex){
|
||||
return;
|
||||
}
|
||||
var w = o.crWColsRow.cells[colIndex].style.width;
|
||||
var col = o.gridColElms[colIndex];
|
||||
col.style.width = w;
|
||||
|
||||
var thCW = o.crWColsRow.cells[colIndex].clientWidth;
|
||||
var tdCW = o.crWRowDataTbl.cells[colIndex].clientWidth;
|
||||
|
||||
if(thCW != tdCW){
|
||||
o.headTbl.style.width = tbl.clientWidth+'px';
|
||||
}
|
||||
|
||||
if(afterColResizedFn){
|
||||
afterColResizedFn.call(null,o,colIndex);
|
||||
}
|
||||
};
|
||||
|
||||
if(tf.popUpFilters){
|
||||
filtersRow.style.display = 'none';
|
||||
/**
|
||||
* Sets width defined in configuration to passed element
|
||||
* @param {DOMElement} element DOM element
|
||||
* @private
|
||||
*/
|
||||
setConfigWidth(element) {
|
||||
if (!this.width) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(tbl.clientWidth !== this.headTbl.clientWidth){
|
||||
tbl.style.width = this.headTbl.clientWidth+'px';
|
||||
if (this.width.indexOf('%') !== -1) {
|
||||
element.style.width = '100%';
|
||||
} else {
|
||||
element.style.width = this.width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of header IDs used for specifing external sort triggers
|
||||
* @param {HTMLTableRowElement} row DOM row element
|
||||
* @returns {Array} List of IDs
|
||||
* @private
|
||||
*/
|
||||
getSortTriggerIds(row) {
|
||||
let tf = this.tf;
|
||||
let sortTriggers = [];
|
||||
tf.eachCol((n) => {
|
||||
let c = row.cells[n];
|
||||
let thId = c.getAttribute('id');
|
||||
if (!thId || thId === '') {
|
||||
thId = `${this.prfxGridTh + n}_${tf.id}`;
|
||||
c.setAttribute('id', thId);
|
||||
}
|
||||
sortTriggers.push(thId);
|
||||
});
|
||||
return sortTriggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the grid layout
|
||||
*/
|
||||
destroy(){
|
||||
var tf = this.tf;
|
||||
var tbl = tf.tbl;
|
||||
destroy() {
|
||||
let tf = this.tf;
|
||||
let tbl = tf.dom();
|
||||
|
||||
if(!tf.gridLayout){
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
var t = tbl.parentNode.removeChild(tbl);
|
||||
let t = removeElm(tbl);
|
||||
this.tblMainCont.parentNode.insertBefore(t, this.tblMainCont);
|
||||
this.tblMainCont.parentNode.removeChild(this.tblMainCont);
|
||||
removeElm(this.tblMainCont);
|
||||
|
||||
this.tblMainCont = null;
|
||||
this.headTblCont = null;
|
||||
this.headTbl = null;
|
||||
this.tblCont = null;
|
||||
|
||||
tbl.outerHTML = tf.sourceTblHtml;
|
||||
//needed to keep reference of table element
|
||||
tbl = Dom.id(tf.id);
|
||||
tbl.outerHTML = this.sourceTblHtml;
|
||||
//needed to keep reference of table element for future usage
|
||||
this.tf.tbl = elm(tf.id);
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
126
src/modules/hash.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
import {addEvt, removeEvt} from '../event';
|
||||
import {root} from '../root';
|
||||
|
||||
const JSON = root.JSON;
|
||||
const location = root.location;
|
||||
const decodeURIComponent = root.decodeURIComponent;
|
||||
const encodeURIComponent = root.encodeURIComponent;
|
||||
|
||||
/**
|
||||
* Checks if browser has onhashchange event
|
||||
*/
|
||||
export const hasHashChange = () => {
|
||||
let docMode = root.documentMode;
|
||||
return ('onhashchange' in root) && (docMode === undefined || docMode > 7);
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages state via URL hash changes
|
||||
*
|
||||
* @export
|
||||
* @class Hash
|
||||
*/
|
||||
export class Hash {
|
||||
|
||||
/**
|
||||
* Creates an instance of Hash
|
||||
*
|
||||
* @param {State} state Instance of State
|
||||
*/
|
||||
constructor(state) {
|
||||
/**
|
||||
* State object
|
||||
* @type {State}
|
||||
*/
|
||||
this.state = state;
|
||||
|
||||
/**
|
||||
* Cached URL hash
|
||||
* @type {String} Hash string
|
||||
* @private
|
||||
*/
|
||||
this.lastHash = null;
|
||||
|
||||
/**
|
||||
* Application event emitter instance
|
||||
* @type {Emitter}
|
||||
*/
|
||||
this.emitter = state.emitter;
|
||||
|
||||
/**
|
||||
* Bound sync wrapper for future use
|
||||
* @private
|
||||
*/
|
||||
this.boundSync = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Hash object
|
||||
*/
|
||||
init() {
|
||||
if (!hasHashChange()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastHash = location.hash;
|
||||
//Store a bound sync wrapper
|
||||
this.boundSync = this.sync.bind(this);
|
||||
this.emitter.on(['state-changed'], (tf, state) => this.update(state));
|
||||
this.emitter.on(['initialized'], this.boundSync);
|
||||
addEvt(root, 'hashchange', this.boundSync);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the URL hash based on a state change
|
||||
*
|
||||
* @param {State} state Instance of State
|
||||
*/
|
||||
update(state) {
|
||||
let hash = `#${encodeURIComponent(JSON.stringify(state))}`;
|
||||
if (this.lastHash === hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
location.hash = hash;
|
||||
this.lastHash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a URL hash into a JSON object
|
||||
*
|
||||
* @param {String} hash URL hash fragment
|
||||
* @returns {Object} JSON object
|
||||
*/
|
||||
parse(hash) {
|
||||
if (hash.indexOf('#') === -1) {
|
||||
return null;
|
||||
}
|
||||
hash = hash.substr(1);
|
||||
return JSON.parse(decodeURIComponent(hash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies current hash state to features
|
||||
*/
|
||||
sync() {
|
||||
let state = this.parse(location.hash);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
// override current state with persisted one and sync features
|
||||
this.state.overrideAndSync(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release Hash event subscriptions and clear fields
|
||||
*/
|
||||
destroy() {
|
||||
this.emitter.off(['state-changed'], (tf, state) => this.update(state));
|
||||
this.emitter.off(['initialized'], this.boundSync);
|
||||
removeEvt(root, 'hashchange', this.boundSync);
|
||||
|
||||
this.state = null;
|
||||
this.lastHash = null;
|
||||
this.emitter = null;
|
||||
}
|
||||
}
|
|
@ -1,151 +1,264 @@
|
|||
import Dom from '../dom';
|
||||
import Event from '../event';
|
||||
|
||||
import {Feature} from '../feature';
|
||||
import {createElm, createText, elm, removeElm} from '../dom';
|
||||
import {addEvt, targetEvt, removeEvt} from '../event';
|
||||
import {NONE} from '../const';
|
||||
import {root} from '../root';
|
||||
import {isEmpty, isNull} from '../types';
|
||||
import {defaultsStr, defaultsNb} from '../settings';
|
||||
import {RIGHT} from './toolbar';
|
||||
|
||||
const WIKI_URL = 'https://github.com/koalyptus/TableFilter/wiki/' +
|
||||
'4.-Filter-operators';
|
||||
const WEBSITE_URL = 'http://koalyptus.github.io/TableFilter/';
|
||||
'4.-Filter-operators';
|
||||
const WEBSITE_URL = 'https://www.tablefilter.com/';
|
||||
|
||||
export class Help{
|
||||
/**
|
||||
* Help UI component
|
||||
*/
|
||||
export class Help extends Feature {
|
||||
|
||||
/**
|
||||
* Help UI component
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of Help
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
// Configuration object
|
||||
var f = tf.config();
|
||||
constructor(tf) {
|
||||
super(tf, Help);
|
||||
|
||||
//id of custom container element for instructions
|
||||
this.tgtId = f.help_instructions_target_id || null;
|
||||
//id of custom container element for instructions
|
||||
this.contTgtId = f.help_instructions_container_target_id ||
|
||||
null;
|
||||
//defines help text
|
||||
this.instrText = f.help_instructions_text ?
|
||||
f.help_instructions_text :
|
||||
let f = this.config.help_instructions || {};
|
||||
|
||||
/**
|
||||
* ID of main custom container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.tgtId = defaultsStr(f.target_id, null);
|
||||
|
||||
/**
|
||||
* ID of custom container element for instructions
|
||||
* @type {String}
|
||||
*/
|
||||
this.contTgtId = defaultsStr(f.container_target_id, null);
|
||||
|
||||
/**
|
||||
* Instructions text (accepts HTML)
|
||||
* @type {String}
|
||||
*/
|
||||
this.instrText = !isEmpty(f.text) ? f.text :
|
||||
'Use the filters above each column to filter and limit table ' +
|
||||
'data. Advanced searches can be performed by using the following ' +
|
||||
'operators: <br /><b><</b>, <b><=</b>, <b>></b>, ' +
|
||||
'<b>>=</b>, <b>=</b>, <b>*</b>, <b>!</b>, <b>{</b>, <b>}</b>, ' +
|
||||
'<b>||</b>,<b>&&</b>, <b>[empty]</b>, <b>[nonempty]</b>, ' +
|
||||
'<b>rgx:</b><br/><a href="'+ WIKI_URL +'" target="_blank">' +
|
||||
'<b>rgx:</b><br/><a href="' + WIKI_URL + '" target="_blank">' +
|
||||
'Learn more</a><hr/>';
|
||||
//defines help innerHtml
|
||||
this.instrHtml = f.help_instructions_html || null;
|
||||
//defines reset button text
|
||||
this.btnText = f.help_instructions_btn_text || '?';
|
||||
//defines reset button innerHtml
|
||||
this.btnHtml = f.help_instructions_btn_html || null;
|
||||
//defines css class for help button
|
||||
this.btnCssClass = f.help_instructions_btn_css_class || 'helpBtn';
|
||||
//defines css class for help container
|
||||
this.contCssClass = f.help_instructions_container_css_class ||
|
||||
'helpCont';
|
||||
//help button element
|
||||
|
||||
/**
|
||||
* Instructions HTML
|
||||
* @type {String}
|
||||
*/
|
||||
this.instrHtml = defaultsStr(f.html, null);
|
||||
|
||||
/**
|
||||
* Help button text ('?')
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnText = defaultsStr(f.btn_text, '?');
|
||||
|
||||
/**
|
||||
* Custom help button HTML
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnHtml = defaultsStr(f.btn_html, null);
|
||||
|
||||
/**
|
||||
* Css class for help button
|
||||
* @type {String}
|
||||
*/
|
||||
this.btnCssClass = defaultsStr(f.btn_css_class, 'helpBtn');
|
||||
|
||||
/**
|
||||
* Css class for help container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.contCssClass = defaultsStr(f.container_css_class, 'helpCont');
|
||||
|
||||
/**
|
||||
* Button DOM element
|
||||
* @type {DOMElement}
|
||||
*/
|
||||
this.btn = null;
|
||||
//help content div
|
||||
|
||||
/**
|
||||
* Help container DOM element
|
||||
* @type {DOMElement}
|
||||
*/
|
||||
this.cont = null;
|
||||
|
||||
/**
|
||||
* Adjust container left position when table's horizontal scroll is
|
||||
* on, typically when `responsive` option is enabled.
|
||||
* @type {Number}
|
||||
* @defaultValue 25
|
||||
*/
|
||||
this.contAdjustLeftPosition =
|
||||
defaultsNb(f.container_adjust_left_position, 25);
|
||||
|
||||
/**
|
||||
* Bound mouseup wrapper
|
||||
* @private
|
||||
*/
|
||||
this.boundMouseup = null;
|
||||
|
||||
/**
|
||||
* Default HTML appended to instructions text
|
||||
* @type {String}
|
||||
*/
|
||||
this.defaultHtml = '<div class="helpFooter"><h4>TableFilter ' +
|
||||
'v'+ tf.version +'</h4>' +
|
||||
'<a href="'+ WEBSITE_URL +'" target="_blank">'+ WEBSITE_URL +'</a>'+
|
||||
'<br/><span>©2015-'+ tf.year +' {AUTHOR}</span>' +
|
||||
'v' + tf.version + '</h4>' + '<a href="' + WEBSITE_URL +
|
||||
'" target="_blank">' + WEBSITE_URL + '</a>' +
|
||||
'<br/><span>©2015-' + tf.year + ' {AUTHOR}</span>' +
|
||||
'<div align="center" style="margin-top:8px;">' +
|
||||
'<a href="javascript:void(0);" class="close">Close</a></div></div>';
|
||||
|
||||
//id prefix for help elements
|
||||
this.prfxHelpSpan = 'helpSpan_';
|
||||
//id prefix for help elements
|
||||
this.prfxHelpDiv = 'helpDiv_';
|
||||
/**
|
||||
* Default position in toolbar ('left'|'center'|'right')
|
||||
* @type {String}
|
||||
*/
|
||||
this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
|
||||
|
||||
this.tf = tf;
|
||||
this.emitter.on(['init-help'], () => this.init());
|
||||
}
|
||||
|
||||
init(){
|
||||
if(this.btn){
|
||||
/**
|
||||
* Mouse-up event handler handling popup auto-close behaviour
|
||||
* @private
|
||||
*/
|
||||
onMouseup(evt) {
|
||||
let targetElm = targetEvt(evt);
|
||||
|
||||
while (targetElm && targetElm !== this.cont && targetElm !== this.btn) {
|
||||
targetElm = targetElm.parentNode;
|
||||
}
|
||||
|
||||
if (targetElm !== this.cont && targetElm !== this.btn) {
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise Help instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tf = this.tf;
|
||||
this.emitter.emit('initializing-feature', this, !isNull(this.tgtId));
|
||||
|
||||
var helpspan = Dom.create('span',['id', this.prfxHelpSpan+tf.id]);
|
||||
var helpdiv = Dom.create('div',['id', this.prfxHelpDiv+tf.id]);
|
||||
let tf = this.tf;
|
||||
|
||||
let btn = createElm('span');
|
||||
let cont = createElm('div');
|
||||
|
||||
this.boundMouseup = this.onMouseup.bind(this);
|
||||
|
||||
//help button is added to defined element
|
||||
if(!this.tgtId){
|
||||
tf.setToolbar();
|
||||
}
|
||||
var targetEl = !this.tgtId ?
|
||||
tf.rDiv : Dom.id(this.tgtId);
|
||||
targetEl.appendChild(helpspan);
|
||||
let targetEl = !this.tgtId ?
|
||||
tf.feature('toolbar').container(this.toolbarPosition) :
|
||||
elm(this.tgtId);
|
||||
targetEl.appendChild(btn);
|
||||
|
||||
var divContainer = !this.contTgtId ?
|
||||
helpspan : Dom.id(this.contTgtId);
|
||||
let divContainer = !this.contTgtId ? btn : elm(this.contTgtId);
|
||||
|
||||
if(!this.btnHtml){
|
||||
divContainer.appendChild(helpdiv);
|
||||
var helplink = Dom.create('a', ['href', 'javascript:void(0);']);
|
||||
if (!this.btnHtml) {
|
||||
divContainer.appendChild(cont);
|
||||
let helplink = createElm('a', ['href', 'javascript:void(0);']);
|
||||
helplink.className = this.btnCssClass;
|
||||
helplink.appendChild(Dom.text(this.btnText));
|
||||
helpspan.appendChild(helplink);
|
||||
Event.add(helplink, 'click', () => { this.toggle(); });
|
||||
helplink.appendChild(createText(this.btnText));
|
||||
btn.appendChild(helplink);
|
||||
addEvt(helplink, 'click', () => this.toggle());
|
||||
} else {
|
||||
helpspan.innerHTML = this.btnHtml;
|
||||
var helpEl = helpspan.firstChild;
|
||||
Event.add(helpEl, 'click', () => { this.toggle(); });
|
||||
divContainer.appendChild(helpdiv);
|
||||
btn.innerHTML = this.btnHtml;
|
||||
let helpEl = btn.firstChild;
|
||||
addEvt(helpEl, 'click', () => this.toggle());
|
||||
divContainer.appendChild(cont);
|
||||
}
|
||||
|
||||
if(!this.instrHtml){
|
||||
helpdiv.innerHTML = this.instrText;
|
||||
helpdiv.className = this.contCssClass;
|
||||
Event.add(helpdiv, 'dblclick', () => { this.toggle(); });
|
||||
if (!this.instrHtml) {
|
||||
cont.innerHTML = this.instrText;
|
||||
cont.className = this.contCssClass;
|
||||
} else {
|
||||
if(this.contTgtId){
|
||||
divContainer.appendChild(helpdiv);
|
||||
if (this.contTgtId) {
|
||||
divContainer.appendChild(cont);
|
||||
}
|
||||
helpdiv.innerHTML = this.instrHtml;
|
||||
if(!this.contTgtId){
|
||||
helpdiv.className = this.contCssClass;
|
||||
Event.add(helpdiv, 'dblclick', () => { this.toggle(); });
|
||||
cont.innerHTML = this.instrHtml;
|
||||
if (!this.contTgtId) {
|
||||
cont.className = this.contCssClass;
|
||||
}
|
||||
}
|
||||
helpdiv.innerHTML += this.defaultHtml;
|
||||
Event.add(helpdiv, 'click', () => { this.toggle(); });
|
||||
cont.innerHTML += this.defaultHtml;
|
||||
addEvt(cont, 'click', () => this.toggle());
|
||||
|
||||
this.cont = helpdiv;
|
||||
this.btn = helpspan;
|
||||
this.cont = cont;
|
||||
this.btn = btn;
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
|
||||
this.emitter.emit('feature-initialized', this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle help pop-up
|
||||
*/
|
||||
toggle(){
|
||||
if(!this.cont){
|
||||
toggle() {
|
||||
// check only if explicitily disabled as in this case undefined
|
||||
// signifies the help feature is enabled by default
|
||||
if (!this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
var divDisplay = this.cont.style.display;
|
||||
if(divDisplay === '' || divDisplay === 'none'){
|
||||
|
||||
// ensure mouseup event handler is removed
|
||||
removeEvt(root, 'mouseup', this.boundMouseup);
|
||||
|
||||
let divDisplay = this.cont.style.display;
|
||||
if (divDisplay === '' || divDisplay === NONE) {
|
||||
this.cont.style.display = 'inline';
|
||||
|
||||
// if table element has an horizontal scrollbar adjust container
|
||||
// left position accordingly
|
||||
if (this.tf.dom().scrollLeft > 0) {
|
||||
this.cont.style.left = `${
|
||||
this.btn.offsetLeft
|
||||
- this.tf.dom().scrollLeft
|
||||
+ this.contAdjustLeftPosition
|
||||
}px`;
|
||||
}
|
||||
|
||||
addEvt(root, 'mouseup', this.boundMouseup);
|
||||
} else {
|
||||
this.cont.style.display = 'none';
|
||||
this.cont.style.display = NONE;
|
||||
this.cont.style.left = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove help UI
|
||||
*/
|
||||
destroy(){
|
||||
if(!this.btn){
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.btn.parentNode.removeChild(this.btn);
|
||||
removeElm(this.btn);
|
||||
this.btn = null;
|
||||
if(!this.cont){
|
||||
return;
|
||||
}
|
||||
this.cont.parentNode.removeChild(this.cont);
|
||||
|
||||
removeElm(this.cont);
|
||||
this.cont = null;
|
||||
|
||||
this.boundMouseup = null;
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: remove as soon as feature name is fixed
|
||||
Help.meta = {alwaysInstantiate: true};
|
||||
|
|
|
@ -1,58 +1,94 @@
|
|||
import Dom from '../dom';
|
||||
import Str from '../string';
|
||||
import {createText, createElm, getText} from '../dom';
|
||||
import {isNull} from '../types';
|
||||
import {rgxEsc} from '../string';
|
||||
import {defaultsStr} from '../settings';
|
||||
|
||||
export class HighlightKeyword{
|
||||
/**
|
||||
* Highlight matched keywords upon filtering
|
||||
*
|
||||
* @export
|
||||
* @class HighlightKeyword
|
||||
*/
|
||||
export class HighlightKeyword {
|
||||
|
||||
/**
|
||||
* HighlightKeyword, highlight matched keyword
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of HighlightKeyword
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf) {
|
||||
var f = tf.config();
|
||||
//defines css class for highlighting
|
||||
this.highlightCssClass = f.highlight_css_class || 'keyword';
|
||||
this.highlightedNodes = [];
|
||||
let f = tf.config();
|
||||
|
||||
/**
|
||||
* Css class for highlighted term
|
||||
* @type {String}
|
||||
*/
|
||||
this.highlightCssClass = defaultsStr(f.highlight_css_class, 'keyword');
|
||||
|
||||
/**
|
||||
* TableFilter instance
|
||||
* @type {TableFilter}
|
||||
*/
|
||||
this.tf = tf;
|
||||
|
||||
/**
|
||||
* TableFilter's emitter instance
|
||||
* @type {Emitter}
|
||||
*/
|
||||
this.emitter = tf.emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* highlight occurences of searched term in passed node
|
||||
* @param {Node} node
|
||||
* @param {String} word Searched term
|
||||
* @param {String} cssClass Css class name
|
||||
* Initializes HighlightKeyword instance
|
||||
*/
|
||||
highlight(node, word, cssClass){
|
||||
init() {
|
||||
this.emitter.on(
|
||||
['before-filtering', 'destroy'],
|
||||
() => this.unhighlightAll()
|
||||
);
|
||||
this.emitter.on(
|
||||
['highlight-keyword'],
|
||||
(tf, cell, term) => this._processTerm(cell, term)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight occurences of searched term in passed node
|
||||
* @param {Node} node
|
||||
* @param {String} term Searched term
|
||||
* @param {String} cssClass Css class name
|
||||
*
|
||||
* TODO: refactor this method
|
||||
*/
|
||||
highlight(node, term, cssClass) {
|
||||
// Iterate into this nodes childNodes
|
||||
if(node.hasChildNodes){
|
||||
var children = node.childNodes;
|
||||
for(var i=0; i<children.length; i++){
|
||||
this.highlight(children[i], word, cssClass);
|
||||
if (node.hasChildNodes) {
|
||||
let children = node.childNodes;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
this.highlight(children[i], term, cssClass);
|
||||
}
|
||||
}
|
||||
|
||||
if(node.nodeType === 3){
|
||||
var tempNodeVal = Str.lower(node.nodeValue);
|
||||
var tempWordVal = Str.lower(word);
|
||||
if(tempNodeVal.indexOf(tempWordVal) != -1){
|
||||
var pn = node.parentNode;
|
||||
if(pn && pn.className != cssClass){
|
||||
// word not highlighted yet
|
||||
var nv = node.nodeValue,
|
||||
ni = tempNodeVal.indexOf(tempWordVal),
|
||||
if (node.nodeType === 3) {
|
||||
let nodeVal = node.nodeValue.toLowerCase();
|
||||
let termIdx = nodeVal.indexOf(term.toLowerCase());
|
||||
|
||||
if (termIdx !== -1) {
|
||||
let pn = node.parentNode;
|
||||
if (pn && pn.className !== cssClass) {
|
||||
// term not highlighted yet
|
||||
let nv = node.nodeValue,
|
||||
// Create a load of replacement nodes
|
||||
before = Dom.text(nv.substr(0, ni)),
|
||||
docWordVal = nv.substr(ni,word.length),
|
||||
after = Dom.text(nv.substr(ni+word.length)),
|
||||
hiwordtext = Dom.text(docWordVal),
|
||||
hiword = Dom.create('span');
|
||||
hiword.className = cssClass;
|
||||
hiword.appendChild(hiwordtext);
|
||||
pn.insertBefore(before,node);
|
||||
pn.insertBefore(hiword,node);
|
||||
pn.insertBefore(after,node);
|
||||
before = createText(nv.substr(0, termIdx)),
|
||||
value = nv.substr(termIdx, term.length),
|
||||
after = createText(nv.substr(termIdx + term.length)),
|
||||
text = createText(value),
|
||||
container = createElm('span');
|
||||
container.className = cssClass;
|
||||
container.appendChild(text);
|
||||
pn.insertBefore(before, node);
|
||||
pn.insertBefore(container, node);
|
||||
pn.insertBefore(after, node);
|
||||
pn.removeChild(node);
|
||||
this.highlightedNodes.push(hiword.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,49 +96,85 @@ export class HighlightKeyword{
|
|||
|
||||
/**
|
||||
* Removes highlight to nodes matching passed string
|
||||
* @param {String} word
|
||||
* @param {String} term
|
||||
* @param {String} cssClass Css class to remove
|
||||
*/
|
||||
unhighlight(word, cssClass){
|
||||
var arrRemove = [];
|
||||
var highlightedNodes = this.highlightedNodes;
|
||||
for(var i=0; i<highlightedNodes.length; i++){
|
||||
var n = highlightedNodes[i];
|
||||
if(!n){
|
||||
continue;
|
||||
unhighlight(term, cssClass) {
|
||||
let highlightedNodes = this.tf.dom().querySelectorAll(`.${cssClass}`);
|
||||
for (let i = 0; i < highlightedNodes.length; i++) {
|
||||
let n = highlightedNodes[i];
|
||||
let nodeVal = getText(n);
|
||||
|
||||
if (isNull(term) ||
|
||||
nodeVal.toLowerCase().indexOf(term.toLowerCase()) !== -1) {
|
||||
let parentNode = n.parentNode;
|
||||
parentNode.replaceChild(createText(nodeVal), n);
|
||||
parentNode.normalize();
|
||||
}
|
||||
var tempNodeVal = Str.lower(n.nodeValue),
|
||||
tempWordVal = Str.lower(word);
|
||||
if(tempNodeVal.indexOf(tempWordVal) !== -1){
|
||||
var pn = n.parentNode;
|
||||
if(pn && pn.className === cssClass){
|
||||
var prevSib = pn.previousSibling,
|
||||
nextSib = pn.nextSibling;
|
||||
if(!prevSib || !nextSib){ continue; }
|
||||
nextSib.nodeValue = prevSib.nodeValue + n.nodeValue +
|
||||
nextSib.nodeValue;
|
||||
prevSib.nodeValue = '';
|
||||
n.nodeValue = '';
|
||||
arrRemove.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var k=0; k<arrRemove.length; k++){
|
||||
highlightedNodes.splice(arrRemove[k], 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all occurrences of highlighted nodes
|
||||
*/
|
||||
unhighlightAll(){
|
||||
if(!this.tf.highlightKeywords || !this.tf.searchArgs){
|
||||
unhighlightAll() {
|
||||
if (!this.tf.highlightKeywords) {
|
||||
return;
|
||||
}
|
||||
for(var y=0; y<this.tf.searchArgs.length; y++){
|
||||
this.unhighlight(
|
||||
this.tf.searchArgs[y], this.highlightCssClass);
|
||||
}
|
||||
this.highlightedNodes = [];
|
||||
|
||||
this.unhighlight(null, this.highlightCssClass);
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove feature */
|
||||
destroy() {
|
||||
this.emitter.off(
|
||||
['before-filtering', 'destroy'],
|
||||
() => this.unhighlightAll()
|
||||
);
|
||||
this.emitter.off(
|
||||
['highlight-keyword'],
|
||||
(tf, cell, term) => this._processTerm(cell, term)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure filtering operators are handled before highlighting any match
|
||||
* @param {any} Table cell to look searched term into
|
||||
* @param {any} Searched termIdx
|
||||
*/
|
||||
_processTerm(cell, term) {
|
||||
let tf = this.tf;
|
||||
let reLk = new RegExp(rgxEsc(tf.lkOperator));
|
||||
let reEq = new RegExp(tf.eqOperator);
|
||||
let reSt = new RegExp(tf.stOperator);
|
||||
let reEn = new RegExp(tf.enOperator);
|
||||
let reLe = new RegExp(tf.leOperator);
|
||||
let reGe = new RegExp(tf.geOperator);
|
||||
let reL = new RegExp(tf.lwOperator);
|
||||
let reG = new RegExp(tf.grOperator);
|
||||
let reD = new RegExp(tf.dfOperator);
|
||||
|
||||
term = term
|
||||
.replace(reLk, '')
|
||||
.replace(reEq, '')
|
||||
.replace(reSt, '')
|
||||
.replace(reEn, '');
|
||||
|
||||
if (reLe.test(term) || reGe.test(term) || reL.test(term) ||
|
||||
reG.test(term) || reD.test(term)) {
|
||||
term = getText(cell);
|
||||
}
|
||||
|
||||
if (term === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.highlight(cell, term, this.highlightCssClass);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove as soon as feature name is fixed
|
||||
HighlightKeyword.meta = {
|
||||
name: 'highlightKeyword',
|
||||
altName: 'highlightKeywords'
|
||||
};
|
||||
|
|
|
@ -1,92 +1,183 @@
|
|||
import Dom from '../dom';
|
||||
import Types from '../types';
|
||||
import {Feature} from '../feature';
|
||||
import {createElm, createText, elm, removeElm} from '../dom';
|
||||
import {EMPTY_FN} from '../types';
|
||||
import {root} from '../root';
|
||||
import {NONE} from '../const';
|
||||
import {defaultsStr, defaultsFn} from '../settings';
|
||||
|
||||
var global = window;
|
||||
const BEFORE_ACTION_EVENTS = [
|
||||
'before-filtering',
|
||||
'before-populating-filter',
|
||||
'before-page-change',
|
||||
'before-clearing-filters',
|
||||
'before-page-length-change',
|
||||
'before-reset-page',
|
||||
'before-reset-page-length',
|
||||
'before-loading-extensions',
|
||||
'before-loading-themes'
|
||||
];
|
||||
|
||||
export class Loader{
|
||||
const AFTER_ACTION_EVENTS = [
|
||||
'after-filtering',
|
||||
'after-populating-filter',
|
||||
'after-page-change',
|
||||
'after-clearing-filters',
|
||||
'after-page-length-change',
|
||||
'after-reset-page',
|
||||
'after-reset-page-length',
|
||||
'after-loading-extensions',
|
||||
'after-loading-themes'
|
||||
];
|
||||
|
||||
/**
|
||||
* Activity indicator
|
||||
*
|
||||
* @export
|
||||
* @class Loader
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class Loader extends Feature {
|
||||
|
||||
/**
|
||||
* Loading message/spinner
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of Loader.
|
||||
*
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
constructor(tf) {
|
||||
super(tf, Loader);
|
||||
|
||||
// TableFilter configuration
|
||||
var f = tf.config();
|
||||
//id of container element
|
||||
this.loaderTgtId = f.loader_target_id || null;
|
||||
//div containing loader
|
||||
this.loaderDiv = null;
|
||||
//defines loader text
|
||||
this.loaderText = f.loader_text || 'Loading...';
|
||||
//defines loader innerHtml
|
||||
this.loaderHtml = f.loader_html || null;
|
||||
//defines css class for loader div
|
||||
this.loaderCssClass = f.loader_css_class || 'loader';
|
||||
//delay for hiding loader
|
||||
this.loaderCloseDelay = 200;
|
||||
//callback function before loader is displayed
|
||||
this.onShowLoader = Types.isFn(f.on_show_loader) ?
|
||||
f.on_show_loader : null;
|
||||
//callback function after loader is closed
|
||||
this.onHideLoader = Types.isFn(f.on_hide_loader) ?
|
||||
f.on_hide_loader : null;
|
||||
//loader div
|
||||
this.prfxLoader = 'load_';
|
||||
let f = this.config.loader || {};
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* ID of custom container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.targetId = defaultsStr(f.target_id, null);
|
||||
|
||||
var containerDiv = Dom.create('div', ['id', this.prfxLoader+tf.id]);
|
||||
containerDiv.className = this.loaderCssClass;
|
||||
/**
|
||||
* Loader container DOM element
|
||||
* @type {DOMElement}
|
||||
*/
|
||||
this.cont = null;
|
||||
|
||||
var targetEl = !this.loaderTgtId ?
|
||||
tf.tbl.parentNode : Dom.id(this.loaderTgtId);
|
||||
if(!this.loaderTgtId){
|
||||
targetEl.insertBefore(containerDiv, tf.tbl);
|
||||
/**
|
||||
* Text displayed when indicator is visible
|
||||
* @type {String}
|
||||
*/
|
||||
this.text = defaultsStr(f.text, 'Loading...');
|
||||
|
||||
/**
|
||||
* Custom HTML injected in Loader's container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.html = defaultsStr(f.html, null);
|
||||
|
||||
/**
|
||||
* Css class for Loader's container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.cssClass = defaultsStr(f.css_class, 'loader');
|
||||
|
||||
/**
|
||||
* Close delay in milliseconds
|
||||
* @type {Number}
|
||||
*/
|
||||
this.closeDelay = 250;
|
||||
|
||||
/**
|
||||
* Callback fired when loader is displayed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onShow = defaultsFn(f.on_show_loader, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired when loader is closed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onHide = defaultsFn(f.on_hide_loader, EMPTY_FN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Loader instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tf = this.tf;
|
||||
let emitter = this.emitter;
|
||||
|
||||
let containerDiv = createElm('div');
|
||||
containerDiv.className = this.cssClass;
|
||||
|
||||
let targetEl = !this.targetId ?
|
||||
tf.dom().parentNode : elm(this.targetId);
|
||||
if (!this.targetId) {
|
||||
targetEl.insertBefore(containerDiv, tf.dom());
|
||||
} else {
|
||||
targetEl.appendChild(containerDiv);
|
||||
}
|
||||
this.loaderDiv = Dom.id(this.prfxLoader+tf.id);
|
||||
if(!this.loaderHtml){
|
||||
this.loaderDiv.appendChild(Dom.text(this.loaderText));
|
||||
this.cont = containerDiv;
|
||||
if (!this.html) {
|
||||
this.cont.appendChild(createText(this.text));
|
||||
} else {
|
||||
this.loaderDiv.innerHTML = this.loaderHtml;
|
||||
this.cont.innerHTML = this.html;
|
||||
}
|
||||
|
||||
this.show(NONE);
|
||||
|
||||
// Subscribe to events
|
||||
emitter.on(BEFORE_ACTION_EVENTS, () => this.show(''));
|
||||
emitter.on(AFTER_ACTION_EVENTS, () => this.show(NONE));
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides activity indicator
|
||||
* @param {String} Two possible values: '' or 'none'
|
||||
*/
|
||||
show(p) {
|
||||
if(!this.tf.loader || !this.loaderDiv ||
|
||||
this.loaderDiv.style.display===p){
|
||||
if (!this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var displayLoader = () => {
|
||||
if(!this.loaderDiv){
|
||||
function displayLoader() {
|
||||
if (!this.cont) {
|
||||
return;
|
||||
}
|
||||
if(this.onShowLoader && p!=='none'){
|
||||
this.onShowLoader.call(null, this);
|
||||
if (p !== NONE) {
|
||||
this.onShow(this);
|
||||
}
|
||||
this.loaderDiv.style.display = p;
|
||||
if(this.onHideLoader && p==='none'){
|
||||
this.onHideLoader.call(null, this);
|
||||
this.cont.style.display = p;
|
||||
if (p === NONE) {
|
||||
this.onHide(this);
|
||||
}
|
||||
};
|
||||
|
||||
var t = p==='none' ? this.loaderCloseDelay : 1;
|
||||
global.setTimeout(displayLoader, t);
|
||||
let t = p === NONE ? this.closeDelay : 1;
|
||||
root.setTimeout(displayLoader.bind(this), t);
|
||||
}
|
||||
|
||||
destroy(){
|
||||
if(!this.loaderDiv){
|
||||
/**
|
||||
* Removes feature
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
var tf = this.tf,
|
||||
targetEl = !this.loaderTgtId ?
|
||||
(tf.gridLayout ?
|
||||
tf.feature('gridLayout').tblCont : tf.tbl.parentNode):
|
||||
Dom.id(this.loaderTgtId);
|
||||
targetEl.removeChild(this.loaderDiv);
|
||||
this.loaderDiv = null;
|
||||
|
||||
let emitter = this.emitter;
|
||||
|
||||
removeElm(this.cont);
|
||||
this.cont = null;
|
||||
|
||||
// Unsubscribe to events
|
||||
emitter.off(BEFORE_ACTION_EVENTS, () => this.show(''));
|
||||
emitter.off(AFTER_ACTION_EVENTS, () => this.show(NONE));
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
|
146
src/modules/markActiveColumns.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
import {Feature} from '../feature';
|
||||
import {addClass, removeClass, hasClass} from '../dom';
|
||||
import {EMPTY_FN} from '../types';
|
||||
import {defaultsStr, defaultsFn} from '../settings';
|
||||
|
||||
/**
|
||||
* Visual indicator for filtered columns
|
||||
* @export
|
||||
* @class MarkActiveColumns
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class MarkActiveColumns extends Feature {
|
||||
|
||||
/**
|
||||
* Create an instance of MarkActiveColumns
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf) {
|
||||
super(tf, MarkActiveColumns);
|
||||
|
||||
let config = this.config.mark_active_columns || {};
|
||||
|
||||
/**
|
||||
* Css class for filtered (active) columns
|
||||
* @type {String}
|
||||
*/
|
||||
this.headerCssClass = defaultsStr(config.header_css_class,
|
||||
'activeHeader');
|
||||
|
||||
/**
|
||||
* Css class for filtered (active) column cells
|
||||
* @type {String}
|
||||
*/
|
||||
this.cellCssClass = defaultsStr(config.cell_css_class,
|
||||
'activeCell');
|
||||
|
||||
/**
|
||||
* Enable/disable column highlighting
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.highlightColumn = Boolean(config.highlight_column);
|
||||
|
||||
/**
|
||||
* Callback fired before a column is marked as filtered
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeActiveColumn = defaultsFn(config.on_before_active_column,
|
||||
EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after a column is marked as filtered
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterActiveColumn = defaultsFn(config.on_after_active_column,
|
||||
EMPTY_FN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise MarkActiveColumns instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.on(['before-filtering'], () => this.clearActiveColumns());
|
||||
this.emitter.on(
|
||||
['cell-processed'],
|
||||
(tf, colIndex) => this.markActiveColumn(colIndex)
|
||||
);
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear filtered columns visual indicator (background color)
|
||||
*/
|
||||
clearActiveColumns() {
|
||||
let tf = this.tf;
|
||||
tf.eachCol((idx) => {
|
||||
removeClass(tf.getHeaderElement(idx), this.headerCssClass);
|
||||
|
||||
if (this.highlightColumn) {
|
||||
this.eachColumnCell(idx,
|
||||
(cell) => removeClass(cell, this.cellCssClass));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark currently filtered column
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
markActiveColumn(colIndex) {
|
||||
let tf = this.tf;
|
||||
let header = tf.getHeaderElement(colIndex);
|
||||
if (hasClass(header, this.headerCssClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onBeforeActiveColumn(this, colIndex);
|
||||
|
||||
addClass(header, this.headerCssClass);
|
||||
|
||||
if (this.highlightColumn) {
|
||||
this.eachColumnCell(colIndex,
|
||||
(cell) => addClass(cell, this.cellCssClass));
|
||||
}
|
||||
|
||||
this.onAfterActiveColumn(this, colIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column cells iterator
|
||||
* TODO: make public and move into TableFilter if used elsewhere
|
||||
* @param {Number} colIndex
|
||||
* @param {Function} fn
|
||||
* @param {DOMElement} tbl
|
||||
* @private
|
||||
*/
|
||||
eachColumnCell(colIndex, fn = EMPTY_FN, tbl = this.tf.dom()) {
|
||||
// TODO: remove [].forEach when polyfill for PhanthomJs is available
|
||||
[].forEach.call(
|
||||
tbl.querySelectorAll(`tbody td:nth-child(${colIndex + 1})`), fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove feature
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearActiveColumns();
|
||||
this.emitter.off(['before-filtering'], () => this.clearActiveColumns());
|
||||
this.emitter.off(
|
||||
['cell-processed'],
|
||||
(tf, colIndex) => this.markActiveColumn(colIndex)
|
||||
);
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
193
src/modules/noResults.js
Normal file
|
@ -0,0 +1,193 @@
|
|||
import {Feature} from '../feature';
|
||||
import {createElm, elm, removeElm} from '../dom';
|
||||
import {isEmpty, EMPTY_FN} from '../types';
|
||||
import {NONE} from '../const';
|
||||
import {defaultsStr, defaultsFn} from '../settings';
|
||||
|
||||
/**
|
||||
* UI when filtering yields no matches
|
||||
* @export
|
||||
* @class NoResults
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class NoResults extends Feature {
|
||||
|
||||
/**
|
||||
* Creates an instance of NoResults
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf) {
|
||||
super(tf, NoResults);
|
||||
|
||||
//configuration object
|
||||
let f = this.config.no_results_message || {};
|
||||
|
||||
/**
|
||||
* Text (accepts HTML)
|
||||
* @type {String}
|
||||
*/
|
||||
this.content = defaultsStr(f.content, 'No results');
|
||||
|
||||
/**
|
||||
* Custom container DOM element
|
||||
* @type {DOMElement}
|
||||
*/
|
||||
this.customContainer = defaultsStr(f.custom_container, null);
|
||||
|
||||
/**
|
||||
* ID of custom container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.customContainerId = defaultsStr(f.custom_container_id, null);
|
||||
|
||||
/**
|
||||
* Indicates if UI is contained in a external element
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isExternal = !isEmpty(this.customContainer) ||
|
||||
!isEmpty(this.customContainerId);
|
||||
|
||||
/**
|
||||
* Css class assigned to container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.cssClass = defaultsStr(f.css_class, 'no-results');
|
||||
|
||||
/**
|
||||
* Stores container DOM element
|
||||
* @type {DOMElement}
|
||||
*/
|
||||
this.cont = null;
|
||||
|
||||
/**
|
||||
* Callback fired before the message is displayed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeShow = defaultsFn(f.on_before_show_msg, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after the message is displayed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterShow = defaultsFn(f.on_after_show_msg, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired before the message is hidden
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeHide = defaultsFn(f.on_before_hide_msg, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after the message is hidden
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterHide = defaultsFn(f.on_after_hide_msg, EMPTY_FN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes NoResults instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
let tf = this.tf;
|
||||
let target = this.customContainer || elm(this.customContainerId) ||
|
||||
tf.dom();
|
||||
|
||||
//container
|
||||
let cont = createElm('div');
|
||||
cont.className = this.cssClass;
|
||||
cont.innerHTML = this.content;
|
||||
|
||||
if (this.isExternal) {
|
||||
target.appendChild(cont);
|
||||
} else {
|
||||
target.parentNode.insertBefore(cont, target.nextSibling);
|
||||
}
|
||||
|
||||
this.cont = cont;
|
||||
|
||||
// subscribe to after-filtering event
|
||||
this.emitter.on(
|
||||
['initialized', 'after-filtering'],
|
||||
() => this.toggle()
|
||||
);
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle no results message
|
||||
*/
|
||||
toggle() {
|
||||
if (this.tf.getValidRowsNb() > 0) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show no results message
|
||||
*/
|
||||
show() {
|
||||
if (!this.initialized || !this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
this.onBeforeShow(this.tf, this);
|
||||
|
||||
this.setWidth();
|
||||
this.cont.style.display = 'block';
|
||||
|
||||
this.onAfterShow(this.tf, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide no results message
|
||||
*/
|
||||
hide() {
|
||||
if (!this.initialized || !this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
this.onBeforeHide(this.tf, this);
|
||||
|
||||
this.cont.style.display = NONE;
|
||||
|
||||
this.onAfterHide(this.tf, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets no results container width
|
||||
* @private
|
||||
*/
|
||||
setWidth() {
|
||||
if (!this.initialized || this.isExternal || !this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
let tf = this.tf;
|
||||
if (tf.gridLayout) {
|
||||
let gridLayout = tf.feature('gridLayout');
|
||||
this.cont.style.width = gridLayout.headTbl.clientWidth + 'px';
|
||||
} else {
|
||||
this.cont.style.width = (tf.dom().tHead ?
|
||||
tf.dom().tHead.clientWidth :
|
||||
tf.dom().tBodies[0].clientWidth) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove feature */
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
removeElm(this.cont);
|
||||
this.cont = null;
|
||||
// unsubscribe to after-filtering event
|
||||
this.emitter.off(['after-filtering'], () => this.toggle());
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
|
@ -1,108 +1,276 @@
|
|||
import Types from '../types';
|
||||
import Dom from '../dom';
|
||||
import Event from '../event';
|
||||
import {Feature} from '../feature';
|
||||
import {isUndef, EMPTY_FN} from '../types';
|
||||
import {createElm, removeElm} from '../dom';
|
||||
import {addEvt, cancelEvt, stopEvt, targetEvt, removeEvt} from '../event';
|
||||
import {INPUT, NONE, CHECKLIST, MULTIPLE} from '../const';
|
||||
import {root} from '../root';
|
||||
import {defaultsStr, defaultsBool, defaultsArr, defaultsFn} from '../settings';
|
||||
|
||||
export class PopupFilter{
|
||||
/**
|
||||
* Pop-up filter component
|
||||
* @export
|
||||
* @class PopupFilter
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class PopupFilter extends Feature {
|
||||
|
||||
/**
|
||||
* Pop-up filter component
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of PopupFilter
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
constructor(tf) {
|
||||
super(tf, PopupFilter);
|
||||
|
||||
// Configuration object
|
||||
var f = tf.config();
|
||||
let f = this.config.popup_filters || {};
|
||||
|
||||
// Enable external filters behaviour
|
||||
tf.isExternalFlt = true;
|
||||
tf.externalFltTgtIds = [];
|
||||
/**
|
||||
* Close active popup filter upon filtering, enabled by default
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.closeOnFiltering = defaultsBool(f.close_on_filtering, true);
|
||||
|
||||
//filter icon path
|
||||
this.popUpImgFlt = f.popup_filters_image ||
|
||||
tf.themesPath+'icn_filter.gif';
|
||||
//active filter icon path
|
||||
this.popUpImgFltActive = f.popup_filters_image_active ||
|
||||
tf.themesPath+'icn_filterActive.gif';
|
||||
this.popUpImgFltHtml = f.popup_filters_image_html ||
|
||||
'<img src="'+ this.popUpImgFlt +'" alt="Column filter" />';
|
||||
//defines css class for popup div containing filter
|
||||
this.popUpDivCssClass = f.popup_div_css_class || 'popUpFilter';
|
||||
//callback function before popup filtes is opened
|
||||
this.onBeforePopUpOpen = Types.isFn(f.on_before_popup_filter_open) ?
|
||||
f.on_before_popup_filter_open : null;
|
||||
//callback function after popup filtes is opened
|
||||
this.onAfterPopUpOpen = Types.isFn(f.on_after_popup_filter_open) ?
|
||||
f.on_after_popup_filter_open : null;
|
||||
//callback function before popup filtes is closed
|
||||
this.onBeforePopUpClose =
|
||||
Types.isFn(f.on_before_popup_filter_close) ?
|
||||
f.on_before_popup_filter_close : null;
|
||||
//callback function after popup filtes is closed
|
||||
this.onAfterPopUpClose = Types.isFn(f.on_after_popup_filter_close) ?
|
||||
f.on_after_popup_filter_close : null;
|
||||
/**
|
||||
* Filter icon path
|
||||
* @type {String}
|
||||
*/
|
||||
this.iconPath = defaultsStr(f.image, tf.themesPath + 'icn_filter.gif');
|
||||
|
||||
//stores filters spans
|
||||
this.popUpFltSpans = [];
|
||||
//stores filters icons
|
||||
this.popUpFltImgs = [];
|
||||
//stores filters containers
|
||||
this.popUpFltElms = this.popUpFltElmCache || [];
|
||||
this.popUpFltAdjustToContainer = true;
|
||||
/**
|
||||
* Active filter icon path
|
||||
* @type {string}
|
||||
*/
|
||||
this.activeIconPath = defaultsStr(f.image_active,
|
||||
tf.themesPath + 'icn_filterActive.gif');
|
||||
|
||||
//id prefix for pop-up filter span
|
||||
this.prfxPopUpSpan = 'popUpSpan_';
|
||||
//id prefix for pop-up div containing filter
|
||||
this.prfxPopUpDiv = 'popUpDiv_';
|
||||
/**
|
||||
* HTML for the filter icon
|
||||
* @type {string}
|
||||
*/
|
||||
this.iconHtml = defaultsStr(f.image_html,
|
||||
'<img src="' + this.iconPath + '" alt="Column filter" />');
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Css class assigned to the popup container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.placeholderCssClass = defaultsStr(f.placeholder_css_class,
|
||||
'popUpPlaceholder');
|
||||
|
||||
/**
|
||||
* Css class assigned to filter container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.containerCssClass = defaultsStr(f.div_css_class, 'popUpFilter');
|
||||
|
||||
/**
|
||||
* Ensure filter's container element width matches column width, enabled
|
||||
* by default
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.adjustToContainer = defaultsBool(f.adjust_to_container, true);
|
||||
|
||||
/**
|
||||
* Callback fired before a popup filter is opened
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeOpen = defaultsFn(f.on_before_popup_filter_open, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after a popup filter is opened
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterOpen = defaultsFn(f.on_after_popup_filter_open, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired before a popup filter is closed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeClose = defaultsFn(f.on_before_popup_filter_close,
|
||||
EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after a popup filter is closed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterClose = defaultsFn(f.on_after_popup_filter_close, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Collection of filters spans
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.fltSpans = [];
|
||||
|
||||
/**
|
||||
* Collection of filters icons
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.fltIcons = [];
|
||||
|
||||
/**
|
||||
* Collection of filters icons cached after pop-up filters are removed
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.filtersCache = null;
|
||||
|
||||
/**
|
||||
* Collection of filters containers
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.fltElms = defaultsArr(this.filtersCache, []);
|
||||
|
||||
/**
|
||||
* Prefix for pop-up filter container ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.prfxDiv = 'popup_';
|
||||
|
||||
/**
|
||||
* Column index of popup filter currently active
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
this.activeFilterIdx = -1;
|
||||
}
|
||||
|
||||
onClick(e){
|
||||
var evt = e || global.event,
|
||||
elm = evt.target.parentNode,
|
||||
colIndex = parseInt(elm.getAttribute('ci'), 10);
|
||||
/**
|
||||
* Click event handler for pop-up filter icon
|
||||
* @private
|
||||
*/
|
||||
onClick(evt) {
|
||||
let elm = targetEvt(evt).parentNode;
|
||||
let colIndex = parseInt(elm.getAttribute('ci'), 10);
|
||||
|
||||
this.closeAll(colIndex);
|
||||
this.toggle(colIndex);
|
||||
|
||||
if(this.popUpFltAdjustToContainer){
|
||||
var popUpDiv = this.popUpFltElms[colIndex],
|
||||
if (this.adjustToContainer) {
|
||||
let cont = this.fltElms[colIndex],
|
||||
header = this.tf.getHeaderElement(colIndex),
|
||||
headerWidth = header.clientWidth * 0.95;
|
||||
popUpDiv.style.width = parseInt(headerWidth, 10) + 'px';
|
||||
cont.style.width = parseInt(headerWidth, 10) + 'px';
|
||||
}
|
||||
Event.cancel(evt);
|
||||
Event.stop(evt);
|
||||
cancelEvt(evt);
|
||||
stopEvt(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse-up event handler handling popup filter auto-close behaviour
|
||||
* @private
|
||||
*/
|
||||
onMouseup(evt) {
|
||||
if (this.activeFilterIdx === -1) {
|
||||
return;
|
||||
}
|
||||
let targetElm = targetEvt(evt);
|
||||
let activeFlt = this.fltElms[this.activeFilterIdx];
|
||||
let icon = this.fltIcons[this.activeFilterIdx];
|
||||
|
||||
if (icon === targetElm) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (targetElm && targetElm !== activeFlt) {
|
||||
targetElm = targetElm.parentNode;
|
||||
}
|
||||
|
||||
if (targetElm !== activeFlt) {
|
||||
this.close(this.activeFilterIdx);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize DOM elements
|
||||
*/
|
||||
init(){
|
||||
var tf = this.tf;
|
||||
for(var i=0; i<tf.nbCells; i++){
|
||||
if(tf.getFilterType(i) === tf.fltTypeNone){
|
||||
continue;
|
||||
}
|
||||
var popUpSpan = Dom.create(
|
||||
'span',
|
||||
['id', this.prfxPopUpSpan+tf.id+'_'+i],
|
||||
['ci', i]
|
||||
);
|
||||
popUpSpan.innerHTML = this.popUpImgFltHtml;
|
||||
var header = tf.getHeaderElement(i);
|
||||
header.appendChild(popUpSpan);
|
||||
Event.add(popUpSpan, 'click', (evt) => { this.onClick(evt); });
|
||||
this.popUpFltSpans[i] = popUpSpan;
|
||||
this.popUpFltImgs[i] = popUpSpan.firstChild;
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tf = this.tf;
|
||||
|
||||
// Enable external filters
|
||||
tf.externalFltIds = [''];
|
||||
|
||||
// Override filters row index supplied by configuration
|
||||
tf.filtersRowIndex = 0;
|
||||
|
||||
// Override headers row index if no grouped headers
|
||||
// TODO: Because of the filters row generation, headers row index needs
|
||||
// adjusting: prevent useless row generation
|
||||
if (tf.headersRow <= 1 && isNaN(tf.config().headers_row_index)) {
|
||||
tf.headersRow = 0;
|
||||
}
|
||||
|
||||
// Adjust headers row index for grid-layout mode
|
||||
// TODO: Because of the filters row generation, headers row index needs
|
||||
// adjusting: prevent useless row generation
|
||||
if (tf.gridLayout) {
|
||||
tf.headersRow--;
|
||||
this.buildIcons();
|
||||
}
|
||||
|
||||
// subscribe to events
|
||||
this.emitter.on(['before-filtering'], () => this.setIconsState());
|
||||
this.emitter.on(['after-filtering'], () => this.closeAll());
|
||||
this.emitter.on(['cell-processed'],
|
||||
(tf, cellIndex) => this.changeState(cellIndex, true));
|
||||
this.emitter.on(['filters-row-inserted'], () => this.buildIcons());
|
||||
this.emitter.on(['before-filter-init'],
|
||||
(tf, colIndex) => this.build(colIndex));
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset previously destroyed feature
|
||||
*/
|
||||
reset() {
|
||||
this.enable();
|
||||
this.init();
|
||||
this.buildIcons();
|
||||
this.buildAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all filters icons
|
||||
*/
|
||||
buildIcons() {
|
||||
let tf = this.tf;
|
||||
|
||||
// TODO: Because of the filters row generation, headers row index needs
|
||||
// adjusting: prevent useless row generation
|
||||
tf.headersRow++;
|
||||
|
||||
tf.eachCol(
|
||||
(i) => {
|
||||
let icon = createElm('span', ['ci', i]);
|
||||
icon.innerHTML = this.iconHtml;
|
||||
let header = tf.getHeaderElement(i);
|
||||
header.appendChild(icon);
|
||||
addEvt(icon, 'click', (evt) => this.onClick(evt));
|
||||
this.fltSpans[i] = icon;
|
||||
this.fltIcons[i] = icon.firstChild;
|
||||
},
|
||||
// continue condition function
|
||||
(i) => tf.getFilterType(i) === NONE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all pop-up filters elements
|
||||
*/
|
||||
buildAll(){
|
||||
for(var i=0; i<this.popUpFltElmCache.length; i++){
|
||||
this.build(i, this.popUpFltElmCache[i]);
|
||||
buildAll() {
|
||||
for (let i = 0; i < this.filtersCache.length; i++) {
|
||||
this.build(i, this.filtersCache[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,69 +279,106 @@ export class PopupFilter{
|
|||
* @param {Number} colIndex Column index
|
||||
* @param {Object} div Optional container DOM element
|
||||
*/
|
||||
build(colIndex, div){
|
||||
var tf = this.tf;
|
||||
var popUpDiv = !div ?
|
||||
Dom.create('div', ['id', this.prfxPopUpDiv+tf.id+'_'+colIndex]) :
|
||||
div;
|
||||
popUpDiv.className = this.popUpDivCssClass;
|
||||
tf.externalFltTgtIds.push(popUpDiv.id);
|
||||
var header = tf.getHeaderElement(colIndex);
|
||||
header.insertBefore(popUpDiv, header.firstChild);
|
||||
Event.add(popUpDiv, 'click', (evt) => { Event.stop(evt); });
|
||||
this.popUpFltElms[colIndex] = popUpDiv;
|
||||
build(colIndex, div) {
|
||||
let tf = this.tf;
|
||||
let contId = `${this.prfxDiv}${tf.id}_${colIndex}`;
|
||||
let placeholder = createElm('div', ['class', this.placeholderCssClass]);
|
||||
let cont = div ||
|
||||
createElm('div', ['id', contId], ['class', this.containerCssClass]);
|
||||
tf.externalFltIds[colIndex] = cont.id;
|
||||
placeholder.appendChild(cont);
|
||||
|
||||
let header = tf.getHeaderElement(colIndex);
|
||||
header.insertBefore(placeholder, header.firstChild);
|
||||
addEvt(cont, 'click', (evt) => stopEvt(evt));
|
||||
this.fltElms[colIndex] = cont;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toogle visibility of specified filter
|
||||
* Toggle visibility of specified filter
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
toggle(colIndex){
|
||||
var tf = this.tf,
|
||||
popUpFltElm = this.popUpFltElms[colIndex];
|
||||
|
||||
if(popUpFltElm.style.display === 'none' ||
|
||||
popUpFltElm.style.display === ''){
|
||||
if(this.onBeforePopUpOpen){
|
||||
this.onBeforePopUpOpen.call(
|
||||
null, this, this.popUpFltElms[colIndex], colIndex);
|
||||
}
|
||||
popUpFltElm.style.display = 'block';
|
||||
if(tf.getFilterType(colIndex) === tf.fltTypeInp){
|
||||
var flt = tf.getFilterElement(colIndex);
|
||||
if(flt){
|
||||
flt.focus();
|
||||
}
|
||||
}
|
||||
if(this.onAfterPopUpOpen){
|
||||
this.onAfterPopUpOpen.call(
|
||||
null, this, this.popUpFltElms[colIndex], colIndex);
|
||||
}
|
||||
toggle(colIndex) {
|
||||
if (!this.isOpen(colIndex)) {
|
||||
this.open(colIndex);
|
||||
} else {
|
||||
if(this.onBeforePopUpClose){
|
||||
this.onBeforePopUpClose.call(
|
||||
null, this, this.popUpFltElms[colIndex], colIndex);
|
||||
}
|
||||
popUpFltElm.style.display = 'none';
|
||||
if(this.onAfterPopUpClose){
|
||||
this.onAfterPopUpClose.call(
|
||||
null, this, this.popUpFltElms[colIndex], colIndex);
|
||||
this.close(colIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open popup filter of specified column
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
open(colIndex) {
|
||||
let tf = this.tf,
|
||||
container = this.fltElms[colIndex];
|
||||
|
||||
this.onBeforeOpen(this, container, colIndex);
|
||||
|
||||
container.style.display = 'block';
|
||||
this.activeFilterIdx = colIndex;
|
||||
addEvt(root, 'mouseup', (evt) => this.onMouseup(evt));
|
||||
|
||||
if (tf.getFilterType(colIndex) === INPUT) {
|
||||
let flt = tf.getFilterElement(colIndex);
|
||||
if (flt) {
|
||||
flt.focus();
|
||||
}
|
||||
}
|
||||
|
||||
this.onAfterOpen(this, container, colIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close popup filter of specified column
|
||||
* @param {Number} colIndex Column index
|
||||
*/
|
||||
close(colIndex) {
|
||||
let container = this.fltElms[colIndex];
|
||||
|
||||
this.onBeforeClose(this, container, colIndex);
|
||||
|
||||
container.style.display = NONE;
|
||||
if (this.activeFilterIdx === colIndex) {
|
||||
this.activeFilterIdx = -1;
|
||||
}
|
||||
removeEvt(root, 'mouseup', (evt) => this.onMouseup(evt));
|
||||
|
||||
this.onAfterClose(this, container, colIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if popup filter for specified column is open
|
||||
* @param {Number} colIndex Column index
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isOpen(colIndex) {
|
||||
return this.fltElms[colIndex].style.display === 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all filters excepted for the specified one if any
|
||||
* @param {Number} exceptIdx Column index of the filter to not close
|
||||
*/
|
||||
closeAll(exceptIdx){
|
||||
for(var i=0; i<this.popUpFltElms.length; i++){
|
||||
if(i === exceptIdx){
|
||||
closeAll(exceptIdx) {
|
||||
// Do not close filters only if argument is undefined and close on
|
||||
// filtering option is disabled
|
||||
if (isUndef(exceptIdx) && !this.closeOnFiltering) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.fltElms.length; i++) {
|
||||
if (i === exceptIdx) {
|
||||
continue;
|
||||
}
|
||||
var popUpFltElm = this.popUpFltElms[i];
|
||||
if(popUpFltElm){
|
||||
popUpFltElm.style.display = 'none';
|
||||
let fltType = this.tf.getFilterType(i);
|
||||
let isMultipleFilter =
|
||||
(fltType === CHECKLIST || fltType === MULTIPLE);
|
||||
|
||||
// Always hide all single selection filter types but hide multiple
|
||||
// selection filter types only if index set
|
||||
if (!isMultipleFilter || !isUndef(exceptIdx)) {
|
||||
this.close(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,50 +386,76 @@ export class PopupFilter{
|
|||
/**
|
||||
* Build all the icons representing the pop-up filters
|
||||
*/
|
||||
buildIcons(){
|
||||
for(var i=0; i<this.popUpFltImgs.length; i++){
|
||||
this.buildIcon(i, false);
|
||||
setIconsState() {
|
||||
for (let i = 0; i < this.fltIcons.length; i++) {
|
||||
this.changeState(i, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build specified icon
|
||||
* Apply specified icon state
|
||||
* @param {Number} colIndex Column index
|
||||
* @param {Boolean} active Apply active state
|
||||
*/
|
||||
buildIcon(colIndex, active){
|
||||
if(this.popUpFltImgs[colIndex]){
|
||||
this.popUpFltImgs[colIndex].src = active ?
|
||||
this.popUpImgFltActive : this.popUpImgFlt;
|
||||
changeState(colIndex, active) {
|
||||
let icon = this.fltIcons[colIndex];
|
||||
if (icon) {
|
||||
icon.src = active ? this.activeIconPath : this.iconPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove pop-up filters
|
||||
*/
|
||||
destroy(){
|
||||
this.popUpFltElmCache = [];
|
||||
for(var i=0; i<this.popUpFltElms.length; i++){
|
||||
var popUpFltElm = this.popUpFltElms[i],
|
||||
popUpFltSpan = this.popUpFltSpans[i],
|
||||
popUpFltImg = this.popUpFltImgs[i];
|
||||
if(popUpFltElm){
|
||||
popUpFltElm.parentNode.removeChild(popUpFltElm);
|
||||
this.popUpFltElmCache[i] = popUpFltElm;
|
||||
}
|
||||
popUpFltElm = null;
|
||||
if(popUpFltSpan){
|
||||
popUpFltSpan.parentNode.removeChild(popUpFltSpan);
|
||||
}
|
||||
popUpFltSpan = null;
|
||||
if(popUpFltImg){
|
||||
popUpFltImg.parentNode.removeChild(popUpFltImg);
|
||||
}
|
||||
popUpFltImg = null;
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.popUpFltElms = [];
|
||||
this.popUpFltSpans = [];
|
||||
this.popUpFltImgs = [];
|
||||
|
||||
this.filtersCache = [];
|
||||
for (let i = 0; i < this.fltElms.length; i++) {
|
||||
let container = this.fltElms[i],
|
||||
placeholder = container.parentNode,
|
||||
icon = this.fltSpans[i],
|
||||
iconImg = this.fltIcons[i];
|
||||
if (container) {
|
||||
removeElm(container);
|
||||
this.filtersCache[i] = container;
|
||||
}
|
||||
container = null;
|
||||
if (placeholder) {
|
||||
removeElm(placeholder);
|
||||
}
|
||||
placeholder = null;
|
||||
if (icon) {
|
||||
removeElm(icon);
|
||||
}
|
||||
icon = null;
|
||||
if (iconImg) {
|
||||
removeElm(iconImg);
|
||||
}
|
||||
iconImg = null;
|
||||
}
|
||||
this.fltElms = [];
|
||||
this.fltSpans = [];
|
||||
this.fltIcons = [];
|
||||
|
||||
// TODO: expose an API to handle external filter IDs
|
||||
this.tf.externalFltIds = [];
|
||||
|
||||
// unsubscribe to events
|
||||
this.emitter.off(['before-filtering'], () => this.setIconsState());
|
||||
this.emitter.off(['after-filtering'], () => this.closeAll());
|
||||
this.emitter.off(['cell-processed'],
|
||||
(tf, cellIndex) => this.changeState(cellIndex, true));
|
||||
this.emitter.off(['filters-row-inserted'], () => this.buildIcons());
|
||||
this.emitter.off(['before-filter-init'],
|
||||
(tf, colIndex) => this.build(colIndex));
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: remove as soon as feature name is fixed
|
||||
PopupFilter.meta = {altName: 'popupFilters'};
|
||||
|
|
|
@ -1,135 +1,207 @@
|
|||
import Dom from '../dom';
|
||||
import Types from '../types';
|
||||
import {Feature} from '../feature';
|
||||
import {createElm, createText, elm, removeElm} from '../dom';
|
||||
import {EMPTY_FN, isNull} from '../types';
|
||||
import {defaultsStr, defaultsFn} from '../settings';
|
||||
import {LEFT} from './toolbar';
|
||||
|
||||
export class RowsCounter{
|
||||
/**
|
||||
* Rows counter UI component
|
||||
* @export
|
||||
* @class RowsCounter
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class RowsCounter extends Feature {
|
||||
|
||||
/**
|
||||
* Rows counter
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of RowsCounter
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
constructor(tf) {
|
||||
super(tf, RowsCounter);
|
||||
|
||||
// TableFilter configuration
|
||||
var f = tf.config();
|
||||
let f = this.config.rows_counter || {};
|
||||
|
||||
//id of custom container element
|
||||
this.rowsCounterTgtId = f.rows_counter_target_id || null;
|
||||
//element containing tot nb rows
|
||||
this.rowsCounterDiv = null;
|
||||
//element containing tot nb rows label
|
||||
this.rowsCounterSpan = null;
|
||||
//defines rows counter text
|
||||
this.rowsCounterText = f.rows_counter_text || 'Rows: ';
|
||||
this.fromToTextSeparator = f.from_to_text_separator || '-';
|
||||
this.overText = f.over_text || ' / ';
|
||||
//defines css class rows counter
|
||||
this.totRowsCssClass = f.tot_rows_css_class || 'tot';
|
||||
//rows counter div
|
||||
this.prfxCounter = 'counter_';
|
||||
//nb displayed rows label
|
||||
this.prfxTotRows = 'totrows_span_';
|
||||
//label preceding nb rows label
|
||||
this.prfxTotRowsTxt = 'totRowsTextSpan_';
|
||||
//callback raised before counter is refreshed
|
||||
this.onBeforeRefreshCounter = Types.isFn(f.on_before_refresh_counter) ?
|
||||
f.on_before_refresh_counter : null;
|
||||
//callback raised after counter is refreshed
|
||||
this.onAfterRefreshCounter = Types.isFn(f.on_after_refresh_counter) ?
|
||||
f.on_after_refresh_counter : null;
|
||||
/**
|
||||
* ID of custom container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.targetId = defaultsStr(f.target_id, null);
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Container DOM element
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.container = null;
|
||||
|
||||
/**
|
||||
* Container DOM element for label displaying the total number of rows
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.label = null;
|
||||
|
||||
/**
|
||||
* Text preceding the total number of rows
|
||||
* @type {String}
|
||||
*/
|
||||
this.text = defaultsStr(f.text, 'Rows: ');
|
||||
|
||||
/**
|
||||
* Separator symbol appearing between the first and last visible rows of
|
||||
* current page when paging is enabled. ie: Rows: 31-40 / 70
|
||||
* @type {String}
|
||||
*/
|
||||
this.fromToTextSeparator = defaultsStr(f.separator, '-');
|
||||
|
||||
/**
|
||||
* Separator symbol appearing between the first and last visible rows of
|
||||
* current page and the total number of filterable rows when paging is
|
||||
* enabled. ie: Rows: 31-40 / 70
|
||||
* @type {String}
|
||||
*/
|
||||
this.overText = defaultsStr(f.over_text, ' / ');
|
||||
|
||||
/**
|
||||
* Css class for container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.cssClass = defaultsStr(f.css_class, 'tot');
|
||||
|
||||
/**
|
||||
* Default position in toolbar ('left'|'center'|'right')
|
||||
* @type {String}
|
||||
*/
|
||||
this.toolbarPosition = defaultsStr(f.toolbar_position, LEFT);
|
||||
|
||||
/**
|
||||
* Callback fired before the counter is refreshed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeRefreshCounter = defaultsFn(f.on_before_refresh_counter,
|
||||
EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after the counter is refreshed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterRefreshCounter = defaultsFn(f.on_after_refresh_counter,
|
||||
EMPTY_FN);
|
||||
}
|
||||
|
||||
init(){
|
||||
var tf = this.tf;
|
||||
|
||||
if((!tf.hasGrid() && !tf.isFirstLoad) || this.rowsCounterSpan){
|
||||
/**
|
||||
* Initializes RowsCounter instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.emit('initializing-feature', this, !isNull(this.targetId));
|
||||
|
||||
let tf = this.tf;
|
||||
|
||||
//rows counter container
|
||||
var countDiv = Dom.create('div', ['id', this.prfxCounter+tf.id]);
|
||||
countDiv.className = this.totRowsCssClass;
|
||||
let countDiv = createElm('div');
|
||||
countDiv.className = this.cssClass;
|
||||
//rows counter label
|
||||
var countSpan = Dom.create('span', ['id', this.prfxTotRows+tf.id]);
|
||||
var countText = Dom.create('span', ['id', this.prfxTotRowsTxt+tf.id]);
|
||||
countText.appendChild(Dom.text(this.rowsCounterText));
|
||||
let countSpan = createElm('span');
|
||||
let countText = createElm('span');
|
||||
countText.appendChild(createText(this.text));
|
||||
|
||||
// counter is added to defined element
|
||||
if(!this.rowsCounterTgtId){
|
||||
tf.setToolbar();
|
||||
}
|
||||
var targetEl = !this.rowsCounterTgtId ?
|
||||
tf.lDiv : Dom.id( this.rowsCounterTgtId );
|
||||
let targetEl = !this.targetId ?
|
||||
tf.feature('toolbar').container(this.toolbarPosition) :
|
||||
elm(this.targetId);
|
||||
|
||||
//default container: 'lDiv'
|
||||
if(!this.rowsCounterTgtId){
|
||||
if (!this.targetId) {
|
||||
countDiv.appendChild(countText);
|
||||
countDiv.appendChild(countSpan);
|
||||
targetEl.appendChild(countDiv);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
//custom container, no need to append statusDiv
|
||||
targetEl.appendChild(countText);
|
||||
targetEl.appendChild(countSpan);
|
||||
}
|
||||
this.rowsCounterDiv = countDiv;
|
||||
this.rowsCounterSpan = countSpan;
|
||||
this.container = countDiv;
|
||||
this.label = countSpan;
|
||||
|
||||
// subscribe to events
|
||||
this.emitter.on(['after-filtering', 'grouped-by-page'],
|
||||
() => this.refresh(tf.getValidRowsNb()));
|
||||
this.emitter.on(['rows-changed'], () => this.refresh());
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
this.refresh();
|
||||
|
||||
this.emitter.emit('feature-initialized', this);
|
||||
}
|
||||
|
||||
refresh(p){
|
||||
if(!this.rowsCounterSpan){
|
||||
/**
|
||||
* Refreshes the rows counter
|
||||
* @param {Number} p Optional parameter the total number of rows to display
|
||||
*/
|
||||
refresh(p) {
|
||||
if (!this.initialized || !this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tf = this.tf;
|
||||
let tf = this.tf;
|
||||
|
||||
if(this.onBeforeRefreshCounter){
|
||||
this.onBeforeRefreshCounter.call(null, tf, this.rowsCounterSpan);
|
||||
}
|
||||
this.onBeforeRefreshCounter(tf, this.label);
|
||||
|
||||
var totTxt;
|
||||
if(!tf.paging){
|
||||
if(p && p !== ''){
|
||||
let totTxt;
|
||||
if (!tf.paging) {
|
||||
if (p && p !== '') {
|
||||
totTxt = p;
|
||||
} else{
|
||||
totTxt = tf.nbFilterableRows - tf.nbHiddenRows;
|
||||
} else {
|
||||
totTxt = tf.getFilterableRowsNb() - tf.nbHiddenRows;
|
||||
}
|
||||
} else {
|
||||
var paging = tf.feature('paging');
|
||||
if(paging){
|
||||
let paging = tf.feature('paging');
|
||||
if (paging) {
|
||||
let nbValidRows = tf.getValidRowsNb();
|
||||
//paging start row
|
||||
var paging_start_row = parseInt(paging.startPagingRow, 10) +
|
||||
((tf.nbVisibleRows>0) ? 1 : 0);
|
||||
var paging_end_row = (paging_start_row+paging.pagingLength)-1 <=
|
||||
tf.nbVisibleRows ?
|
||||
paging_start_row+paging.pagingLength-1 :
|
||||
tf.nbVisibleRows;
|
||||
totTxt = paging_start_row + this.fromToTextSeparator +
|
||||
paging_end_row + this.overText + tf.nbVisibleRows;
|
||||
let pagingStartRow = parseInt(paging.startPagingRow, 10) +
|
||||
((nbValidRows > 0) ? 1 : 0);
|
||||
let pagingEndRow =
|
||||
(pagingStartRow + paging.pageLength) - 1 <=
|
||||
nbValidRows ?
|
||||
pagingStartRow + paging.pageLength - 1 :
|
||||
nbValidRows;
|
||||
totTxt = pagingStartRow + this.fromToTextSeparator +
|
||||
pagingEndRow + this.overText + nbValidRows;
|
||||
}
|
||||
}
|
||||
|
||||
this.rowsCounterSpan.innerHTML = totTxt;
|
||||
if(this.onAfterRefreshCounter){
|
||||
this.onAfterRefreshCounter.call(
|
||||
null, tf, this.rowsCounterSpan, totTxt);
|
||||
}
|
||||
this.label.innerHTML = totTxt;
|
||||
this.onAfterRefreshCounter(tf, this.label, totTxt);
|
||||
}
|
||||
|
||||
destroy(){
|
||||
var tf = this.tf;
|
||||
if(!tf.hasGrid() || !this.rowsCounterSpan){
|
||||
/**
|
||||
* Remove feature
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.rowsCounterTgtId && this.rowsCounterDiv){
|
||||
this.rowsCounterDiv.parentNode.removeChild(this.rowsCounterDiv);
|
||||
if (!this.targetId && this.container) {
|
||||
removeElm(this.container);
|
||||
} else {
|
||||
Dom.id(this.rowsCounterTgtId).innerHTML = '';
|
||||
elm(this.targetId).innerHTML = '';
|
||||
}
|
||||
this.rowsCounterSpan = null;
|
||||
this.rowsCounterDiv = null;
|
||||
this.label = null;
|
||||
this.container = null;
|
||||
|
||||
// unsubscribe to events
|
||||
this.emitter.off(['after-filtering', 'grouped-by-page'],
|
||||
() => this.refresh(tf.getValidRowsNb()));
|
||||
this.emitter.off(['rows-changed'], () => this.refresh());
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
|
545
src/modules/state.js
Normal file
|
@ -0,0 +1,545 @@
|
|||
import {Feature} from '../feature';
|
||||
import {Hash} from './hash';
|
||||
import {Storage} from './storage';
|
||||
import {isEmpty} from '../string';
|
||||
import {isArray, isNull, isString, isUndef} from '../types';
|
||||
import {defaultsBool, defaultsNb} from '../settings';
|
||||
|
||||
/**
|
||||
* Features state object persistable with localStorage, cookie or URL hash
|
||||
*
|
||||
* @export
|
||||
* @class State
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class State extends Feature {
|
||||
|
||||
/**
|
||||
* Creates an instance of State
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf) {
|
||||
super(tf, State);
|
||||
|
||||
let cfg = this.config.state || {};
|
||||
|
||||
/**
|
||||
* Determines whether state is persisted with URL hash
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableHash = cfg === true ||
|
||||
(isArray(cfg.types) && cfg.types.indexOf('hash') !== -1);
|
||||
|
||||
/**
|
||||
* Determines whether state is persisted with localStorage
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableLocalStorage = isArray(cfg.types) &&
|
||||
cfg.types.indexOf('local_storage') !== -1;
|
||||
|
||||
/**
|
||||
* Determines whether state is persisted with localStorage
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enableCookie = isArray(cfg.types) &&
|
||||
cfg.types.indexOf('cookie') !== -1;
|
||||
|
||||
/**
|
||||
* Persist filters values, enabled by default
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.persistFilters = defaultsBool(cfg.filters, true);
|
||||
|
||||
/**
|
||||
* Persist current page number when paging is enabled
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.persistPageNumber = Boolean(cfg.page_number);
|
||||
|
||||
/**
|
||||
* Persist page length when paging is enabled
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.persistPageLength = Boolean(cfg.page_length);
|
||||
|
||||
/**
|
||||
* Persist column sorting
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.persistSort = Boolean(cfg.sort);
|
||||
|
||||
/**
|
||||
* Persist columns visibility
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.persistColsVisibility = Boolean(cfg.columns_visibility);
|
||||
|
||||
/**
|
||||
* Persist filters row visibility
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.persistFiltersVisibility = Boolean(cfg.filters_visibility);
|
||||
|
||||
/**
|
||||
* Cookie duration in hours
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.cookieDuration = defaultsNb(parseInt(cfg.cookie_duration, 10),
|
||||
87600);
|
||||
|
||||
/**
|
||||
* Enable Storage if localStorage or cookie is required
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.enableStorage = this.enableLocalStorage || this.enableCookie;
|
||||
|
||||
/**
|
||||
* Storage instance if storage is required
|
||||
* @type {Storage}
|
||||
* @private
|
||||
*/
|
||||
this.storage = null;
|
||||
|
||||
/**
|
||||
* Hash instance if URL hash is required
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hash = null;
|
||||
|
||||
/**
|
||||
* Current page number
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
this.pageNb = null;
|
||||
|
||||
/**
|
||||
* Current page length
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
this.pageLength = null;
|
||||
|
||||
/**
|
||||
* Current column sorting
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.sort = null;
|
||||
|
||||
/**
|
||||
* Current hidden columns
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.hiddenCols = null;
|
||||
|
||||
/**
|
||||
* Filters row visibility
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.filtersVisibility = null;
|
||||
|
||||
/**
|
||||
* State object
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.state = {};
|
||||
|
||||
/**
|
||||
* Prefix for column ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.prfxCol = 'col_';
|
||||
|
||||
/**
|
||||
* Prefix for page number ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.pageNbKey = 'page';
|
||||
|
||||
/**
|
||||
* Prefix for page length ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.pageLengthKey = 'page_length';
|
||||
|
||||
/**
|
||||
* Prefix for filters visibility ID
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
this.filtersVisKey = 'filters_visibility';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes State instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.on(['after-filtering'], () => this.update());
|
||||
this.emitter.on(['after-page-change', 'after-clearing-filters'],
|
||||
(tf, pageNb) => this.updatePage(pageNb));
|
||||
this.emitter.on(['after-page-length-change'],
|
||||
(tf, pageLength) => this.updatePageLength(pageLength));
|
||||
this.emitter.on(['column-sorted'],
|
||||
(tf, index, descending) => this.updateSort(index, descending));
|
||||
this.emitter.on(['sort-initialized'], () => this._syncSort());
|
||||
this.emitter.on(['columns-visibility-initialized'],
|
||||
() => this._syncColsVisibility());
|
||||
this.emitter.on(['column-shown', 'column-hidden'], (tf, feature,
|
||||
colIndex, hiddenCols) => this.updateColsVisibility(hiddenCols));
|
||||
this.emitter.on(['filters-visibility-initialized'],
|
||||
() => this._syncFiltersVisibility());
|
||||
this.emitter.on(['filters-toggled'],
|
||||
(tf, extension, visible) => this.updateFiltersVisibility(visible));
|
||||
|
||||
if (this.enableHash) {
|
||||
this.hash = new Hash(this);
|
||||
this.hash.init();
|
||||
}
|
||||
if (this.enableStorage) {
|
||||
this.storage = new Storage(this);
|
||||
this.storage.init();
|
||||
}
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update state object based on current features state
|
||||
*/
|
||||
update() {
|
||||
if (!this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
let state = this.state;
|
||||
let tf = this.tf;
|
||||
|
||||
if (this.persistFilters) {
|
||||
let filterValues = tf.getFiltersValue();
|
||||
|
||||
filterValues.forEach((val, idx) => {
|
||||
let key = `${this.prfxCol}${idx}`;
|
||||
|
||||
if (isString(val) && isEmpty(val)) {
|
||||
if (state.hasOwnProperty(key)) {
|
||||
state[key].flt = undefined;
|
||||
}
|
||||
} else {
|
||||
state[key] = state[key] || {};
|
||||
state[key].flt = val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.persistPageNumber) {
|
||||
if (isNull(this.pageNb)) {
|
||||
state[this.pageNbKey] = undefined;
|
||||
} else {
|
||||
state[this.pageNbKey] = this.pageNb;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.persistPageLength) {
|
||||
if (isNull(this.pageLength)) {
|
||||
state[this.pageLengthKey] = undefined;
|
||||
} else {
|
||||
state[this.pageLengthKey] = this.pageLength;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.persistSort) {
|
||||
if (!isNull(this.sort)) {
|
||||
// Remove previuosly sorted column
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.indexOf(this.prfxCol) !== -1 && state[key]) {
|
||||
state[key].sort = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
let key = `${this.prfxCol}${this.sort.column}`;
|
||||
state[key] = state[key] || {};
|
||||
state[key].sort = { descending: this.sort.descending };
|
||||
}
|
||||
}
|
||||
|
||||
if (this.persistColsVisibility) {
|
||||
if (!isNull(this.hiddenCols)) {
|
||||
// Clear previuosly hidden columns
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.indexOf(this.prfxCol) !== -1 && state[key]) {
|
||||
state[key].hidden = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
this.hiddenCols.forEach((colIdx) => {
|
||||
let key = `${this.prfxCol}${colIdx}`;
|
||||
state[key] = state[key] || {};
|
||||
state[key].hidden = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.persistFiltersVisibility) {
|
||||
if (isNull(this.filtersVisibility)) {
|
||||
state[this.filtersVisKey] = undefined;
|
||||
} else {
|
||||
state[this.filtersVisKey] = this.filtersVisibility;
|
||||
}
|
||||
}
|
||||
|
||||
this.emitter.emit('state-changed', tf, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh page number field on page number changes
|
||||
*
|
||||
* @param {Number} pageNb Current page number
|
||||
*/
|
||||
updatePage(pageNb) {
|
||||
this.pageNb = pageNb;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh page length field on page length changes
|
||||
*
|
||||
* @param {Number} pageLength Current page length value
|
||||
*/
|
||||
updatePageLength(pageLength) {
|
||||
this.pageLength = pageLength;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh column sorting information on sort changes
|
||||
*
|
||||
* @param index {Number} Column index
|
||||
* @param {Boolean} descending Descending manner
|
||||
*/
|
||||
updateSort(index, descending) {
|
||||
this.sort = {
|
||||
column: index,
|
||||
descending: descending
|
||||
};
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh hidden columns information on columns visibility changes
|
||||
*
|
||||
* @param {Array} hiddenCols Columns indexes
|
||||
*/
|
||||
updateColsVisibility(hiddenCols) {
|
||||
this.hiddenCols = hiddenCols;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh filters visibility on filters visibility change
|
||||
*
|
||||
* @param {Boolean} visible Visibility flad
|
||||
*/
|
||||
updateFiltersVisibility(visible) {
|
||||
this.filtersVisibility = visible;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override state field
|
||||
*
|
||||
* @param state State object
|
||||
*/
|
||||
override(state) {
|
||||
this.state = state;
|
||||
this.emitter.emit('state-changed', this.tf, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync stored features state
|
||||
*/
|
||||
sync() {
|
||||
let state = this.state;
|
||||
let tf = this.tf;
|
||||
|
||||
this._syncFilters();
|
||||
|
||||
if (this.persistPageNumber) {
|
||||
let pageNumber = state[this.pageNbKey];
|
||||
this.emitter.emit('change-page', tf, pageNumber);
|
||||
}
|
||||
|
||||
if (this.persistPageLength) {
|
||||
let pageLength = state[this.pageLengthKey];
|
||||
this.emitter.emit('change-page-results', tf, pageLength);
|
||||
}
|
||||
|
||||
this._syncSort();
|
||||
this._syncColsVisibility();
|
||||
this._syncFiltersVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override current state with passed one and sync features
|
||||
*
|
||||
* @param {Object} state State object
|
||||
*/
|
||||
overrideAndSync(state) {
|
||||
// To prevent state to react to features changes, state is temporarily
|
||||
// disabled
|
||||
this.disable();
|
||||
// State is overriden with passed state object
|
||||
this.override(state);
|
||||
// New hash state is applied to features
|
||||
this.sync();
|
||||
// State is re-enabled
|
||||
this.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync filters with stored values and filter table
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_syncFilters() {
|
||||
if (!this.persistFilters) {
|
||||
return;
|
||||
}
|
||||
let state = this.state;
|
||||
let tf = this.tf;
|
||||
|
||||
// clear all filters
|
||||
// TODO: use tf.clearFilters() once it allows to not filter the table
|
||||
tf.eachCol((colIdx) => tf.setFilterValue(colIdx, ''));
|
||||
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.indexOf(this.prfxCol) !== -1) {
|
||||
let colIdx = parseInt(key.replace(this.prfxCol, ''), 10);
|
||||
let val = state[key].flt;
|
||||
tf.setFilterValue(colIdx, val);
|
||||
}
|
||||
});
|
||||
|
||||
tf.filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync sorted column with stored sorting information and sort table
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_syncSort() {
|
||||
if (!this.persistSort) {
|
||||
return;
|
||||
}
|
||||
let state = this.state;
|
||||
let tf = this.tf;
|
||||
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.indexOf(this.prfxCol) !== -1) {
|
||||
let colIdx = parseInt(key.replace(this.prfxCol, ''), 10);
|
||||
if (!isUndef(state[key].sort)) {
|
||||
let sort = state[key].sort;
|
||||
this.emitter.emit('sort', tf, colIdx, sort.descending);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync hidden columns with stored information
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_syncColsVisibility() {
|
||||
if (!this.persistColsVisibility) {
|
||||
return;
|
||||
}
|
||||
let state = this.state;
|
||||
let tf = this.tf;
|
||||
let hiddenCols = [];
|
||||
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.indexOf(this.prfxCol) !== -1) {
|
||||
let colIdx = parseInt(key.replace(this.prfxCol, ''), 10);
|
||||
if (!isUndef(state[key].hidden)) {
|
||||
hiddenCols.push(colIdx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hiddenCols.forEach((colIdx) => {
|
||||
this.emitter.emit('hide-column', tf, colIdx);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync filters visibility with stored information
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_syncFiltersVisibility() {
|
||||
if (!this.persistFiltersVisibility) {
|
||||
return;
|
||||
}
|
||||
let state = this.state;
|
||||
let tf = this.tf;
|
||||
let filtersVisibility = state[this.filtersVisKey];
|
||||
|
||||
this.filtersVisibility = filtersVisibility;
|
||||
this.emitter.emit('show-filters', tf, filtersVisibility);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy State instance
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = {};
|
||||
|
||||
this.emitter.off(['after-filtering'], () => this.update());
|
||||
this.emitter.off(['after-page-change', 'after-clearing-filters'],
|
||||
(tf, pageNb) => this.updatePage(pageNb));
|
||||
this.emitter.off(['after-page-length-change'],
|
||||
(tf, index) => this.updatePageLength(index));
|
||||
this.emitter.off(['column-sorted'],
|
||||
(tf, index, descending) => this.updateSort(index, descending));
|
||||
this.emitter.off(['sort-initialized'], () => this._syncSort());
|
||||
this.emitter.off(['columns-visibility-initialized'],
|
||||
() => this._syncColsVisibility());
|
||||
this.emitter.off(['column-shown', 'column-hidden'], (tf, feature,
|
||||
colIndex, hiddenCols) => this.updateColsVisibility(hiddenCols));
|
||||
this.emitter.off(['filters-visibility-initialized'],
|
||||
() => this._syncFiltersVisibility());
|
||||
this.emitter.off(['filters-toggled'],
|
||||
(tf, extension, visible) => this.updateFiltersVisibility(visible));
|
||||
|
||||
if (this.enableHash) {
|
||||
this.hash.destroy();
|
||||
this.hash = null;
|
||||
}
|
||||
|
||||
if (this.enableStorage) {
|
||||
this.storage.destroy();
|
||||
this.storage = null;
|
||||
}
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
|
@ -1,76 +1,207 @@
|
|||
import Dom from '../dom';
|
||||
import Types from '../types';
|
||||
import {Feature} from '../feature';
|
||||
import {root} from '../root';
|
||||
import {createElm, createText, elm, removeElm} from '../dom';
|
||||
import {EMPTY_FN, isNull} from '../types';
|
||||
import {defaultsStr, defaultsFn} from '../settings';
|
||||
import {LEFT} from './toolbar';
|
||||
|
||||
var global = window;
|
||||
const EVENTS = [
|
||||
'after-filtering',
|
||||
'after-populating-filter',
|
||||
'after-page-change',
|
||||
'after-clearing-filters',
|
||||
'after-page-length-change',
|
||||
'after-reset-page',
|
||||
'after-reset-page-length',
|
||||
'after-loading-extensions',
|
||||
'after-loading-themes'
|
||||
];
|
||||
|
||||
export class StatusBar{
|
||||
/**
|
||||
* Status bar UI component
|
||||
* @export
|
||||
* @class StatusBar
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class StatusBar extends Feature {
|
||||
|
||||
/**
|
||||
* Status bar UI component
|
||||
* @param {Object} tf TableFilter instance
|
||||
* Creates an instance of StatusBar
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
*/
|
||||
constructor(tf){
|
||||
constructor(tf) {
|
||||
super(tf, StatusBar);
|
||||
|
||||
// Configuration object
|
||||
var f = tf.config();
|
||||
let f = this.config.status_bar || {};
|
||||
|
||||
//id of custom container element
|
||||
this.statusBarTgtId = f.status_bar_target_id || null;
|
||||
//element containing status bar label
|
||||
this.statusBarDiv = null;
|
||||
//status bar
|
||||
this.statusBarSpan = null;
|
||||
//status bar label
|
||||
this.statusBarSpanText = null;
|
||||
//defines status bar text
|
||||
this.statusBarText = f.status_bar_text || '';
|
||||
//defines css class status bar
|
||||
this.statusBarCssClass = f.status_bar_css_class || 'status';
|
||||
//delay for status bar clearing
|
||||
this.statusBarCloseDelay = 250;
|
||||
/**
|
||||
* ID of custom container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.targetId = defaultsStr(f.target_id, null);
|
||||
|
||||
//calls function before message is displayed
|
||||
this.onBeforeShowMsg = Types.isFn(f.on_before_show_msg) ?
|
||||
f.on_before_show_msg : null;
|
||||
//calls function after message is displayed
|
||||
this.onAfterShowMsg = Types.isFn(f.on_after_show_msg) ?
|
||||
f.on_after_show_msg : null;
|
||||
/**
|
||||
* Container DOM element
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.container = null;
|
||||
|
||||
// status bar div
|
||||
this.prfxStatus = 'status_';
|
||||
// status bar label
|
||||
this.prfxStatusSpan = 'statusSpan_';
|
||||
// text preceding status bar label
|
||||
this.prfxStatusTxt = 'statusText_';
|
||||
/**
|
||||
* Message container DOM element
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.msgContainer = null;
|
||||
|
||||
this.tf = tf;
|
||||
/**
|
||||
* Label container DOM element
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.labelContainer = null;
|
||||
|
||||
/**
|
||||
* Text preceding status message
|
||||
* @type {String}
|
||||
*/
|
||||
this.text = defaultsStr(f.text, '');
|
||||
|
||||
/**
|
||||
* Css class for container element
|
||||
* @type {String}
|
||||
*/
|
||||
this.cssClass = defaultsStr(f.css_class, 'status');
|
||||
|
||||
/**
|
||||
* Message visibility duration in milliseconds
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
this.delay = 250;
|
||||
|
||||
/**
|
||||
* Callback fired before the message is displayed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onBeforeShowMsg = defaultsFn(f.on_before_show_msg, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Callback fired after the message is displayed
|
||||
* @type {Function}
|
||||
*/
|
||||
this.onAfterShowMsg = defaultsFn(f.on_after_show_msg, EMPTY_FN);
|
||||
|
||||
/**
|
||||
* Message appearing upon filtering
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgFilter = defaultsStr(f.msg_filter, 'Filtering data...');
|
||||
|
||||
/**
|
||||
* Message appearing when a drop-down filter is populated
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgPopulate = defaultsStr(f.msg_populate, 'Populating filter...');
|
||||
|
||||
/**
|
||||
* Message appearing when a checklist filter is populated
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgPopulateCheckList = defaultsStr(f.msg_populate_checklist,
|
||||
'Populating list...');
|
||||
|
||||
/**
|
||||
* Message appearing when a pagination page is changed
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgChangePage = defaultsStr(f.msg_change_page,
|
||||
'Collecting paging data...');
|
||||
|
||||
/**
|
||||
* Message appearing when filters are cleared
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgClear = defaultsStr(f.msg_clear, 'Clearing filters...');
|
||||
|
||||
/**
|
||||
* Message appearing when the page length is changed
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgChangeResults = defaultsStr(f.msg_change_results,
|
||||
'Changing results per page...');
|
||||
|
||||
/**
|
||||
* Message appearing when the page is re-set
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgResetPage = defaultsStr(f.msg_reset_page, 'Re-setting page...');
|
||||
|
||||
/**
|
||||
* Message appearing when the page length is re-set
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgResetPageLength = defaultsStr(f.msg_reset_page_length,
|
||||
'Re-setting page length...');
|
||||
|
||||
/**
|
||||
* Message appearing upon column sorting
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgSort = defaultsStr(f.msg_sort, 'Sorting data...');
|
||||
|
||||
/**
|
||||
* Message appearing when extensions are loading
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgLoadExtensions = defaultsStr(f.msg_load_extensions,
|
||||
'Loading extensions...');
|
||||
|
||||
/**
|
||||
* Message appearing when themes are loading
|
||||
* @type {String}
|
||||
*/
|
||||
this.msgLoadThemes = defaultsStr(f.msg_load_themes,
|
||||
'Loading theme(s)...');
|
||||
|
||||
/**
|
||||
* Default position in toolbar ('left'|'center'|'right')
|
||||
* @type {String}
|
||||
*/
|
||||
this.toolbarPosition = defaultsStr(f.toolbar_position, LEFT);
|
||||
}
|
||||
|
||||
init(){
|
||||
var tf = this.tf;
|
||||
if(!tf.hasGrid() && !tf.isFirstLoad){
|
||||
/**
|
||||
* Initializes StatusBar instance
|
||||
*/
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
//status bar container
|
||||
var statusDiv = Dom.create('div', ['id', this.prfxStatus+tf.id]);
|
||||
statusDiv.className = this.statusBarCssClass;
|
||||
let tf = this.tf;
|
||||
let emitter = this.emitter;
|
||||
|
||||
//status bar label
|
||||
var statusSpan = Dom.create('span', ['id', this.prfxStatusSpan+tf.id]);
|
||||
//preceding text
|
||||
var statusSpanText = Dom.create('span',
|
||||
['id', this.prfxStatusTxt+tf.id]);
|
||||
statusSpanText.appendChild(Dom.text(this.statusBarText));
|
||||
emitter.emit('initializing-feature', this, !isNull(this.targetId));
|
||||
|
||||
// status bar container
|
||||
let statusDiv = createElm('div');
|
||||
statusDiv.className = this.cssClass;
|
||||
|
||||
// status bar label
|
||||
let statusSpan = createElm('span');
|
||||
// preceding text
|
||||
let statusSpanText = createElm('span');
|
||||
statusSpanText.appendChild(createText(this.text));
|
||||
|
||||
// target element container
|
||||
if(!this.statusBarTgtId){
|
||||
tf.setToolbar();
|
||||
}
|
||||
var targetEl = (!this.statusBarTgtId) ?
|
||||
tf.lDiv : Dom.id(this.statusBarTgtId);
|
||||
let targetEl = (!this.targetId) ?
|
||||
tf.feature('toolbar').container(this.toolbarPosition) :
|
||||
elm(this.targetId);
|
||||
|
||||
//default container: 'lDiv'
|
||||
if(!this.statusBarTgtId){
|
||||
// default container
|
||||
if (!this.targetId) {
|
||||
statusDiv.appendChild(statusSpanText);
|
||||
statusDiv.appendChild(statusSpan);
|
||||
targetEl.appendChild(statusDiv);
|
||||
|
@ -80,41 +211,98 @@ export class StatusBar{
|
|||
targetEl.appendChild(statusSpan);
|
||||
}
|
||||
|
||||
this.statusBarDiv = statusDiv;
|
||||
this.statusBarSpan = statusSpan;
|
||||
this.statusBarSpanText = statusSpanText;
|
||||
this.container = statusDiv;
|
||||
this.msgContainer = statusSpan;
|
||||
this.labelContainer = statusSpanText;
|
||||
|
||||
// subscribe to events
|
||||
emitter.on(['before-filtering'], () => this.message(this.msgFilter));
|
||||
emitter.on(['before-populating-filter'],
|
||||
() => this.message(this.msgPopulate));
|
||||
emitter.on(['before-page-change'],
|
||||
() => this.message(this.msgChangePage));
|
||||
emitter.on(['before-clearing-filters'], () =>
|
||||
this.message(this.msgClear));
|
||||
emitter.on(['before-page-length-change'],
|
||||
() => this.message(this.msgChangeResults));
|
||||
emitter.on(['before-reset-page'],
|
||||
() => this.message(this.msgResetPage));
|
||||
emitter.on(['before-reset-page-length'],
|
||||
() => this.message(this.msgResetPageLength));
|
||||
emitter.on(['before-loading-extensions'],
|
||||
() => this.message(this.msgLoadExtensions));
|
||||
emitter.on(['before-loading-themes'],
|
||||
() => this.message(this.msgLoadThemes));
|
||||
|
||||
emitter.on(EVENTS, () => this.message(''));
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
|
||||
emitter.emit('feature-initialized', this);
|
||||
}
|
||||
|
||||
message(t=''){
|
||||
var tf = this.tf;
|
||||
if(!tf.statusBar || !this.statusBarSpan){
|
||||
/**
|
||||
* Display status message
|
||||
* @param {String} [t=''] Message to be displayed
|
||||
*/
|
||||
message(t = '') {
|
||||
if (!this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if(this.onBeforeShowMsg){
|
||||
this.onBeforeShowMsg.call(null, this.tf, t);
|
||||
}
|
||||
|
||||
var d = t==='' ? this.statusBarCloseDelay : 1;
|
||||
global.setTimeout(() => {
|
||||
this.statusBarSpan.innerHTML = t;
|
||||
if(this.onAfterShowMsg){
|
||||
this.onAfterShowMsg.call(null, this.tf, t);
|
||||
this.onBeforeShowMsg(this.tf, t);
|
||||
|
||||
let d = t === '' ? this.delay : 1;
|
||||
root.setTimeout(() => {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.msgContainer.innerHTML = t;
|
||||
|
||||
this.onAfterShowMsg(this.tf, t);
|
||||
}, d);
|
||||
}
|
||||
|
||||
destroy(){
|
||||
var tf = this.tf;
|
||||
if(!tf.hasGrid() || !this.statusBarDiv){
|
||||
/**
|
||||
* Destroy StatusBar instance
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.statusBarDiv.innerHTML = '';
|
||||
this.statusBarDiv.parentNode.removeChild(this.statusBarDiv);
|
||||
this.statusBarSpan = null;
|
||||
this.statusBarSpanText = null;
|
||||
this.statusBarDiv = null;
|
||||
}
|
||||
let emitter = this.emitter;
|
||||
|
||||
this.container.innerHTML = '';
|
||||
if (!this.targetId) {
|
||||
removeElm(this.container);
|
||||
}
|
||||
this.labelContainer = null;
|
||||
this.msgContainer = null;
|
||||
this.container = null;
|
||||
|
||||
// Unsubscribe to events
|
||||
emitter.off(['before-filtering'], () => this.message(this.msgFilter));
|
||||
emitter.off(['before-populating-filter'],
|
||||
() => this.message(this.msgPopulate));
|
||||
emitter.off(['before-page-change'],
|
||||
() => this.message(this.msgChangePage));
|
||||
emitter.off(['before-clearing-filters'],
|
||||
() => this.message(this.msgClear));
|
||||
emitter.off(['before-page-length-change'],
|
||||
() => this.message(this.msgChangeResults));
|
||||
emitter.off(['before-reset-page'], () =>
|
||||
this.message(this.msgResetPage));
|
||||
emitter.off(['before-reset-page-length'],
|
||||
() => this.message(this.msgResetPageLength));
|
||||
emitter.off(['before-loading-extensions'],
|
||||
() => this.message(this.msgLoadExtensions));
|
||||
emitter.off(['before-loading-themes'],
|
||||
() => this.message(this.msgLoadThemes));
|
||||
|
||||
emitter.off(EVENTS, () => this.message(''));
|
||||
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
|
161
src/modules/storage.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
import Cookie from '../cookie';
|
||||
import {root} from '../root';
|
||||
|
||||
const JSON = root.JSON;
|
||||
const localStorage = root.localStorage;
|
||||
const location = root.location;
|
||||
|
||||
/**
|
||||
* Checks if browser has Storage feature
|
||||
*/
|
||||
export const hasStorage = () => {
|
||||
return 'Storage' in root;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores the features state in browser's local storage or cookie
|
||||
*
|
||||
* @export
|
||||
* @class Storage
|
||||
*/
|
||||
export class Storage {
|
||||
|
||||
/**
|
||||
* Creates an instance of Storage
|
||||
*
|
||||
* @param {State} state Instance of State
|
||||
*/
|
||||
constructor(state) {
|
||||
|
||||
/**
|
||||
* State object
|
||||
* @type {State}
|
||||
* @private
|
||||
*/
|
||||
this.state = state;
|
||||
|
||||
/**
|
||||
* TableFilter object
|
||||
* @type {TableFilter}
|
||||
* @private
|
||||
*/
|
||||
this.tf = state.tf;
|
||||
|
||||
/**
|
||||
* Persist with local storage
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.enableLocalStorage = state.enableLocalStorage && hasStorage();
|
||||
|
||||
/**
|
||||
* Persist with cookie
|
||||
* @type {Boolean}
|
||||
* @private
|
||||
*/
|
||||
this.enableCookie = state.enableCookie && !this.enableLocalStorage;
|
||||
|
||||
/**
|
||||
* Emitter object
|
||||
* @type {Emitter}
|
||||
* @private
|
||||
*/
|
||||
this.emitter = state.emitter;
|
||||
|
||||
/**
|
||||
* Cookie duration in hours from state object
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
this.duration = state.cookieDuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the Storage object
|
||||
*/
|
||||
init() {
|
||||
this.emitter.on(['state-changed'], (tf, state) => this.save(state));
|
||||
this.emitter.on(['initialized'], () => this.sync());
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the features state on state changes
|
||||
*
|
||||
* @param {State} state Instance of State
|
||||
*/
|
||||
save(state) {
|
||||
if (this.enableLocalStorage) {
|
||||
localStorage[this.getKey()] = JSON.stringify(state);
|
||||
} else {
|
||||
Cookie.write(this.getKey(), JSON.stringify(state), this.duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns stored string into a State JSON object
|
||||
*
|
||||
* @returns {Object} JSON object
|
||||
*/
|
||||
retrieve() {
|
||||
let state = null;
|
||||
if (this.enableLocalStorage) {
|
||||
state = localStorage[this.getKey()];
|
||||
} else {
|
||||
state = Cookie.read(this.getKey());
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes persisted state from storage
|
||||
*/
|
||||
remove() {
|
||||
if (this.enableLocalStorage) {
|
||||
localStorage.removeItem(this.getKey());
|
||||
} else {
|
||||
Cookie.remove(this.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies persisted state to features
|
||||
*/
|
||||
sync() {
|
||||
let state = this.retrieve();
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
// override current state with persisted one and sync features
|
||||
this.state.overrideAndSync(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the storage key
|
||||
*
|
||||
* @returns {String} Key
|
||||
*/
|
||||
getKey() {
|
||||
return JSON.stringify({
|
||||
key: `${this.tf.prfxTf}_${this.tf.id}`,
|
||||
path: location.pathname
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Release Storage event subscriptions and clear fields
|
||||
*/
|
||||
destroy() {
|
||||
this.emitter.off(['state-changed'], (tf, state) => this.save(state));
|
||||
this.emitter.off(['initialized'], () => this.sync());
|
||||
|
||||
this.remove();
|
||||
|
||||
this.state = null;
|
||||
this.emitter = null;
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
import Cookie from '../cookie';
|
||||
|
||||
export class Store{
|
||||
|
||||
/**
|
||||
* Store, persistence manager
|
||||
* @param {Object} tf TableFilter instance
|
||||
*
|
||||
* TODO: use localStorage and fallback to cookie persistence
|
||||
*/
|
||||
constructor(tf) {
|
||||
var f = tf.config();
|
||||
|
||||
this.duration = !isNaN(f.set_cookie_duration) ?
|
||||
parseInt(f.set_cookie_duration, 10) : 100000;
|
||||
|
||||
this.tf = tf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store filters' values in cookie
|
||||
* @param {String} cookie name
|
||||
*/
|
||||
saveFilterValues(name){
|
||||
var tf = this.tf;
|
||||
var fltValues = [];
|
||||
//store filters' values
|
||||
for(var i=0; i<tf.fltIds.length; i++){
|
||||
var value = tf.getFilterValue(i);
|
||||
if (value === ''){
|
||||
value = ' ';
|
||||
}
|
||||
fltValues.push(value);
|
||||
}
|
||||
//adds array size
|
||||
fltValues.push(tf.fltIds.length);
|
||||
|
||||
//writes cookie
|
||||
Cookie.write(
|
||||
name,
|
||||
fltValues.join(tf.separator),
|
||||
this.duration
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve filters' values from cookie
|
||||
* @param {String} cookie name
|
||||
* @return {Array}
|
||||
*/
|
||||
getFilterValues(name){
|
||||
var flts = Cookie.read(name);
|
||||
var rgx = new RegExp(this.tf.separator, 'g');
|
||||
// filters' values array
|
||||
return flts.split(rgx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store page number in cookie
|
||||
* @param {String} cookie name
|
||||
*/
|
||||
savePageNb(name){
|
||||
Cookie.write(
|
||||
name,
|
||||
this.tf.feature('paging').currentPageNb,
|
||||
this.duration
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve page number from cookie
|
||||
* @param {String} cookie name
|
||||
* @return {String}
|
||||
*/
|
||||
getPageNb(name){
|
||||
return Cookie.read(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store page length in cookie
|
||||
* @param {String} cookie name
|
||||
*/
|
||||
savePageLength(name){
|
||||
Cookie.write(
|
||||
name,
|
||||
this.tf.feature('paging').resultsPerPageSlc.selectedIndex,
|
||||
this.duration
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve page length from cookie
|
||||
* @param {String} cookie name
|
||||
* @return {String}
|
||||
*/
|
||||
getPageLength(name){
|
||||
return Cookie.read(name);
|
||||
}
|
||||
|
||||
}
|
224
src/modules/toolbar.js
Normal file
|
@ -0,0 +1,224 @@
|
|||
import {Feature} from '../feature';
|
||||
import {createElm, removeElm, elm, tag} from '../dom';
|
||||
import {defaultsStr} from '../settings';
|
||||
import {isUndef} from '../types';
|
||||
|
||||
const EVENTS = [
|
||||
'initializing-feature',
|
||||
'initializing-extension'
|
||||
];
|
||||
|
||||
/** Left position in toolbar */
|
||||
export const LEFT = 'left';
|
||||
/** Right position in toolbar */
|
||||
export const RIGHT = 'right';
|
||||
/** Center position in toolbar */
|
||||
export const CENTER = 'center';
|
||||
|
||||
/**
|
||||
* Toolbar UI component
|
||||
* @export
|
||||
* @class Toolbar
|
||||
* @extends {Feature}
|
||||
*/
|
||||
export class Toolbar extends Feature {
|
||||
|
||||
/**
|
||||
* Create an instance of Toolbar
|
||||
* @param {TableFilter} tf TableFilter instance
|
||||
* @memberof Toolbar
|
||||
*/
|
||||
constructor(tf) {
|
||||
super(tf, Toolbar);
|
||||
|
||||
// Configuration object
|
||||
let f = this.config.toolbar || {};
|
||||
|
||||
/**
|
||||
* Css class for toolbar's container DOM element
|
||||
* @type {String}
|
||||
*/
|
||||
this.contCssClass = defaultsStr(f.container_css_class, 'inf');
|
||||
|
||||
/**
|
||||
* Css class for left-side inner container DOM element
|
||||
* @type {String}
|
||||
*/
|
||||
this.lContCssClass = defaultsStr(f.left_cont_css_class, 'ldiv');
|
||||
|
||||
/**
|
||||
* Css class for right-side inner container DOM element
|
||||
* @type {String}
|
||||
*/
|
||||
this.rContCssClass = defaultsStr(f.right_cont_css_class, 'rdiv');
|
||||
|
||||
/**
|
||||
* Css class for middle inner container DOM element
|
||||
* @type {String}
|
||||
*/
|
||||
this.cContCssClass = defaultsStr(f.center_cont_css_class, 'mdiv');
|
||||
|
||||
/**
|
||||
* Toolbar's custom container ID
|
||||
* @type {String}
|
||||
*/
|
||||
this.tgtId = defaultsStr(f.target_id, null);
|
||||
|
||||
/**
|
||||
* Toolbar's container DOM element
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.cont = null;
|
||||
|
||||
/**
|
||||
* Left-side inner container DOM element (rows counter in toolbar)
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.lCont = null;
|
||||
|
||||
/**
|
||||
* Right-side inner container DOM element (reset button,
|
||||
* page length selector in toolbar)
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.rCont = null;
|
||||
|
||||
/**
|
||||
* Middle inner container DOM element (paging elements in toolbar)
|
||||
* @type {DOMElement}
|
||||
* @private
|
||||
*/
|
||||
this.cCont = null;
|
||||
|
||||
/**
|
||||
* Container elements inside toolbar
|
||||
* @private
|
||||
*/
|
||||
this.innerCont = {
|
||||
left: null,
|
||||
center: null,
|
||||
right: null
|
||||
};
|
||||
|
||||
this.emitter.on(EVENTS,
|
||||
(feature, isExternal) => this.init(isExternal));
|
||||
|
||||
/** @inherited */
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize toolbar components
|
||||
* @param {Boolean} isExternal initialize only if component belongs
|
||||
* to toolbar
|
||||
*/
|
||||
init(isExternal) {
|
||||
if (this.initialized || isExternal) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tf = this.tf;
|
||||
|
||||
// default container
|
||||
let container = createElm('div');
|
||||
container.className = this.contCssClass;
|
||||
|
||||
// custom container
|
||||
if (this.tgtId) {
|
||||
elm(this.tgtId).appendChild(container);
|
||||
}
|
||||
// grid-layout
|
||||
else if (tf.gridLayout) {
|
||||
let gridLayout = tf.Mod.gridLayout;
|
||||
gridLayout.tblMainCont.appendChild(container);
|
||||
container.className = gridLayout.infDivCssClass;
|
||||
}
|
||||
// default location: just above the table
|
||||
else {
|
||||
let cont = createElm('caption');
|
||||
cont.appendChild(container);
|
||||
tf.dom().insertBefore(cont, tf.dom().firstChild);
|
||||
}
|
||||
this.cont = container;
|
||||
|
||||
// left container
|
||||
this.lCont = this.createContainer(container, this.lContCssClass);
|
||||
|
||||
// right container
|
||||
this.rCont = this.createContainer(container, this.rContCssClass);
|
||||
|
||||
// middle container
|
||||
this.cCont = this.createContainer(container, this.cContCssClass);
|
||||
|
||||
this.innerCont = {
|
||||
left: this.lCont,
|
||||
center: this.cCont,
|
||||
right: this.rCont
|
||||
};
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = true;
|
||||
|
||||
// emit help initialisation only if undefined
|
||||
if (isUndef(tf.help)) {
|
||||
// explicitily enable help to initialise feature by
|
||||
// default, only if setting is undefined
|
||||
tf.Mod.help.enable();
|
||||
this.emitter.emit('init-help', tf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the container based on requested position inside the toolbar
|
||||
* @param {String} [position=RIGHT] 3 possible positions: 'left', 'center',
|
||||
* 'right'
|
||||
* @param {DOMElement} el optional DOM element to be inserter in container
|
||||
* @returns {DOMElement}
|
||||
*/
|
||||
container(position = RIGHT, el) {
|
||||
let cont = this.innerCont[position];
|
||||
if (el) {
|
||||
cont.appendChild(el);
|
||||
}
|
||||
return cont;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DOM element inside passed container
|
||||
* @param {DOMElement} container
|
||||
* @param {String} css
|
||||
* @private
|
||||
*/
|
||||
createContainer(container, css) {
|
||||
let div = createElm('div', ['class', css]);
|
||||
container.appendChild(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy Toolbar instance
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tf = this.tf;
|
||||
|
||||
removeElm(this.cont);
|
||||
this.cont = null;
|
||||
|
||||
let tbl = tf.dom();
|
||||
let captions = tag(tbl, 'caption');
|
||||
[].forEach.call(captions, (el) => removeElm(el));
|
||||
|
||||
/** @inherited */
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove as soon as feature name is fixed
|
||||
Toolbar.meta = {alwaysInstantiate: true};
|
32
src/number.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import {isNumber} from './types';
|
||||
|
||||
/**
|
||||
* Takes a string, removes all formatting/cruft and returns the raw float value
|
||||
* @param {String} Formatted number
|
||||
* @param {String} Decimal type '.' or ','
|
||||
* @return {Number} Unformatted number
|
||||
*
|
||||
* https://github.com/openexchangerates/accounting.js/blob/master/accounting.js
|
||||
*/
|
||||
export const parse = (value, decimal = '.') => {
|
||||
// Return the value as-is if it's already a number
|
||||
if (isNumber(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Build regex to strip out everything except digits, decimal point and
|
||||
// minus sign
|
||||
let regex = new RegExp('[^0-9-' + decimal + ']', ['g']);
|
||||
let unformatted = parseFloat(
|
||||
('' + value)
|
||||
// replace bracketed values with negatives
|
||||
.replace(/\((.*)\)/, '-$1')
|
||||
// strip out any cruft
|
||||
.replace(regex, '')
|
||||
// make sure decimal point is standard
|
||||
.replace(decimal, '.')
|
||||
);
|
||||
|
||||
// This will fail silently
|
||||
return !isNaN(unformatted) ? unformatted : 0;
|
||||
};
|
6
src/root.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Export window or global object depending on the environment
|
||||
*/
|
||||
export const root = (typeof self === 'object' && self.self === self && self) ||
|
||||
(typeof global === 'object' && global.global === global && global) ||
|
||||
this;
|
53
src/settings.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import {isBoolean, isString, isFn, isArray} from './types';
|
||||
|
||||
/** Configuration settings helpers */
|
||||
|
||||
/**
|
||||
* If passed value is not of boolean type return the default value
|
||||
* otherwise return the value itself
|
||||
* @param {Boolean|Any} value
|
||||
* @param {Boolean} default value
|
||||
* @return {Boolean|Any}
|
||||
*/
|
||||
export const defaultsBool =
|
||||
(val, defaultVal) => isBoolean(val) ? val : defaultVal;
|
||||
|
||||
/**
|
||||
* If passed value is not of string type return the default value
|
||||
* otherwise return the value itself
|
||||
* @param {String|Any} value
|
||||
* @param {String} default value
|
||||
* @return {String|Any}
|
||||
*/
|
||||
export const defaultsStr =
|
||||
(val, defaultVal) => isString(val) ? val : defaultVal;
|
||||
|
||||
/**
|
||||
* If passed value is not of number type return the default value
|
||||
* otherwise return the value itself
|
||||
* @param {Number|Any} value
|
||||
* @param {Number} default value
|
||||
* @return {Number|Any}
|
||||
*/
|
||||
export const defaultsNb =
|
||||
(val, defaultVal) => isNaN(val) ? defaultVal : val;
|
||||
|
||||
/**
|
||||
* If passed value is not of array type return the default value
|
||||
* otherwise return the value itself
|
||||
* @param {Array|Any} value
|
||||
* @param {Array} default value
|
||||
* @return {Array|Any}
|
||||
*/
|
||||
export const defaultsArr =
|
||||
(val, defaultVal) => isArray(val) ? val : defaultVal;
|
||||
|
||||
/**
|
||||
* If passed value is not of function type return the default value
|
||||
* otherwise return the value itself
|
||||
* @param {Function|Any} value
|
||||
* @param {Function} default value
|
||||
* @return {Function|Any}
|
||||
*/
|
||||
export const defaultsFn =
|
||||
(val, defaultVal) => isFn(val) ? val : defaultVal;
|
87
src/sort.js
|
@ -1,9 +1,82 @@
|
|||
import Str from './string';
|
||||
import {parse as parseNb} from './number';
|
||||
import {Date as SugarDate} from 'sugar-date';
|
||||
|
||||
export default {
|
||||
ignoreCase(a, b){
|
||||
let x = Str.lower(a);
|
||||
let y = Str.lower(b);
|
||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
||||
}
|
||||
/** Sorting utilities */
|
||||
|
||||
/**
|
||||
* Case insensitive compare function for passed strings
|
||||
* @param {String} First string
|
||||
* @param {String} Second string
|
||||
* @return {Number} -1 if first string lower than second one
|
||||
* 0 if first string same order as second one
|
||||
* 1 if first string greater than second one
|
||||
*/
|
||||
export const ignoreCase = (a, b) => {
|
||||
let x = a.toLowerCase();
|
||||
let y = b.toLowerCase();
|
||||
return x < y ? -1 : (x > y ? 1 : 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare function for sorting passed numbers in ascending manner
|
||||
* @param {Number} First number
|
||||
* @param {Number} Second number
|
||||
* @return {Number} Negative, zero or positive number
|
||||
*/
|
||||
export const numSortAsc = (a, b) => (a - b);
|
||||
|
||||
/**
|
||||
* Compare function for sorting passed numbers in descending manner
|
||||
* @param {Number} First number
|
||||
* @param {Number} Second number
|
||||
* @return {Number} Negative, zero or positive number
|
||||
*/
|
||||
export const numSortDesc = (a, b) => (b - a);
|
||||
|
||||
/**
|
||||
* Compare function for sorting passed dates in ascending manner according to
|
||||
* the corresponding UTC numeric value (returned by getTime)
|
||||
* @param {Date} First date object
|
||||
* @param {Date} Second date object
|
||||
* @return {Number} Negative, zero or positive number
|
||||
*/
|
||||
export const dateSortAsc = (date1, date2) => date1.getTime() - date2.getTime();
|
||||
|
||||
/**
|
||||
* Compare function for sorting passed dates in descending manner according to
|
||||
* the corresponding UTC numeric value (returned by getTime)
|
||||
* @param {Date} First date object
|
||||
* @param {Date} Second date object
|
||||
* @return {Number} Negative, zero or positive number
|
||||
*/
|
||||
export const dateSortDesc = (date1, date2) => date2.getTime() - date1.getTime();
|
||||
|
||||
/**
|
||||
* Curried compare function for sorting passed formatted numbers in desired
|
||||
* fashion according to supplied compare function and decimal separator
|
||||
* @param {Function} Compare function
|
||||
* @param {String} [decimal=','] Decimal separator
|
||||
* @return {Function} Compare function receiving parsed numeric arguments
|
||||
*/
|
||||
export const sortNumberStr = (compareFn, decimal = ',') => {
|
||||
return (numStr1, numStr2) => {
|
||||
let num1 = parseNb(numStr1, decimal);
|
||||
let num2 = parseNb(numStr2, decimal);
|
||||
return compareFn(num1, num2);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Curried compare function for sorting passed formatted dates in desired
|
||||
* fashion according to supplied compare function and locale
|
||||
* @param {Function} Compare function
|
||||
* @param {String} [locale='en-us'] Locale code
|
||||
* @return {Function} Compare function receiving parsed date arguments
|
||||
*/
|
||||
export const sortDateStr = (compareFn, locale = 'en-us') => {
|
||||
return (dateStr1, dateStr2) => {
|
||||
let date1 = SugarDate.create(dateStr1, locale);
|
||||
let date2 = SugarDate.create(dateStr2, locale);
|
||||
return compareFn(date1, date2);
|
||||
};
|
||||
};
|
||||
|
|
139
src/string.js
|
@ -1,39 +1,112 @@
|
|||
import {remove as removeDiacritics} from 'diacritics';
|
||||
|
||||
/**
|
||||
* String utilities
|
||||
*/
|
||||
|
||||
export default {
|
||||
|
||||
lower(text){
|
||||
return text.toLowerCase();
|
||||
},
|
||||
|
||||
upper(text){
|
||||
return text.toUpperCase();
|
||||
},
|
||||
|
||||
trim(text){
|
||||
if (text.trim){
|
||||
return text.trim();
|
||||
}
|
||||
return text.replace(/^\s*|\s*$/g, '');
|
||||
},
|
||||
|
||||
isEmpty(text){
|
||||
return this.trim(text) === '';
|
||||
},
|
||||
|
||||
rgxEsc(text){
|
||||
let chars = /[-\/\\^$*+?.()|[\]{}]/g;
|
||||
let escMatch = '\\$&';
|
||||
return String(text).replace(chars, escMatch);
|
||||
},
|
||||
|
||||
matchCase(text, mc){
|
||||
if(!mc){
|
||||
return this.lower(text);
|
||||
}
|
||||
return text;
|
||||
/**
|
||||
* Removes whitespace from both sides of passed string
|
||||
* @param {String} text
|
||||
* @return {String}
|
||||
*/
|
||||
export const trim = (text) => {
|
||||
if (text.trim) {
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
return text.replace(/^\s*|\s*$/g, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if passed string is empty
|
||||
* @param {String} text
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const isEmpty = (text) => trim(text) === '';
|
||||
|
||||
/**
|
||||
* Makes regex safe string by escaping special characters from passed string
|
||||
* @param {String} text
|
||||
* @return {String} escaped string
|
||||
*/
|
||||
export const rgxEsc = (text) => {
|
||||
let chars = /[-\/\\^$*+?.()|[\]{}]/g;
|
||||
let escMatch = '\\$&';
|
||||
return String(text).replace(chars, escMatch);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns passed string as lowercase if caseSensitive flag set false. By
|
||||
* default it returns the string with no casing changes.
|
||||
* @param {String} text
|
||||
* @return {String} string
|
||||
*/
|
||||
export const matchCase = (text, caseSensitive = false) => {
|
||||
if (!caseSensitive) {
|
||||
return text.toLowerCase();
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if passed data contains the searched term
|
||||
* @param {String} term Searched term
|
||||
* @param {String} data Data string
|
||||
* @param {Boolean} exactMatch Exact match
|
||||
* @param {Boolean} caseSensitive Case sensitive
|
||||
* @param {Boolean} ignoreDiacritics Ignore diacritics
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export const contains = (term, data, exactMatch = false, caseSensitive = false,
|
||||
ignoreDiacritics = false) => {
|
||||
// Improved by Cedric Wartel (cwl) automatic exact match for selects and
|
||||
// special characters are now filtered
|
||||
let regexp;
|
||||
let modifier = caseSensitive ? 'g' : 'gi';
|
||||
if (ignoreDiacritics) {
|
||||
term = removeDiacritics(term);
|
||||
data = removeDiacritics(data);
|
||||
}
|
||||
if (exactMatch) {
|
||||
regexp = new RegExp('(^\\s*)' + rgxEsc(term) + '(\\s*$)',
|
||||
modifier);
|
||||
} else {
|
||||
regexp = new RegExp(rgxEsc(term), modifier);
|
||||
}
|
||||
return regexp.test(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Camelize a string, cutting the string by multiple separators like
|
||||
* hyphens, underscores and spaces.
|
||||
* @param {String} text text to camelize
|
||||
* @return {String} camelized text
|
||||
*/
|
||||
export const toCamelCase = (text = '') => {
|
||||
return text.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => {
|
||||
if (p2) {
|
||||
return p2.toUpperCase();
|
||||
}
|
||||
return p1.toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a string in the format of a UUID (Universally Unique IDentifier).
|
||||
* NOTE: This format of 8 chars, followed by 3 groups of 4 chars, followed by 12
|
||||
* chars is known as a UUID and is defined in RFC4122 and is a standard for
|
||||
* generating unique IDs. This function DOES NOT implement this standard.
|
||||
* It simply outputs a string that looks similar. The standard is found here:
|
||||
* https://www.ietf.org/rfc/rfc4122.txt
|
||||
* source: https://gist.github.com/gordonbrander/2230317
|
||||
* @return {String}
|
||||
*/
|
||||
export const uuid = () => {
|
||||
const chr4 = () => Math.random().toString(16).slice(-4);
|
||||
|
||||
return chr4() + chr4()
|
||||
+ '-' + chr4()
|
||||
+ '-' + chr4()
|
||||
+ '-' + chr4()
|
||||
+ '-' + chr4()
|
||||
+ chr4() + chr4();
|
||||
};
|
||||
|
|