mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-18 04:45:12 -06:00
Compare commits
2037 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c9ad3f406 | ||
|
|
4cf0339393 | ||
|
|
a2ff3e3474 | ||
|
|
c2f85b0447 | ||
|
|
8f07efa4e3 | ||
|
|
b490c5d035 | ||
|
|
f9449da576 | ||
|
|
34e95ab70c | ||
|
|
5f9236d280 | ||
|
|
44fe5721e0 | ||
|
|
f6f3815f31 | ||
|
|
78bcbe271a | ||
|
|
2e8d3b6424 | ||
|
|
c9021fe991 | ||
|
|
0e1b647e7b | ||
|
|
b74d365529 | ||
|
|
3da5505b58 | ||
|
|
5a168abbfe | ||
|
|
8f626d305e | ||
|
|
5d9e273559 | ||
|
|
91b6f98f95 | ||
|
|
2f1db486a0 | ||
|
|
823f84e46a | ||
|
|
178715ce61 | ||
|
|
e787c853e5 | ||
|
|
5e54b0f5cf | ||
|
|
9086c8a3bf | ||
|
|
e011faa9b7 | ||
|
|
e6bd91c9e2 | ||
|
|
1372343bd5 | ||
|
|
8eb571bddf | ||
|
|
c7e1ac5648 | ||
|
|
6bf3cc9e01 | ||
|
|
6bf4140e5a | ||
|
|
afddf12339 | ||
|
|
6a1bb0a806 | ||
|
|
97e1bc0a67 | ||
|
|
cbddda6640 | ||
|
|
7491cda313 | ||
|
|
b5a5f5b409 | ||
|
|
47a93bc4cb | ||
|
|
3381945e14 | ||
|
|
3926276fd1 | ||
|
|
71cac6e269 | ||
|
|
ed304f571a | ||
|
|
14aead038e | ||
|
|
d4d9263131 | ||
|
|
9c38d67b52 | ||
|
|
4a3ca4638c | ||
|
|
d91531720b | ||
|
|
5e0e364b6c | ||
|
|
da63ec5351 | ||
|
|
6412cf1c87 | ||
|
|
32eaf48c12 | ||
|
|
530b90042a | ||
|
|
2c053eae4c | ||
|
|
ef64d1e61d | ||
|
|
adcb211572 | ||
|
|
efd14fca64 | ||
|
|
20c455384e | ||
|
|
c68220a597 | ||
|
|
cd7724d490 | ||
|
|
f67210b20f | ||
|
|
1854da380b | ||
|
|
0547e1c03b | ||
|
|
446bc06c1b | ||
|
|
2de6fcbfa4 | ||
|
|
698b946403 | ||
|
|
00c4519d28 | ||
|
|
2436b1f2c9 | ||
|
|
8d3b9e2ca4 | ||
|
|
be41be3981 | ||
|
|
8e7e36089b | ||
|
|
47ba0599eb | ||
|
|
db858b3cfc | ||
|
|
148600a9c4 | ||
|
|
839c29117d | ||
|
|
d446f8ddd1 | ||
|
|
08a1a6cf43 | ||
|
|
76ab38a06b | ||
|
|
38b9655ad9 | ||
|
|
b9e81c3c0e | ||
|
|
b9f9b26ca5 | ||
|
|
7683cc1aaa | ||
|
|
ff6b127f1b | ||
|
|
8b227ce297 | ||
|
|
286cf9b102 | ||
|
|
24d36e0b66 | ||
|
|
251ec7a02f | ||
|
|
61f8b982ce | ||
|
|
1f881dd041 | ||
|
|
42b400e619 | ||
|
|
601aff8283 | ||
|
|
58bfc80f79 | ||
|
|
2746396d11 | ||
|
|
6017cad6b3 | ||
|
|
1a7b6e2613 | ||
|
|
ab57fd3b76 | ||
|
|
c0a0ca4588 | ||
|
|
3f257279ee | ||
|
|
731ec25b24 | ||
|
|
53c9475e6d | ||
|
|
9ccff64679 | ||
|
|
de3395ed26 | ||
|
|
16433f49c6 | ||
|
|
73e032e1d0 | ||
|
|
82b6c48946 | ||
|
|
566c3af39e | ||
|
|
5d141e3568 | ||
|
|
83cd24961d | ||
|
|
737a378515 | ||
|
|
f71ebc20ec | ||
|
|
ac9753e72c | ||
|
|
dc94376f6d | ||
|
|
c846e2e65a | ||
|
|
608f47837f | ||
|
|
2f6ee330de | ||
|
|
c66dddc03f | ||
|
|
48cbd3be97 | ||
|
|
6b99808c52 | ||
|
|
61d14a0eda | ||
|
|
5aea35f8fa | ||
|
|
3cc0530419 | ||
|
|
cef75aabc5 | ||
|
|
09693d0d09 | ||
|
|
70703f8588 | ||
|
|
67a4c5a9a2 | ||
|
|
db8d93e2e9 | ||
|
|
af52f0eace | ||
|
|
631e41e22a | ||
|
|
f8d29b7b3b | ||
|
|
9e4218d02f | ||
|
|
d09483f30c | ||
|
|
d1a42f55a2 | ||
|
|
bdd5fa96e7 | ||
|
|
4f87c848a5 | ||
|
|
d20c3bb733 | ||
|
|
ae72224ece | ||
|
|
fd43f8dc64 | ||
|
|
6b32142725 | ||
|
|
01e6635032 | ||
|
|
88a021f165 | ||
|
|
b16f85de65 | ||
|
|
865436c42a | ||
|
|
db994fd908 | ||
|
|
83f8afe113 | ||
|
|
e7ed4c4eab | ||
|
|
39b2c5f943 | ||
|
|
3ddbb6a6cd | ||
|
|
fd01e23245 | ||
|
|
1a288168b7 | ||
|
|
f288b906ad | ||
|
|
58c30f48d5 | ||
|
|
bfbdd72306 | ||
|
|
0b78f3d931 | ||
|
|
32cfe1b954 | ||
|
|
67f06d6e5d | ||
|
|
408b9d5e5b | ||
|
|
49bb5634da | ||
|
|
709bfb1bd2 | ||
|
|
217097c9d3 | ||
|
|
19059ea4cf | ||
|
|
d64dd68403 | ||
|
|
bd87f4c733 | ||
|
|
19c03e1472 | ||
|
|
cffe2ba84b | ||
|
|
7a4b90649a | ||
|
|
88c1bc79d7 | ||
|
|
a3ab70b05d | ||
|
|
c558a04162 | ||
|
|
1ffd3a0070 | ||
|
|
5a5e76e0a6 | ||
|
|
3a0c68bf5c | ||
|
|
df0ab2359f | ||
|
|
ce64feb79d | ||
|
|
e3f49b8996 | ||
|
|
edbbfff1af | ||
|
|
8fd666e662 | ||
|
|
f25c83226f | ||
|
|
b7718d9c6c | ||
|
|
283663633a | ||
|
|
c9464fd393 | ||
|
|
84aea1d587 | ||
|
|
57289fe141 | ||
|
|
9c91ba4692 | ||
|
|
4e7243b999 | ||
|
|
e3a5a56371 | ||
|
|
db47629867 | ||
|
|
8b310d8e47 | ||
|
|
be30933bfa | ||
|
|
9a710ca28f | ||
|
|
47a1143570 | ||
|
|
5f19f534fc | ||
|
|
e2e870858d | ||
|
|
15678f789c | ||
|
|
45edb7e802 | ||
|
|
5b7d35e6f7 | ||
|
|
3900f9b1b6 | ||
|
|
8619af9f09 | ||
|
|
85474516a3 | ||
|
|
6946abae13 | ||
|
|
5e48009241 | ||
|
|
64a57128bc | ||
|
|
d39bd88440 | ||
|
|
7b81a470b9 | ||
|
|
247cbe0154 | ||
|
|
2de879d2ba | ||
|
|
b17a50bd51 | ||
|
|
9918860820 | ||
|
|
c6e154f996 | ||
|
|
c1aed51de1 | ||
|
|
24823adc6d | ||
|
|
6ea846ce45 | ||
|
|
f6dd710d6e | ||
|
|
de85d61451 | ||
|
|
5ff18a0a3a | ||
|
|
82faccc62f | ||
|
|
59e98bc22d | ||
|
|
acb0b2c10a | ||
|
|
429f9d0a13 | ||
|
|
89c7c87f1e | ||
|
|
b152f7041b | ||
|
|
700effcee7 | ||
|
|
18b559fee7 | ||
|
|
9d404afec0 | ||
|
|
51616c8aca | ||
|
|
ef4dbda223 | ||
|
|
006dd10a79 | ||
|
|
5b8eb1c530 | ||
|
|
bfb0cb3b47 | ||
|
|
ed028aed62 | ||
|
|
c07f217416 | ||
|
|
47c98db8a1 | ||
|
|
4925f24ca9 | ||
|
|
18defcff16 | ||
|
|
3cf5aec289 | ||
|
|
1532a0c3a1 | ||
|
|
5949ccd74f | ||
|
|
f1ba20c3da | ||
|
|
eb6310c774 | ||
|
|
9d321f4833 | ||
|
|
e1ba0a9a99 | ||
|
|
8e2a2c59bf | ||
|
|
7ccd339b5c | ||
|
|
fa95759a00 | ||
|
|
23e19614a5 | ||
|
|
3897166185 | ||
|
|
7800664f4b | ||
|
|
448c3cc6f5 | ||
|
|
5a6a12604e | ||
|
|
33e176e4e7 | ||
|
|
9b0595d232 | ||
|
|
7f05485954 | ||
|
|
727fd38978 | ||
|
|
d559d92f58 | ||
|
|
39d44ce32f | ||
|
|
5b68d82fa3 | ||
|
|
660fed9196 | ||
|
|
6f63b43c1c | ||
|
|
ca626ead6c | ||
|
|
fb096bd65b | ||
|
|
a00756ec3a | ||
|
|
af0c72d338 | ||
|
|
5ae5b9c0d4 | ||
|
|
48cd227f06 | ||
|
|
ae3a0133eb | ||
|
|
45b3c68930 | ||
|
|
7bec3ff5dd | ||
|
|
9ea3193ffb | ||
|
|
8061669c70 | ||
|
|
ade1d36397 | ||
|
|
a4257ad9df | ||
|
|
15ca35ca94 | ||
|
|
c46fe354c2 | ||
|
|
d43cfccfc1 | ||
|
|
5ad915a845 | ||
|
|
d111e7bff0 | ||
|
|
249860fa76 | ||
|
|
a9e3e46b8a | ||
|
|
fb5d7c415b | ||
|
|
979f543c3f | ||
|
|
14000517ce | ||
|
|
4a28fa7fa8 | ||
|
|
339a02a6b2 | ||
|
|
558ea98fa1 | ||
|
|
0b15b77140 | ||
|
|
a53aadd466 | ||
|
|
3a6f13f259 | ||
|
|
3d4cfcf1b6 | ||
|
|
e7e2abf3fc | ||
|
|
cd01bb0ad7 | ||
|
|
753483e6c2 | ||
|
|
7f6f489463 | ||
|
|
33f195533e | ||
|
|
42dfda6d07 | ||
|
|
7cb712f422 | ||
|
|
a2f849d663 | ||
|
|
9478da2ad2 | ||
|
|
f6e982ce81 | ||
|
|
bc25e795dd | ||
|
|
2cf87c1c30 | ||
|
|
628e87cb28 | ||
|
|
6f4178acdb | ||
|
|
1c3b017283 | ||
|
|
4ff1c690cc | ||
|
|
7cf1715e2b | ||
|
|
197682635e | ||
|
|
a85ac6161e | ||
|
|
478dce449d | ||
|
|
36c7528aeb | ||
|
|
374860c914 | ||
|
|
278466e830 | ||
|
|
7aed696451 | ||
|
|
b7cf57c875 | ||
|
|
9f9629edb3 | ||
|
|
b157690ae0 | ||
|
|
34def10765 | ||
|
|
04dbfbd892 | ||
|
|
3dad86f004 | ||
|
|
44635d2499 | ||
|
|
90feb6d210 | ||
|
|
1239d4af16 | ||
|
|
487794a938 | ||
|
|
8ce7b572cc | ||
|
|
45791c0257 | ||
|
|
6f2ba71339 | ||
|
|
5cdae04c62 | ||
|
|
19c76f7842 | ||
|
|
5069882a7f | ||
|
|
0b9cb507c7 | ||
|
|
732388c7d3 | ||
|
|
f1c8c48d35 | ||
|
|
d5597a3dca | ||
|
|
4b147e86ce | ||
|
|
b6d87850dc | ||
|
|
b315b958b0 | ||
|
|
5a536be22d | ||
|
|
d76b873c00 | ||
|
|
9d099c81a7 | ||
|
|
ee744be5fe | ||
|
|
6e3cb326fb | ||
|
|
0f6cdd0037 | ||
|
|
aad92902f2 | ||
|
|
2460965fef | ||
|
|
5c61c18dc4 | ||
|
|
cd5bc395f2 | ||
|
|
20740035e8 | ||
|
|
81e9fd7a44 | ||
|
|
0e971c468b | ||
|
|
36359fc547 | ||
|
|
cd8c6fa81a | ||
|
|
1dfda5b0ed | ||
|
|
414a981858 | ||
|
|
12c2ea3b37 | ||
|
|
44f5e0907e | ||
|
|
f2da1c990b | ||
|
|
35b9b17167 | ||
|
|
bb03f2bd58 | ||
|
|
26e80fa0ab | ||
|
|
fe42d5c54d | ||
|
|
a451de3735 | ||
|
|
ef84b98a89 | ||
|
|
48d2a395de | ||
|
|
a0eec930bb | ||
|
|
89bdfd1c62 | ||
|
|
888fd4116c | ||
|
|
5525b23b9f | ||
|
|
3210af31b4 | ||
|
|
213851e8aa | ||
|
|
bedfaa582c | ||
|
|
2a7b32d4f4 | ||
|
|
3b03d45f1f | ||
|
|
546a1c91cc | ||
|
|
a660578262 | ||
|
|
15a59d54ca | ||
|
|
edb803bf67 | ||
|
|
2e87499fa5 | ||
|
|
68f24e97ac | ||
|
|
0016628b41 | ||
|
|
8cc904fe8a | ||
|
|
2c85d630f7 | ||
|
|
15275d24b4 | ||
|
|
a48be3bea9 | ||
|
|
b64d48544d | ||
|
|
ad7a70f033 | ||
|
|
8c9202de21 | ||
|
|
94c8bb6805 | ||
|
|
648d322f54 | ||
|
|
8717be66fc | ||
|
|
8972a51bd6 | ||
|
|
333d6dd1b8 | ||
|
|
615e2f8271 | ||
|
|
571a03043e | ||
|
|
949a541ee0 | ||
|
|
66e1b180e4 | ||
|
|
39d4ddd845 | ||
|
|
5a58e0a298 | ||
|
|
791b14920a | ||
|
|
041f056116 | ||
|
|
4dc093662d | ||
|
|
832a6ef9a2 | ||
|
|
58863b1924 | ||
|
|
c1dd5b1ca1 | ||
|
|
2186a66465 | ||
|
|
b8327a5531 | ||
|
|
613776fbe5 | ||
|
|
199ba27031 | ||
|
|
cf6a03456f | ||
|
|
28e9aa4afa | ||
|
|
9159afe9ce | ||
|
|
5d1e6981d8 | ||
|
|
7582b9ccf4 | ||
|
|
9fe9cbc795 | ||
|
|
7d54997cb8 | ||
|
|
bf51e1bfb1 | ||
|
|
da715d7381 | ||
|
|
caf4c293d9 | ||
|
|
23da591c22 | ||
|
|
828b3adc49 | ||
|
|
a742df2c12 | ||
|
|
ad6c69ecc7 | ||
|
|
89cdcda614 | ||
|
|
beb155133a | ||
|
|
2ac9eadd13 | ||
|
|
d426e376c4 | ||
|
|
2b1101e165 | ||
|
|
afd7199a69 | ||
|
|
745188082b | ||
|
|
e53c6e9975 | ||
|
|
cc2b672117 | ||
|
|
d11adf3b34 | ||
|
|
dffff89e9d | ||
|
|
9d12d43574 | ||
|
|
116ea65160 | ||
|
|
6df6a460e4 | ||
|
|
b95d6a4ab2 | ||
|
|
6037f1452a | ||
|
|
beeb94785d | ||
|
|
e02fb8c8fa | ||
|
|
ea4b713eed | ||
|
|
ca16e4f35d | ||
|
|
ba71ce941f | ||
|
|
5b549b8dea | ||
|
|
098b494047 | ||
|
|
e8bacbe45f | ||
|
|
644583f636 | ||
|
|
556d6455e8 | ||
|
|
a9f255be85 | ||
|
|
ef23ab1abc | ||
|
|
cdb3dd2aa5 | ||
|
|
0c053c6339 | ||
|
|
bfbcbe252b | ||
|
|
8cb3d6ab0a | ||
|
|
43a7359501 | ||
|
|
d2d842163d | ||
|
|
0f60482e09 | ||
|
|
1b73f56937 | ||
|
|
e75b9d04fe | ||
|
|
7a51e6cf78 | ||
|
|
25ada20a19 | ||
|
|
07ee27ad9b | ||
|
|
3822b60bb0 | ||
|
|
abe1018abe | ||
|
|
8caf6bad10 | ||
|
|
bf63005731 | ||
|
|
3f30421ba9 | ||
|
|
ef902fc313 | ||
|
|
bb3440a8a4 | ||
|
|
1eb2456e3d | ||
|
|
5b402b6bc0 | ||
|
|
43eba3f7ec | ||
|
|
40d75090a7 | ||
|
|
d692c18274 | ||
|
|
69708b842c | ||
|
|
afc33c59ea | ||
|
|
08cc866e74 | ||
|
|
98bd7dfa9a | ||
|
|
fcc35f2260 | ||
|
|
ba542dcbdb | ||
|
|
c8650ce34c | ||
|
|
8780b8435a | ||
|
|
34df19242c | ||
|
|
28c9c80f54 | ||
|
|
cda97829ab | ||
|
|
d2c9f759b1 | ||
|
|
baa352ca98 | ||
|
|
a444f21e64 | ||
|
|
6b48f0db43 | ||
|
|
7071ef5a5c | ||
|
|
7d30e75bc6 | ||
|
|
46f20593c5 | ||
|
|
f87b15e4ea | ||
|
|
b09e9c0781 | ||
|
|
fdca3d842d | ||
|
|
81cac0927d | ||
|
|
a416c56e5a | ||
|
|
a722057dab | ||
|
|
31a8c94d2f | ||
|
|
6cc0880b4a | ||
|
|
247c4bdb4b | ||
|
|
b222119653 | ||
|
|
e72aaf2e07 | ||
|
|
a349e77866 | ||
|
|
4431082440 | ||
|
|
e0db833038 | ||
|
|
39d9ecffec | ||
|
|
99578e6986 | ||
|
|
10e50cf5e4 | ||
|
|
998620b5f3 | ||
|
|
16692a2905 | ||
|
|
67b6123b70 | ||
|
|
e8fd336612 | ||
|
|
11099676ef | ||
|
|
4e9b9ee6fd | ||
|
|
9be2989971 | ||
|
|
a1cf893eb2 | ||
|
|
ba85ff63bf | ||
|
|
12821b80fb | ||
|
|
19143ed082 | ||
|
|
b22b82877d | ||
|
|
efc2290613 | ||
|
|
b43d6a2ad4 | ||
|
|
d6eae83809 | ||
|
|
89adac49f9 | ||
|
|
2fa7c8706b | ||
|
|
9385447761 | ||
|
|
a5c36e2ca8 | ||
|
|
ef31727ec9 | ||
|
|
30d95e1511 | ||
|
|
dbaa0fc300 | ||
|
|
2aa2f08658 | ||
|
|
b5e993872f | ||
|
|
d686fb5057 | ||
|
|
f458342e5f | ||
|
|
33d66182df | ||
|
|
af7a627230 | ||
|
|
978ea7cc0b | ||
|
|
3c5169c793 | ||
|
|
74704a7c1e | ||
|
|
4d91a34136 | ||
|
|
140c8b5395 | ||
|
|
62a7964dda | ||
|
|
a0e7d37a19 | ||
|
|
5aec487c12 | ||
|
|
bc36775270 | ||
|
|
98d291c2f8 | ||
|
|
59904ace1d | ||
|
|
264bd625d3 | ||
|
|
0de284f1aa | ||
|
|
6ebebe03d4 | ||
|
|
a025b7239d | ||
|
|
ddbf2a6313 | ||
|
|
cf0c43d899 | ||
|
|
6230fb3614 | ||
|
|
801b945438 | ||
|
|
3ccc500e8e | ||
|
|
b107c54eb2 | ||
|
|
cabac6614c | ||
|
|
e407b99d0d | ||
|
|
d811f3c48a | ||
|
|
93f0dbd4ee | ||
|
|
ad0b59bf11 | ||
|
|
b284fe7f2b | ||
|
|
75431f0ee4 | ||
|
|
e417ff2b4d | ||
|
|
29bb735dc4 | ||
|
|
5c8e65c285 | ||
|
|
f135f11564 | ||
|
|
c2b5d96186 | ||
|
|
bd9447d9aa | ||
|
|
34dbfe6d28 | ||
|
|
1d317b1ecb | ||
|
|
cbadb2a888 | ||
|
|
90bef69a59 | ||
|
|
79ddea50f5 | ||
|
|
5e9097b5e0 | ||
|
|
0e246a7fdf | ||
|
|
793e5b820e | ||
|
|
bc43d14ebf | ||
|
|
f798537c73 | ||
|
|
65791a2b9b | ||
|
|
43e03ed023 | ||
|
|
7d38507785 | ||
|
|
3449848682 | ||
|
|
97140b19ba | ||
|
|
27b8928a10 | ||
|
|
cb81d8b66a | ||
|
|
8fda2cf745 | ||
|
|
4ef6a6dc62 | ||
|
|
282ed0c637 | ||
|
|
c1a54ddffd | ||
|
|
62501ecb93 | ||
|
|
8c7621d4bc | ||
|
|
fc193c26d0 | ||
|
|
489a15704c | ||
|
|
ae15ce9d0a | ||
|
|
e8d73c78eb | ||
|
|
c61631a380 | ||
|
|
5e5bced0c7 | ||
|
|
4641123cd8 | ||
|
|
d61ae8532a | ||
|
|
7e8def15ed | ||
|
|
51a33e5dca | ||
|
|
14d24ebe07 | ||
|
|
220777611a | ||
|
|
5c9dd93ff1 | ||
|
|
03f7baf87f | ||
|
|
2d412e2be1 | ||
|
|
b90311acea | ||
|
|
69362ab960 | ||
|
|
71522fa608 | ||
|
|
82e6b87e1c | ||
|
|
914fe15921 | ||
|
|
0d462389b9 | ||
|
|
d17cb47e3c | ||
|
|
5c2012f873 | ||
|
|
0517fe5073 | ||
|
|
59e37b2526 | ||
|
|
ce28b84f34 | ||
|
|
f81c154578 | ||
|
|
436207b315 | ||
|
|
a5aa90cdf2 | ||
|
|
833ba8c472 | ||
|
|
526d81481b | ||
|
|
697a338700 | ||
|
|
6a7c722efc | ||
|
|
952da19600 | ||
|
|
0a566c062d | ||
|
|
ef7cc3f78d | ||
|
|
1b835a71df | ||
|
|
cbb286e46d | ||
|
|
873ac9accb | ||
|
|
f7079c3bc2 | ||
|
|
3ae4125df3 | ||
|
|
1d6bd2c6ca | ||
|
|
c392bba196 | ||
|
|
c517801c5f | ||
|
|
2277d225eb | ||
|
|
2b30df1a70 | ||
|
|
a7baad33ba | ||
|
|
72c7ceb553 | ||
|
|
2991ce9317 | ||
|
|
0dee4df8fb | ||
|
|
69e6006436 | ||
|
|
4952c95c33 | ||
|
|
fc32d99327 | ||
|
|
ef3ac43c4a | ||
|
|
8f4ae14f2d | ||
|
|
85bce24e30 | ||
|
|
aada0ca5af | ||
|
|
7d5571ba5b | ||
|
|
a70416abac | ||
|
|
0134597747 | ||
|
|
449b462bf2 | ||
|
|
4fd0d7d8b5 | ||
|
|
959914c78c | ||
|
|
fef5b7548e | ||
|
|
af7af395f8 | ||
|
|
3cfe358102 | ||
|
|
02132fa495 | ||
|
|
75e1442fce | ||
|
|
e8fd597f29 | ||
|
|
21e8ddd1e6 | ||
|
|
a8e6d0a89f | ||
|
|
4d73aab090 | ||
|
|
834d9ec9a1 | ||
|
|
9efde9de29 | ||
|
|
1dc2636e45 | ||
|
|
8d6b4a2fd3 | ||
|
|
be41741b1e | ||
|
|
76c938762c | ||
|
|
3ec57a7259 | ||
|
|
bb9fe98a7e | ||
|
|
aac835f634 | ||
|
|
d459947949 | ||
|
|
a2f9d721f3 | ||
|
|
1cb951bd0b | ||
|
|
df327d4e64 | ||
|
|
1316e6bf5b | ||
|
|
890ce9ef95 | ||
|
|
df335b683c | ||
|
|
c5778b6fb6 | ||
|
|
e92e5dfe8f | ||
|
|
e4b202edd8 | ||
|
|
b7bbc97218 | ||
|
|
9884fe5c5e | ||
|
|
88a7b3251d | ||
|
|
af1abb7129 | ||
|
|
59c0a50289 | ||
|
|
28fb1b5fab | ||
|
|
6e4cf7c092 | ||
|
|
80c88b4fcc | ||
|
|
4e2d3b7da2 | ||
|
|
e561b3a4fc | ||
|
|
eedd8059d8 | ||
|
|
a1b2347784 | ||
|
|
ba1862478c | ||
|
|
0a0a47a5e4 | ||
|
|
3054c637dd | ||
|
|
5ca03af3e2 | ||
|
|
49c954aa68 | ||
|
|
452d22579a | ||
|
|
b6135fda74 | ||
|
|
9eace09e0e | ||
|
|
6f3cbb4e14 | ||
|
|
6a88bdb37d | ||
|
|
01e27a0d59 | ||
|
|
663a0a6165 | ||
|
|
d1d243fb14 | ||
|
|
b6cd2c215a | ||
|
|
1368b06afa | ||
|
|
d91700fd39 | ||
|
|
af47b211fd | ||
|
|
79b63e6d30 | ||
|
|
a3c5ea1f2b | ||
|
|
21d5440f98 | ||
|
|
e133fff03e | ||
|
|
bdc7367e29 | ||
|
|
44e60a705e | ||
|
|
a0d1f95171 | ||
|
|
587bf26d94 | ||
|
|
2f78c7c036 | ||
|
|
d25a719724 | ||
|
|
7c7a67fcc6 | ||
|
|
f0fa092c66 | ||
|
|
80c7ee6dab | ||
|
|
446c744462 | ||
|
|
ab9a6bd3c4 | ||
|
|
59bb5d15c8 | ||
|
|
67bc86c159 | ||
|
|
f26f1c38a2 | ||
|
|
5666db6b7a | ||
|
|
75f31ecc63 | ||
|
|
47b0f40e97 | ||
|
|
76c86e7b2f | ||
|
|
38b6367453 | ||
|
|
b7203f0ebb | ||
|
|
5f6442ba6b | ||
|
|
b4277e09e8 | ||
|
|
c377fb7b0c | ||
|
|
bb72658e76 | ||
|
|
735a3d2eb2 | ||
|
|
7ac7e8f969 | ||
|
|
a8e3e6c8db | ||
|
|
e5b346e7fa | ||
|
|
1d6a049c5a | ||
|
|
a5b18640af | ||
|
|
e62873a650 | ||
|
|
ba915da22b | ||
|
|
33d6396a4e | ||
|
|
580e7599a0 | ||
|
|
9b086560cb | ||
|
|
d0fb69e67d | ||
|
|
1335c85de1 | ||
|
|
855098e30b | ||
|
|
e715ea5d79 | ||
|
|
213d6550d3 | ||
|
|
9a30108b75 | ||
|
|
9dc9c0fcb7 | ||
|
|
da02ab3eac | ||
|
|
7d7d5d24cc | ||
|
|
37dcf1c1cf | ||
|
|
692cee113c | ||
|
|
4d7c60a130 | ||
|
|
d1ce0f062e | ||
|
|
a0c95579b4 | ||
|
|
39b9dcfec9 | ||
|
|
1a930f7f80 | ||
|
|
6d4c81e68b | ||
|
|
07cda765f0 | ||
|
|
bc72cd612e | ||
|
|
0f9c4703cf | ||
|
|
b05504e1c4 | ||
|
|
fe3d4a9867 | ||
|
|
0aeeba808c | ||
|
|
6c7b648133 | ||
|
|
9fa37a9156 | ||
|
|
8e9c976e99 | ||
|
|
2a91bb0c87 | ||
|
|
bdc3a9ef02 | ||
|
|
2ad090c224 | ||
|
|
fe0d356675 | ||
|
|
11c6248b06 | ||
|
|
298e9cae65 | ||
|
|
2dfa4d1acd | ||
|
|
725a70327a | ||
|
|
b2a732197b | ||
|
|
2b57ffeb08 | ||
|
|
03276629c2 | ||
|
|
3ff76fbdab | ||
|
|
7560b7e167 | ||
|
|
08a8556fe7 | ||
|
|
13e924cc05 | ||
|
|
8e13a7b470 | ||
|
|
c34196538b | ||
|
|
b21c6f0b99 | ||
|
|
c6134b54ab | ||
|
|
000348f70f | ||
|
|
8bb4683bbe | ||
|
|
d38d9b21a9 | ||
|
|
1a48405491 | ||
|
|
bcae1d09a8 | ||
|
|
a8c6e79bc0 | ||
|
|
06d9f4f982 | ||
|
|
7df25df2af | ||
|
|
464d76a819 | ||
|
|
447f0b0ed7 | ||
|
|
0500036d0a | ||
|
|
261a9af4f5 | ||
|
|
04336dd039 | ||
|
|
a9310d4a39 | ||
|
|
a8704a05d6 | ||
|
|
597bf8be73 | ||
|
|
6ef4325eac | ||
|
|
1a07ae0936 | ||
|
|
bf3b5bdd7f | ||
|
|
17d23fa47c | ||
|
|
c8c50af54b | ||
|
|
51da26d21d | ||
|
|
802dd5174c | ||
|
|
f11348c965 | ||
|
|
a7d825158c | ||
|
|
297bfd776c | ||
|
|
ef032d406f | ||
|
|
980cb9522b | ||
|
|
55b7cd3d6a | ||
|
|
70cac17138 | ||
|
|
648595cf18 | ||
|
|
d609e881c2 | ||
|
|
1c168452a4 | ||
|
|
1ef5a2b481 | ||
|
|
33dfecfdef | ||
|
|
3aad5111b5 | ||
|
|
f99c83f69d | ||
|
|
5c6939429a | ||
|
|
1a7a460ba8 | ||
|
|
de1dfdcc38 | ||
|
|
2bdd1305ed | ||
|
|
c66ac2579e | ||
|
|
2d7461f609 | ||
|
|
c9b3c16c6f | ||
|
|
8dac6bb982 | ||
|
|
4104e7df8e | ||
|
|
2c38be2d13 | ||
|
|
e1fb7e5d98 | ||
|
|
9dae7c1566 | ||
|
|
47d38e1cca | ||
|
|
28a5ee32c4 | ||
|
|
95f62d529f | ||
|
|
a8f605c2e6 | ||
|
|
ea2f5009c8 | ||
|
|
a5e6ac2300 | ||
|
|
b150c9c6bc | ||
|
|
50a88e4826 | ||
|
|
6c68197e61 | ||
|
|
7068f70811 | ||
|
|
607cc90ce0 | ||
|
|
5f8f0232a9 | ||
|
|
56f05e2604 | ||
|
|
aacc7119bd | ||
|
|
083d7671d0 | ||
|
|
28333c1a21 | ||
|
|
e3231bbedb | ||
|
|
3ff0759bb9 | ||
|
|
b5d75d6e6a | ||
|
|
ce82579930 | ||
|
|
371ec582e1 | ||
|
|
3391db506a | ||
|
|
af9b88de11 | ||
|
|
73259c0bcb | ||
|
|
a0168515c3 | ||
|
|
20e8161038 | ||
|
|
11745ebd6c | ||
|
|
4768c9cbb3 | ||
|
|
6b104fbb8b | ||
|
|
4049c8e915 | ||
|
|
e55f4a2798 | ||
|
|
599220a931 | ||
|
|
0594ebaef7 | ||
|
|
24fd520ec3 | ||
|
|
4daf291619 | ||
|
|
6144d7e209 | ||
|
|
9fa718e58d | ||
|
|
ac2797c7a1 | ||
|
|
d8e7c2a932 | ||
|
|
20f6964b1f | ||
|
|
771efecaa2 | ||
|
|
2e842503e6 | ||
|
|
b738f8b143 | ||
|
|
aae1400929 | ||
|
|
5f9758e85f | ||
|
|
03e852f957 | ||
|
|
0bb8c0a1e3 | ||
|
|
f0777ead92 | ||
|
|
0f42916521 | ||
|
|
ee70e27f7d | ||
|
|
01ff562dcd | ||
|
|
1d4b826d03 | ||
|
|
d777549a1a | ||
|
|
3130b672b4 | ||
|
|
d4ac35b9aa | ||
|
|
80b70fd2df | ||
|
|
362437e75e | ||
|
|
f239c8f8c8 | ||
|
|
ae7fbd6112 | ||
|
|
47cbf3071d | ||
|
|
534f43872f | ||
|
|
fd79f1ea0e | ||
|
|
7879c7565e | ||
|
|
6e7224ee7c | ||
|
|
0988040172 | ||
|
|
cb3c86f87a | ||
|
|
265a29fa1a | ||
|
|
4765065eb0 | ||
|
|
563bfe9bf5 | ||
|
|
96ef5e1bde | ||
|
|
fe9749ba4f | ||
|
|
21315096d4 | ||
|
|
1738df9042 | ||
|
|
ec8d8e5a64 | ||
|
|
4a8170079e | ||
|
|
b7187c5e06 | ||
|
|
391eeb0e46 | ||
|
|
51d2d85c26 | ||
|
|
1532be9c1e | ||
|
|
643589b4a9 | ||
|
|
b4fa56fd96 | ||
|
|
bfdda847c4 | ||
|
|
a7444a9926 | ||
|
|
4430098e98 | ||
|
|
039a7badd1 | ||
|
|
6d5bdaadbd | ||
|
|
56765d3f5a | ||
|
|
6c667937c5 | ||
|
|
ebac06ebee | ||
|
|
fc89501a62 | ||
|
|
5567ad07fd | ||
|
|
a19cf1f27a | ||
|
|
4dff18e4a6 | ||
|
|
1fc2ef5f18 | ||
|
|
83582ae87f | ||
|
|
e4f2eecb3b | ||
|
|
978fd7c683 | ||
|
|
48c20c600a | ||
|
|
734436b02e | ||
|
|
9d9ef5fc9c | ||
|
|
9ac334ddd2 | ||
|
|
a6028f027a | ||
|
|
b17b8db25c | ||
|
|
eead52a5dd | ||
|
|
7286281a06 | ||
|
|
c4296ad4f1 | ||
|
|
c95f124578 | ||
|
|
75ab7b247b | ||
|
|
e1b70ff68f | ||
|
|
8149759852 | ||
|
|
826c471179 | ||
|
|
dadf4d4c13 | ||
|
|
d23444a86a | ||
|
|
ba2da17f1e | ||
|
|
5b3dd63b89 | ||
|
|
684db67733 | ||
|
|
0b76d1d036 | ||
|
|
15ea73a448 | ||
|
|
a35c4a5a95 | ||
|
|
7e4b84f016 | ||
|
|
2591d34260 | ||
|
|
d44092b209 | ||
|
|
8b7789cdb3 | ||
|
|
6c89a2aaeb | ||
|
|
324645b67c | ||
|
|
e401bb8e3c | ||
|
|
1c14c2237a | ||
|
|
9eba564ff6 | ||
|
|
142cea0cbb | ||
|
|
853a821497 | ||
|
|
dc626ed68b | ||
|
|
f560be1a9a | ||
|
|
ede7d6dbde | ||
|
|
5f6f722d6c | ||
|
|
f8da15287c | ||
|
|
fc99086c8f | ||
|
|
4e157fe956 | ||
|
|
279d5a00ce | ||
|
|
5a5a36083e | ||
|
|
56b287b2c8 | ||
|
|
72b5a105f8 | ||
|
|
0882528b82 | ||
|
|
32b46cdc2a | ||
|
|
de2b7e5daf | ||
|
|
6b702ef676 | ||
|
|
b1885138de | ||
|
|
1e844c642f | ||
|
|
2e1a5a85a3 | ||
|
|
3ea671986c | ||
|
|
083dac1300 | ||
|
|
1c503adced | ||
|
|
ef2c04baa8 | ||
|
|
897d9b4831 | ||
|
|
5988e847ce | ||
|
|
8d0845d92b | ||
|
|
ac03dab8de | ||
|
|
2b91f69c7d | ||
|
|
b936f67d87 | ||
|
|
152801f06f | ||
|
|
3f03adba72 | ||
|
|
13a07be728 | ||
|
|
6320384ecb | ||
|
|
43fab8a8b3 | ||
|
|
34b784d1e4 | ||
|
|
978b5f869d | ||
|
|
05613b9642 | ||
|
|
f5d0d54ded | ||
|
|
500da8099b | ||
|
|
b02c87ea50 | ||
|
|
f1b83f1c17 | ||
|
|
3a347fba21 | ||
|
|
2a563d7370 | ||
|
|
3e5d8d2b2d | ||
|
|
5310ce8465 | ||
|
|
34ff05d66e | ||
|
|
95fadf1300 | ||
|
|
3a702266e6 | ||
|
|
ad90adbc04 | ||
|
|
1caa341f8e | ||
|
|
e049ca1a85 | ||
|
|
87903f27ed | ||
|
|
c533f59405 | ||
|
|
cabbdbb5cf | ||
|
|
fb28204dfd | ||
|
|
2428e77969 | ||
|
|
1311e5fe58 | ||
|
|
5c5641d884 | ||
|
|
2d583d19c2 | ||
|
|
90cfb3496a | ||
|
|
a148cfe593 | ||
|
|
1d6d1121a7 | ||
|
|
f1a7ac3187 | ||
|
|
fda0bff14c | ||
|
|
8246e9c802 | ||
|
|
b3ac261746 | ||
|
|
60a8ef723b | ||
|
|
05ce17f8df | ||
|
|
a263d2fdcd | ||
|
|
551064b3a4 | ||
|
|
544b63cac5 | ||
|
|
4a7e9a2278 | ||
|
|
ed8be5225d | ||
|
|
091a9d9803 | ||
|
|
6dc5ef0cdc | ||
|
|
d8ada930c8 | ||
|
|
d06b4d7c9f | ||
|
|
c8ec55a5fe | ||
|
|
44010fe05b | ||
|
|
d0b7d91ca4 | ||
|
|
d272eec384 | ||
|
|
5e70d8df7e | ||
|
|
c63480c65b | ||
|
|
3ec2396ec1 | ||
|
|
22a5f921b8 | ||
|
|
170d55d64e | ||
|
|
4055a36db2 | ||
|
|
646fe40950 | ||
|
|
54d5d2899e | ||
|
|
43907fb129 | ||
|
|
c61eeca3e4 | ||
|
|
8bf281e153 | ||
|
|
06a3899325 | ||
|
|
b5e1d3f87a | ||
|
|
9ed787b4da | ||
|
|
4167e1fdb3 | ||
|
|
210d39c8b7 | ||
|
|
d8a0ab8879 | ||
|
|
9203f541be | ||
|
|
a4f6efc05d | ||
|
|
b38fde85f2 | ||
|
|
5e0d1fe25a | ||
|
|
7525bc2ead | ||
|
|
664dd0000c | ||
|
|
8f108d42d2 | ||
|
|
24ab48ef4c | ||
|
|
1a4eb3f870 | ||
|
|
815d4bf7eb | ||
|
|
ffe15763a7 | ||
|
|
6aaf178f0b | ||
|
|
720579dcd7 | ||
|
|
e978e1df52 | ||
|
|
5908890726 | ||
|
|
549f50ac3f | ||
|
|
9284837da8 | ||
|
|
7d95faa4f5 | ||
|
|
432ecc1d96 | ||
|
|
3a325399c6 | ||
|
|
f1e8afa314 | ||
|
|
73296eafcb | ||
|
|
4d96b385b1 | ||
|
|
b57a78dea4 | ||
|
|
e3f5e8fbb1 | ||
|
|
3e17bf3316 | ||
|
|
10758a9626 | ||
|
|
767ceed698 | ||
|
|
c63a09330f | ||
|
|
42c1210fba | ||
|
|
a3265ef9fd | ||
|
|
b7e7543be6 | ||
|
|
b45a11aa3d | ||
|
|
ea7b1b65d6 | ||
|
|
a71a51b848 | ||
|
|
fb7d9a7edf | ||
|
|
0752df26dc | ||
|
|
076d5c4f7f | ||
|
|
5e63ccc9f6 | ||
|
|
cd4cb12937 | ||
|
|
647182237e | ||
|
|
5db043ab4b | ||
|
|
f4f6253178 | ||
|
|
33c454ed5a | ||
|
|
6245d65ebc | ||
|
|
23ac83d2a8 | ||
|
|
ae20db0ec6 | ||
|
|
d37cdd8e50 | ||
|
|
fae516b38e | ||
|
|
1ca08f8bff | ||
|
|
5877b9616d | ||
|
|
9131edc43d | ||
|
|
1ef21700c0 | ||
|
|
f989d3b3ec | ||
|
|
46b889c572 | ||
|
|
de65e1631d | ||
|
|
39eddc7203 | ||
|
|
96277edcf1 | ||
|
|
8ae16a125e | ||
|
|
e02536071d | ||
|
|
fd6d630037 | ||
|
|
5793839a01 | ||
|
|
3bb247a135 | ||
|
|
7bc925d016 | ||
|
|
652c2dbcbe | ||
|
|
2e4613e702 | ||
|
|
2df0f03a9a | ||
|
|
ac79e131bc | ||
|
|
28460b3023 | ||
|
|
fdcef7b699 | ||
|
|
bc6f58cf26 | ||
|
|
4193d2e7fe | ||
|
|
85ac1bfb95 | ||
|
|
73a1765a11 | ||
|
|
d242e04e64 | ||
|
|
a2ee172058 | ||
|
|
5a6697866f | ||
|
|
406d7bcf80 | ||
|
|
7aa473712f | ||
|
|
226a91718b | ||
|
|
c6e61c20fe | ||
|
|
98d20bceeb | ||
|
|
e8a0095e50 | ||
|
|
a5639c380f | ||
|
|
06040f94ee | ||
|
|
92c1e3c1a5 | ||
|
|
c13cee2407 | ||
|
|
9b7a9a3ee0 | ||
|
|
b5d15aab08 | ||
|
|
5405ad566e | ||
|
|
0b7cf9e7f3 | ||
|
|
a8d47c54f9 | ||
|
|
934078a42c | ||
|
|
416cfb99da | ||
|
|
6b99d923d7 | ||
|
|
396b7dba73 | ||
|
|
957c538136 | ||
|
|
647e2cf64d | ||
|
|
ce1cce99bb | ||
|
|
634410294b | ||
|
|
2dc9109b11 | ||
|
|
dfa4d3f8ed | ||
|
|
d1df647dde | ||
|
|
529c4052ff | ||
|
|
7edd08035f | ||
|
|
3c175a6c8d | ||
|
|
83077514cb | ||
|
|
4d4f0e5cfc | ||
|
|
5a6cac43f5 | ||
|
|
0b26d68d0f | ||
|
|
34e4409e7f | ||
|
|
8579abb9c2 | ||
|
|
3d9223c2ee | ||
|
|
af6b30eaca | ||
|
|
dbee26aaad | ||
|
|
70a3b7f891 | ||
|
|
ac82640c6c | ||
|
|
3143242d13 | ||
|
|
eb98496a79 | ||
|
|
f1f31a1338 | ||
|
|
ca8472ac23 | ||
|
|
3d60bccae0 | ||
|
|
26ee3032f6 | ||
|
|
8b16304e84 | ||
|
|
b595f3b732 | ||
|
|
305920d1ac | ||
|
|
ec4bc357df | ||
|
|
01eee4d208 | ||
|
|
8499ced636 | ||
|
|
19a2326638 | ||
|
|
1ce2166843 | ||
|
|
5bcf8529ff | ||
|
|
e4ce19d22a | ||
|
|
095cfe9845 | ||
|
|
279b50d977 | ||
|
|
8b37229e4b | ||
|
|
b80e4302ba | ||
|
|
2325b1e4ba | ||
|
|
9abb211cdf | ||
|
|
1c97aaf87a | ||
|
|
606c62078f | ||
|
|
ab454e5ba4 | ||
|
|
88f73443ee | ||
|
|
77a2b6de8b | ||
|
|
b3e4efd96e | ||
|
|
e076470305 | ||
|
|
d691b15f4b | ||
|
|
11d31960c7 | ||
|
|
c740cce5e4 | ||
|
|
dc2c9aa662 | ||
|
|
d2e2e7511f | ||
|
|
fa21d66c41 | ||
|
|
23aee234f0 | ||
|
|
556ffa1099 | ||
|
|
8b2189daca | ||
|
|
e74bfb90a5 | ||
|
|
911b23ca24 | ||
|
|
d980da7247 | ||
|
|
c910307ce5 | ||
|
|
3f59ce3f93 | ||
|
|
16d720b62c | ||
|
|
8ee16d6f98 | ||
|
|
ba4c829b10 | ||
|
|
afadd51a14 | ||
|
|
66bdce3d04 | ||
|
|
4d49cb029f | ||
|
|
796e89c921 | ||
|
|
3e9c7cda21 | ||
|
|
e5960f6ce4 | ||
|
|
806a7f961d | ||
|
|
731c796254 | ||
|
|
dc41231fcc | ||
|
|
cc05220263 | ||
|
|
898c604b3b | ||
|
|
095ef51991 | ||
|
|
bce7eb1aad | ||
|
|
13cd8624b2 | ||
|
|
48e050d317 | ||
|
|
3ee7be1d58 | ||
|
|
c1595396c4 | ||
|
|
fe3a72c6cc | ||
|
|
26d113e8ad | ||
|
|
ee28b4eea5 | ||
|
|
7f3ce9b0b1 | ||
|
|
62734c4b72 | ||
|
|
3fe0886207 | ||
|
|
852da6d696 | ||
|
|
42a75a8238 | ||
|
|
46f459b4c7 | ||
|
|
b9291c6705 | ||
|
|
824ce6778f | ||
|
|
3c5968ef1a | ||
|
|
9c27680202 | ||
|
|
45c888e13d | ||
|
|
a686500df1 | ||
|
|
fd22e713ff | ||
|
|
f04977e7e1 | ||
|
|
b467c8a1ef | ||
|
|
b27f926310 | ||
|
|
fb09f53dc9 | ||
|
|
929411e49a | ||
|
|
31b699d521 | ||
|
|
cda52a58e3 | ||
|
|
c19c014f55 | ||
|
|
d5c0c12d78 | ||
|
|
c09b4980ad | ||
|
|
1ded9e1fc0 | ||
|
|
6c2eb959a6 | ||
|
|
2039100d3e | ||
|
|
6bc5fe2497 | ||
|
|
9e4cc73b1c | ||
|
|
16f1b4c784 | ||
|
|
bedda66949 | ||
|
|
d81aa5c051 | ||
|
|
c7403fd512 | ||
|
|
e6526288cd | ||
|
|
f12f8156bd | ||
|
|
7f3018ebf8 | ||
|
|
496232ed6d | ||
|
|
9d3d9a190b | ||
|
|
15e1c05791 | ||
|
|
b7d25a75c4 | ||
|
|
a71b5ef0a0 | ||
|
|
7356fc3dfc | ||
|
|
4763f3ea46 | ||
|
|
87d836617e | ||
|
|
4f648f8787 | ||
|
|
756f3ddb0f | ||
|
|
626d0266c8 | ||
|
|
56660d52f2 | ||
|
|
81864a6ab8 | ||
|
|
27656633df | ||
|
|
400f183597 | ||
|
|
a952dc38a3 | ||
|
|
c4cae02170 | ||
|
|
8ea2c627c4 | ||
|
|
064a142d76 | ||
|
|
4f1d087654 | ||
|
|
40d8a07acc | ||
|
|
d05a5978a0 | ||
|
|
6b48977e7b | ||
|
|
d1cce7df94 | ||
|
|
0e5f10c020 | ||
|
|
1f6cbd7408 | ||
|
|
18e19d7920 | ||
|
|
9e4a599c44 | ||
|
|
e78e649aa9 | ||
|
|
41d6ad2db9 | ||
|
|
38beaff01b | ||
|
|
d348d90fbe | ||
|
|
ae55c81dae | ||
|
|
54bfcff213 | ||
|
|
b2b22762ef | ||
|
|
a18886f196 | ||
|
|
df8d1fb32b | ||
|
|
81ce284264 | ||
|
|
39cfe39172 | ||
|
|
ca6994566d | ||
|
|
2ef8464a83 | ||
|
|
4de6cc3e4f | ||
|
|
b58f7d7461 | ||
|
|
d684ed076b | ||
|
|
d8a74ad8b7 | ||
|
|
f253bf1843 | ||
|
|
f409bfd72b | ||
|
|
8dd8e69c05 | ||
|
|
97b35d9269 | ||
|
|
042956ad48 | ||
|
|
a3f59d8115 | ||
|
|
b76e6a5023 | ||
|
|
3a5014da4b | ||
|
|
8a4597be6a | ||
|
|
fb247c3dd8 | ||
|
|
7dd988a7dc | ||
|
|
120a17241e | ||
|
|
07adee3c51 | ||
|
|
12e43dcc46 | ||
|
|
bc57845aaa | ||
|
|
8f1e116c0b | ||
|
|
5d6e20d897 | ||
|
|
7dbb6c7c8e | ||
|
|
d6bca4d6ca | ||
|
|
f19a727a02 | ||
|
|
89b3290068 | ||
|
|
083bfe05c0 | ||
|
|
5f81d650e9 | ||
|
|
2e1388475e | ||
|
|
2389bb9621 | ||
|
|
b30754f561 | ||
|
|
b9594db832 | ||
|
|
597ab37ba6 | ||
|
|
1b6843e72d | ||
|
|
87d0d872e0 | ||
|
|
b8509f7533 | ||
|
|
11a17fb9b1 | ||
|
|
eaf42b8abe | ||
|
|
a218b6b351 | ||
|
|
86660a5f17 | ||
|
|
6540fb968e | ||
|
|
c55fa13cdb | ||
|
|
ca1281ee10 | ||
|
|
ff7570aea4 | ||
|
|
1a90106bac | ||
|
|
a95dd86540 | ||
|
|
71c0406cf3 | ||
|
|
945c3c214d | ||
|
|
3d597cc3c3 | ||
|
|
8f1b018f0a | ||
|
|
e51fee081b | ||
|
|
805e8daa57 | ||
|
|
95c5c4b575 | ||
|
|
25e73db455 | ||
|
|
d10ba5edc3 | ||
|
|
6ed604bbb8 | ||
|
|
28a55f0a58 | ||
|
|
a9fffe6a73 | ||
|
|
4af6b65ac3 | ||
|
|
472e2d0acd | ||
|
|
303db293b7 | ||
|
|
d674eacd78 | ||
|
|
adaaa1e81d | ||
|
|
32cde5d8f1 | ||
|
|
04c702e1b8 | ||
|
|
eaf87294ce | ||
|
|
aa35c787b2 | ||
|
|
65bc23a516 | ||
|
|
57e395de71 | ||
|
|
7a7db97914 | ||
|
|
36ac268b96 | ||
|
|
8c9491f3ca | ||
|
|
28585644ea | ||
|
|
8198fad6d5 | ||
|
|
35060ce8dc | ||
|
|
896fa131f0 | ||
|
|
700d522c3c | ||
|
|
8e2d568a42 | ||
|
|
10dd8fad80 | ||
|
|
2e5ec5d249 | ||
|
|
0548bee8ad | ||
|
|
27241e217f | ||
|
|
c5efce36b6 | ||
|
|
41071f0ed4 | ||
|
|
8198e919b1 | ||
|
|
f0713ce01d | ||
|
|
87f25eacc5 | ||
|
|
22e30903b6 | ||
|
|
46a8107be5 | ||
|
|
2cf40c0e0b | ||
|
|
f7ad38dad5 | ||
|
|
5f2e4c3790 | ||
|
|
598e15af46 | ||
|
|
db214dfd73 | ||
|
|
42967905bc | ||
|
|
a2efac4dbe | ||
|
|
f737ad2d38 | ||
|
|
2667dc68d7 | ||
|
|
c4989e59b2 | ||
|
|
6ef4390e29 | ||
|
|
2fa284d37b | ||
|
|
440a5fafea | ||
|
|
11b751323e | ||
|
|
34a334236a | ||
|
|
81b50312e2 | ||
|
|
b89588ffb7 | ||
|
|
303572bc03 | ||
|
|
e3d92e1390 | ||
|
|
eaec85398f | ||
|
|
62c26c881d | ||
|
|
104b9d2d44 | ||
|
|
36955d6d4f | ||
|
|
92ec849fc1 | ||
|
|
bb9e8fa97b | ||
|
|
911727f7b7 | ||
|
|
bc2f131144 | ||
|
|
b8856476be | ||
|
|
37a43c8657 | ||
|
|
85f635c975 | ||
|
|
3af704c453 | ||
|
|
55c1ea750c | ||
|
|
cae6fb6731 | ||
|
|
4613730a19 | ||
|
|
74f25ee7f9 | ||
|
|
e6c0a04131 | ||
|
|
1eeaa17085 | ||
|
|
c3c5a86ea5 | ||
|
|
ffbdb0317f | ||
|
|
90d2265dae | ||
|
|
73892e894e | ||
|
|
cd8214ff4a | ||
|
|
baaabd00d6 | ||
|
|
672936e2d4 | ||
|
|
97ed70502c | ||
|
|
35cd3923a5 | ||
|
|
59e7474f75 | ||
|
|
8615cad711 | ||
|
|
fa540c0096 | ||
|
|
eabb99053c | ||
|
|
5c4781b5b4 | ||
|
|
406dbc36fa | ||
|
|
07f7b46a8a | ||
|
|
06552832cb | ||
|
|
6b1696f8df | ||
|
|
7829e3654c | ||
|
|
9c2e959ab0 | ||
|
|
86e675dbbd | ||
|
|
f6d8a3f229 | ||
|
|
2c1d585f8b | ||
|
|
b17c3f6e0f | ||
|
|
27f7745231 | ||
|
|
19329a9627 | ||
|
|
ce866c6d30 | ||
|
|
146dae6d43 | ||
|
|
4be1b2928b | ||
|
|
72899530cf | ||
|
|
9dcc23ee4e | ||
|
|
3a75682878 | ||
|
|
5aa11063a0 | ||
|
|
2037474fad | ||
|
|
02d17378a6 | ||
|
|
6ab34c3d0e | ||
|
|
d44ad541eb | ||
|
|
0b4f732160 | ||
|
|
54d0c4e8a8 | ||
|
|
ee0df29af0 | ||
|
|
60c6d6d33e | ||
|
|
5d6def75cc | ||
|
|
109307858a | ||
|
|
7d6055f06f | ||
|
|
788d497e87 | ||
|
|
cd016d3f7d | ||
|
|
e0a744b01d | ||
|
|
9c2d13b487 | ||
|
|
0da2682c68 | ||
|
|
d5a374f1fd | ||
|
|
9149aa1536 | ||
|
|
946d824995 | ||
|
|
6f89f7dc7f | ||
|
|
b6f9590d55 | ||
|
|
988d263ef1 | ||
|
|
471ece136e | ||
|
|
78826dc20b | ||
|
|
2a4b9c6c5c | ||
|
|
c3bfcad4df | ||
|
|
ca1985e11a | ||
|
|
9a35293126 | ||
|
|
ed5ad3c047 | ||
|
|
c849f618d5 | ||
|
|
b7ae95686e | ||
|
|
f9149d041e | ||
|
|
116d966d29 | ||
|
|
771b2117c4 | ||
|
|
8f1af0f5f9 | ||
|
|
69a3ad3c5b | ||
|
|
23cc3d9b06 | ||
|
|
c31b30bf83 | ||
|
|
f7ed48809c | ||
|
|
eeca3effee | ||
|
|
98017cb8bd | ||
|
|
de86418ede | ||
|
|
017c1ece89 | ||
|
|
d8f9f95cb4 | ||
|
|
ef1e598341 | ||
|
|
513142d487 | ||
|
|
592e87941d | ||
|
|
f5fd0fc5be | ||
|
|
af50e29e2c | ||
|
|
ec1e646de0 | ||
|
|
ab75f85555 | ||
|
|
05fae4be87 | ||
|
|
f4debeac47 | ||
|
|
7a0e1e9574 | ||
|
|
59b3d8b5bc | ||
|
|
25cced3b90 | ||
|
|
9fa13aeae3 | ||
|
|
89e63df1fb | ||
|
|
6fc275ca30 | ||
|
|
142a1462bb | ||
|
|
852a680c09 | ||
|
|
90d34cdfcf | ||
|
|
2f88cc9fe0 | ||
|
|
4f1b469cee | ||
|
|
8e0dfa9c6f | ||
|
|
64ae181bf6 | ||
|
|
5a8804f4bc | ||
|
|
c1956d13b5 | ||
|
|
71f3662ebc | ||
|
|
0aeb7ddbdf | ||
|
|
41c4a5376b | ||
|
|
6930456e2b | ||
|
|
8556bdec6a | ||
|
|
0faba39dfc | ||
|
|
7e829e88af | ||
|
|
3d0012a3bf | ||
|
|
b5bc8935a2 | ||
|
|
f8bb357e6c | ||
|
|
50460ed527 | ||
|
|
13b64c6a04 | ||
|
|
1767ef7a3a | ||
|
|
82903d2bd6 | ||
|
|
4cd54203a2 | ||
|
|
37baec2db6 | ||
|
|
32133fecc7 | ||
|
|
316dfe5e38 | ||
|
|
92ac93aac5 | ||
|
|
ca4d3df287 | ||
|
|
68fb599c73 | ||
|
|
7349b396ca | ||
|
|
561516e8d7 | ||
|
|
476cc5f661 | ||
|
|
badc306f33 | ||
|
|
783a94acce | ||
|
|
84c0ec7937 | ||
|
|
303157c586 | ||
|
|
685a58b807 | ||
|
|
89c7f72caf | ||
|
|
25d90f643a | ||
|
|
e67327953d | ||
|
|
4dcd6ee136 | ||
|
|
ab62f8fc59 | ||
|
|
b5b882d3b6 | ||
|
|
d52278e423 | ||
|
|
da079b23dc | ||
|
|
ccda637e3c | ||
|
|
a4267f76e8 | ||
|
|
2bbc65cc59 | ||
|
|
2f5e3efada | ||
|
|
1807ba4e7b | ||
|
|
30e24f19d9 | ||
|
|
cdde0f8c43 | ||
|
|
2b1d6c268c | ||
|
|
18b3fd3256 | ||
|
|
aefd70ce49 | ||
|
|
eb2422e6be | ||
|
|
a45902bd4f | ||
|
|
dd77cc00b7 | ||
|
|
e5406cae24 | ||
|
|
97103207b6 | ||
|
|
b27ecf54d7 | ||
|
|
318c16f321 | ||
|
|
f1373e3bea | ||
|
|
e1a8772af6 | ||
|
|
d34383e842 | ||
|
|
2f77007dbe | ||
|
|
411e7507a3 | ||
|
|
3c4cfc9a1b | ||
|
|
2bb669d7de | ||
|
|
b8b3a933ab | ||
|
|
2280558303 | ||
|
|
3b01614a38 | ||
|
|
1f5d81ef93 | ||
|
|
cebba6909c | ||
|
|
3336eb9f45 | ||
|
|
0da9e0f834 | ||
|
|
502702b3bc | ||
|
|
bb6b3b965d | ||
|
|
fcfd1f82d6 | ||
|
|
19c036f50a | ||
|
|
ad11681369 | ||
|
|
bc3fda71a4 | ||
|
|
96a3f2920d | ||
|
|
099f56e779 | ||
|
|
402301e165 | ||
|
|
190610c6c9 | ||
|
|
732405f738 | ||
|
|
a7ee323035 | ||
|
|
ad977d4d26 | ||
|
|
3831a04ce0 | ||
|
|
eac53c836c | ||
|
|
2cd6c0b9fc | ||
|
|
8d9cfd3678 | ||
|
|
a2c3c1086c | ||
|
|
ff71fe6e93 | ||
|
|
861a2982db | ||
|
|
54c1f2d9a2 | ||
|
|
459cc03aae | ||
|
|
80173a9d43 | ||
|
|
bdea29df04 | ||
|
|
7b332d93ee | ||
|
|
518ad3eb60 | ||
|
|
0f199556df | ||
|
|
3a266cf322 | ||
|
|
d65d76653c | ||
|
|
08bab0a1b9 | ||
|
|
481eeb8338 | ||
|
|
e0e82dabf3 | ||
|
|
10691b3d03 | ||
|
|
8a1f358505 | ||
|
|
b7f30ee136 | ||
|
|
d90edab59f | ||
|
|
d1d5b45073 | ||
|
|
daab81fa2c | ||
|
|
f386039d5a | ||
|
|
af70dd5dfe | ||
|
|
51c194fb8f | ||
|
|
6994af411d | ||
|
|
5209a7a1b0 | ||
|
|
0152ae79ef | ||
|
|
3b98d85143 | ||
|
|
46586ba960 | ||
|
|
cfd1194a6e | ||
|
|
9b7977a217 | ||
|
|
5f2ca784c9 | ||
|
|
3d7676e292 | ||
|
|
25b7f0b7ee | ||
|
|
3d245c7ce3 | ||
|
|
134a688e4a | ||
|
|
8ac23c080f | ||
|
|
fa3d2d005a | ||
|
|
da70af7ba0 | ||
|
|
930f903f5d | ||
|
|
16bea33695 | ||
|
|
dedc25d681 | ||
|
|
82433db1ea | ||
|
|
2047b2d7d7 | ||
|
|
2e91f64a96 | ||
|
|
1ad4b8b131 | ||
|
|
a63219466f | ||
|
|
9757fb06db | ||
|
|
ab4e6548dc | ||
|
|
351a55c9ea | ||
|
|
e1c503836d | ||
|
|
8cafaa0e62 | ||
|
|
6064c6ceb5 | ||
|
|
b559816dca | ||
|
|
ba94b340dd | ||
|
|
beccbe8583 | ||
|
|
07be08d0f3 | ||
|
|
4bb9c4c559 | ||
|
|
2d9b75c6ae | ||
|
|
3aecb110a5 | ||
|
|
503d5a41b1 | ||
|
|
e943681baa | ||
|
|
0559946791 | ||
|
|
22a8e82108 | ||
|
|
a72ed11cb0 | ||
|
|
1a15b46d65 | ||
|
|
0068cd9825 | ||
|
|
290c0eb225 | ||
|
|
bc31cc550e | ||
|
|
f40a9d1dad | ||
|
|
218971e6f1 | ||
|
|
38dcdcad54 | ||
|
|
aeba189c1a | ||
|
|
09400fd66b | ||
|
|
f3e3838782 | ||
|
|
c7586b83e9 | ||
|
|
b23017b6bf | ||
|
|
a6a58b299b | ||
|
|
b2c302cb2c | ||
|
|
126e42a022 | ||
|
|
22b30a0626 | ||
|
|
84362ea790 | ||
|
|
7be158722f | ||
|
|
f563ac0dcb | ||
|
|
16d261d956 | ||
|
|
15817ce61a | ||
|
|
dedd87f20a | ||
|
|
fcd588cb01 | ||
|
|
6ebce2b2fd | ||
|
|
544e217347 | ||
|
|
9f599546de | ||
|
|
408c6a4040 | ||
|
|
2c7e0bd321 | ||
|
|
96b79397c2 | ||
|
|
ca1526405b | ||
|
|
80019a3ed8 | ||
|
|
d907136264 | ||
|
|
fbd21827fb | ||
|
|
4ddabb82ef | ||
|
|
c1da4d5207 | ||
|
|
a3c0d35b20 | ||
|
|
7c7055c0a0 | ||
|
|
dd9584f487 | ||
|
|
8459de7be2 | ||
|
|
b73044cd44 | ||
|
|
55bfdc000b | ||
|
|
5f3816212f | ||
|
|
a6dab76ff6 | ||
|
|
59d9e430b4 | ||
|
|
774e26eb19 | ||
|
|
f8e36c5468 | ||
|
|
9b7b65374d | ||
|
|
7bcf9bf5be | ||
|
|
d0b3e0bd7c | ||
|
|
04858f8835 | ||
|
|
78990a99e5 | ||
|
|
fe69a8e7bb | ||
|
|
002b896384 | ||
|
|
e128410d54 | ||
|
|
81e38eeb14 | ||
|
|
861e30e8d6 | ||
|
|
03cc6892ea | ||
|
|
3678c940eb | ||
|
|
c0f1966a2c | ||
|
|
c92bb78ae8 | ||
|
|
e63622341f | ||
|
|
669a76c921 | ||
|
|
4cbf2099fa | ||
|
|
046a00026c | ||
|
|
47a4ab2ed8 | ||
|
|
27ca84fd2a | ||
|
|
ae4bfd07da | ||
|
|
8384071ac3 | ||
|
|
1bf696a052 | ||
|
|
835451c02c | ||
|
|
75cdc7eb59 | ||
|
|
9ad54c6579 | ||
|
|
d2c1e9e2b0 | ||
|
|
0d052334e2 | ||
|
|
fdf57891fc | ||
|
|
009adaf528 | ||
|
|
cfc15582c1 | ||
|
|
22220493bd | ||
|
|
95cc3d2a7a | ||
|
|
4292a32ab9 | ||
|
|
68b9a690f2 | ||
|
|
8537dc2a85 | ||
|
|
c44205273c | ||
|
|
e4d10279fa | ||
|
|
01481ef5c9 | ||
|
|
e63342418f | ||
|
|
50987f47b0 | ||
|
|
4bd0872b2c | ||
|
|
203062a67a | ||
|
|
34b40d205e | ||
|
|
5245442b11 | ||
|
|
e6f56cb056 | ||
|
|
71681bfda1 | ||
|
|
1ad7e699a9 | ||
|
|
0ec880290b | ||
|
|
7215a563b1 | ||
|
|
616f17d08a | ||
|
|
d6cad372db | ||
|
|
865a6db828 | ||
|
|
174c4cc591 | ||
|
|
251a23d127 | ||
|
|
cab87a6860 | ||
|
|
b93ba6339a | ||
|
|
c3dcabcaad | ||
|
|
05be4da25c | ||
|
|
70c5b27d22 | ||
|
|
b78fe88c26 | ||
|
|
3d8c059a43 | ||
|
|
5018f899f7 | ||
|
|
59dfac2c9b | ||
|
|
1e94a597c9 | ||
|
|
dcf00d816d | ||
|
|
e4872d9f67 | ||
|
|
a86513e15d | ||
|
|
c5999f7186 | ||
|
|
bbf6f6f93d | ||
|
|
15011b7b20 | ||
|
|
1c3878829d | ||
|
|
fb0ebbb1e4 | ||
|
|
937211288e | ||
|
|
6912899f41 | ||
|
|
094ef38e27 | ||
|
|
36ad69091b | ||
|
|
351e825ba8 | ||
|
|
b338834146 | ||
|
|
87d8b4674a | ||
|
|
c49cd9ffde | ||
|
|
51e2f5c46b | ||
|
|
4822d3fd63 | ||
|
|
cbfb3b55e6 | ||
|
|
762d735618 | ||
|
|
2e1c4e9792 | ||
|
|
a8ceddc8e4 | ||
|
|
ba2b1ce581 | ||
|
|
4a02a90954 | ||
|
|
93eeeec2f3 | ||
|
|
b8f7555a73 | ||
|
|
efaa532162 | ||
|
|
428b52693a | ||
|
|
55e831f4b4 | ||
|
|
aada5f91cb | ||
|
|
b0edd0eb05 | ||
|
|
5b77ff4a4c | ||
|
|
ed6abcdf32 | ||
|
|
dc0a0f9dc2 | ||
|
|
02ec1d4fa2 | ||
|
|
d8f5255572 | ||
|
|
7424cc1352 | ||
|
|
41259f520f | ||
|
|
388b26dad2 | ||
|
|
ef83480f65 | ||
|
|
0498fd633a | ||
|
|
3479528d5b | ||
|
|
bb4c25ba68 | ||
|
|
bb0c991965 | ||
|
|
8412885e09 | ||
|
|
22e3dc634b | ||
|
|
e03d8f55ea | ||
|
|
40037d826d | ||
|
|
8d924eb300 | ||
|
|
07f3cd5644 | ||
|
|
f217650cec | ||
|
|
07de23f120 | ||
|
|
881931c6b1 | ||
|
|
6bbe818e9c | ||
|
|
f6d5f2e426 | ||
|
|
0e57a4caec | ||
|
|
b29e1ded64 | ||
|
|
6e54c49142 | ||
|
|
f7a1fb0a9d | ||
|
|
cfe33deb8e | ||
|
|
e891f117f3 | ||
|
|
6d628a77fd | ||
|
|
14217ff648 | ||
|
|
d6749dfb24 | ||
|
|
41d91a8f9b | ||
|
|
119cccc318 | ||
|
|
0a7ea27e9f | ||
|
|
420b16104c | ||
|
|
f64758eb03 | ||
|
|
d0ad3f0e37 | ||
|
|
5fde9777fb | ||
|
|
4b33b15dd2 | ||
|
|
2be99be4de | ||
|
|
625ab33bc8 | ||
|
|
9681372a84 | ||
|
|
0683911076 | ||
|
|
d703e48ecb | ||
|
|
015ed66967 | ||
|
|
0a4f7a55b8 | ||
|
|
a6e850e39f | ||
|
|
abecb4856f | ||
|
|
dbf88f8485 | ||
|
|
83af318ad0 | ||
|
|
7be1edd896 | ||
|
|
d6ca21273d | ||
|
|
dd934f0e34 | ||
|
|
612851fd48 | ||
|
|
755962c6a2 | ||
|
|
153dde4129 | ||
|
|
8f3a022b3c | ||
|
|
017bf187f9 | ||
|
|
7b8d7c9fe0 | ||
|
|
d319c79abb | ||
|
|
1e62528d2a | ||
|
|
7ae7c19a24 | ||
|
|
0dce5fab7b | ||
|
|
d1dc0fae19 | ||
|
|
dd33209b1c | ||
|
|
c3fe101571 | ||
|
|
2c1b20b5d5 | ||
|
|
c1ab004c0c | ||
|
|
348f2e1df8 | ||
|
|
3098c8c155 | ||
|
|
55ed483b13 | ||
|
|
84ea95181d | ||
|
|
163f076565 | ||
|
|
7d2d6a79e4 | ||
|
|
40695b3288 | ||
|
|
bd9d5b92dc | ||
|
|
39a62d1cb9 | ||
|
|
fd5b902e1f | ||
|
|
c7877c67ff | ||
|
|
5d0369958f | ||
|
|
6da0106aa2 | ||
|
|
e0bfda2b14 | ||
|
|
e5c2ee5587 | ||
|
|
d4391f99bb | ||
|
|
346c713cd2 | ||
|
|
ec44a480a8 | ||
|
|
f417ddb8e0 | ||
|
|
1b43f151df | ||
|
|
6b5bed7786 | ||
|
|
c906cbe2c2 | ||
|
|
1dea5f1624 | ||
|
|
0fea7f2a22 | ||
|
|
45d19e3a4e | ||
|
|
8a46dda07f | ||
|
|
166b41196b | ||
|
|
a13e50445e | ||
|
|
c00b19bc5b | ||
|
|
9577e4e505 | ||
|
|
72dc75512b | ||
|
|
f4a052ee2b | ||
|
|
080f9da9c8 | ||
|
|
1c1cc670f8 | ||
|
|
141d9b779d | ||
|
|
dbfa812d37 | ||
|
|
026ffde807 | ||
|
|
e07d7e3874 | ||
|
|
413e996e92 | ||
|
|
bebf2c9bee | ||
|
|
afb927258c | ||
|
|
a15f46c972 | ||
|
|
92b0ed6599 | ||
|
|
29ab493cbe | ||
|
|
90915f8dc0 | ||
|
|
1f4471ccc3 | ||
|
|
5d4ec2154b | ||
|
|
9d9f30e41f | ||
|
|
e3a8bb23c1 | ||
|
|
787ab0a2e4 | ||
|
|
c140ecf14b | ||
|
|
980116b33b | ||
|
|
142a7659bd | ||
|
|
6fbe588aee | ||
|
|
17eb8237da | ||
|
|
f11cee7197 | ||
|
|
1b28326c5e | ||
|
|
6ffb6db248 | ||
|
|
9b0825739a | ||
|
|
b42f7fce44 | ||
|
|
30f491795d | ||
|
|
55f7557b5e | ||
|
|
5a0de7088b | ||
|
|
9319b9f433 | ||
|
|
b8942acfed | ||
|
|
a0d8fbede3 | ||
|
|
8e8930d51f | ||
|
|
09b7578c35 | ||
|
|
fdd4d09714 | ||
|
|
0deb6e8bea | ||
|
|
586bad4876 | ||
|
|
47fd317a11 | ||
|
|
0c229ede24 | ||
|
|
64a6ee0f3b | ||
|
|
364f78d20c | ||
|
|
ff0d163a95 | ||
|
|
a7a37437a1 | ||
|
|
e333f5f726 | ||
|
|
c822450792 | ||
|
|
e6d77273c4 | ||
|
|
c203c3542f | ||
|
|
d218f07940 | ||
|
|
0da2999c75 | ||
|
|
67cfd22084 | ||
|
|
d97484b30b | ||
|
|
d5ad888b0a | ||
|
|
a7cec1627f | ||
|
|
e42a1b7b88 | ||
|
|
dc03283e14 | ||
|
|
4aef7643a0 | ||
|
|
ac17cf0121 | ||
|
|
e19061c902 | ||
|
|
db71993905 | ||
|
|
3f8f7ed32c | ||
|
|
b7ac86fab4 | ||
|
|
79e0f972e7 | ||
|
|
255d754045 | ||
|
|
385c13148d | ||
|
|
4de3445c58 | ||
|
|
a2314074d3 | ||
|
|
460ff571e4 | ||
|
|
3a10d235c1 | ||
|
|
207d2bb4d3 | ||
|
|
2076faaa6c | ||
|
|
ec166c906f | ||
|
|
c0e69d7a99 | ||
|
|
a8348aa674 | ||
|
|
b17590942d | ||
|
|
062b3fd0fe | ||
|
|
f2f40e4758 | ||
|
|
922feec7f4 | ||
|
|
64636a6d60 | ||
|
|
ac46a4a413 | ||
|
|
7593d855cb | ||
|
|
27c7f6589c | ||
|
|
edad4f9cf1 | ||
|
|
14e9b71ebb |
@@ -1,8 +1,6 @@
|
||||
[run]
|
||||
source = ./InvenTree
|
||||
omit =
|
||||
# Do not run coverage on migration files
|
||||
*/migrations/*
|
||||
InvenTree/manage.py
|
||||
InvenTree/setup.py
|
||||
InvenTree/InvenTree/middleware.py
|
||||
|
||||
48
.github/workflows/coverage.yaml
vendored
Normal file
48
.github/workflows/coverage.yaml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Perform CI checks, and calculate code coverage
|
||||
|
||||
name: SQLite
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
jobs:
|
||||
|
||||
# Run tests on SQLite database
|
||||
# These tests are used for code coverage analysis
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INVENTREE_DB_NAME: './test_db.sqlite'
|
||||
INVENTREE_DB_ENGINE: django.db.backends.sqlite3
|
||||
INVENTREE_DEBUG: info
|
||||
INVENTREE_MEDIA_ROOT: ./media
|
||||
INVENTREE_STATIC_ROOT: ./static
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
pip3 install invoke
|
||||
invoke install
|
||||
- name: Coverage Tests
|
||||
run: |
|
||||
invoke coverage
|
||||
- name: Data Import Export
|
||||
run: |
|
||||
invoke migrate
|
||||
invoke import-fixtures
|
||||
invoke export-records -f data.json
|
||||
rm test_db.sqlite
|
||||
invoke migrate
|
||||
invoke import-records -f data.json
|
||||
- name: Check Migration Files
|
||||
run: python3 ci/check_migration_files.py
|
||||
- name: Upload Coverage Report
|
||||
run: coveralls
|
||||
18
.github/workflows/docker_build.yaml
vendored
Normal file
18
.github/workflows/docker_build.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Test that the docker file builds correctly
|
||||
|
||||
name: Docker
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
jobs:
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Server Image
|
||||
run: cd docker/inventree && docker build . --tag inventree:$(date +%s)
|
||||
- name: Build nginx Image
|
||||
run: cd docker/nginx && docker build . --tag nxinx:$(date +%s)
|
||||
|
||||
38
.github/workflows/docker_publish.yaml
vendored
Normal file
38
.github/workflows/docker_publish.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Publish docker images to dockerhub
|
||||
|
||||
name: Docker Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
server_image:
|
||||
name: Push InvenTree web server image to dockerhub
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: inventree/inventree
|
||||
tag_with_ref: true
|
||||
context: docker/inventree
|
||||
|
||||
nginx_image:
|
||||
name: Push InvenTree nginx image to dockerhub
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: inventree/nginx
|
||||
tag_with_ref: true
|
||||
context: docker/nginx
|
||||
51
.github/workflows/mysql.yaml
vendored
Normal file
51
.github/workflows/mysql.yaml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
# MySQL Unit Testing
|
||||
|
||||
name: MySQL
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
# Database backend configuration
|
||||
INVENTREE_DB_ENGINE: django.db.backends.mysql
|
||||
INVENTREE_DB_NAME: inventree
|
||||
INVENTREE_DB_USER: root
|
||||
INVENTREE_DB_PASSWORD: password
|
||||
INVENTREE_DB_HOST: '127.0.0.1'
|
||||
INVENTREE_DB_PORT: 3306
|
||||
INVENTREE_DEBUG: info
|
||||
INVENTREE_MEDIA_ROOT: ./media
|
||||
INVENTREE_STATIC_ROOT: ./static
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: inventree
|
||||
MYSQL_USER: inventree
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
ports:
|
||||
- 3306:3306
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get install mysql-server libmysqlclient-dev
|
||||
pip3 install invoke
|
||||
pip3 install mysqlclient
|
||||
invoke install
|
||||
- name: Run Tests
|
||||
run: invoke test
|
||||
47
.github/workflows/postgresql.yaml
vendored
Normal file
47
.github/workflows/postgresql.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# PostgreSQL Unit Testing
|
||||
|
||||
name: PostgreSQL
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
# Database backend configuration
|
||||
INVENTREE_DB_ENGINE: django.db.backends.postgresql
|
||||
INVENTREE_DB_NAME: inventree
|
||||
INVENTREE_DB_USER: inventree
|
||||
INVENTREE_DB_PASSWORD: password
|
||||
INVENTREE_DB_HOST: '127.0.0.1'
|
||||
INVENTREE_DB_PORT: 5432
|
||||
INVENTREE_DEBUG: info
|
||||
INVENTREE_MEDIA_ROOT: ./media
|
||||
INVENTREE_STATIC_ROOT: ./static
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_USER: inventree
|
||||
POSTGRES_PASSWORD: password
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get install libpq-dev
|
||||
pip3 install invoke
|
||||
pip3 install psycopg2
|
||||
invoke install
|
||||
- name: Run Tests
|
||||
run: invoke test
|
||||
27
.github/workflows/style.yaml
vendored
Normal file
27
.github/workflows/style.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Style Checks
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
jobs:
|
||||
style:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install flake8==3.8.3
|
||||
pip install pep8-naming==0.11.1
|
||||
- name: flake8
|
||||
run: |
|
||||
flake8 InvenTree
|
||||
@@ -1,17 +0,0 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
formats: all
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: 3.5
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
27
.travis.yml
27
.travis.yml
@@ -1,27 +0,0 @@
|
||||
dist: xenial
|
||||
|
||||
language: python
|
||||
python:
|
||||
- 3.6
|
||||
- 3.7
|
||||
|
||||
addons:
|
||||
apt-packages:
|
||||
- sqlite3
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install gettext
|
||||
- make install
|
||||
- make migrate
|
||||
- cd InvenTree && python3 manage.py createsuperuser --username InvenTreeAdmin --email admin@inventree.com --noinput && cd ..
|
||||
|
||||
script:
|
||||
- cd InvenTree && python3 manage.py makemigrations && cd ..
|
||||
- python3 ci/check_migration_files.py
|
||||
- make coverage
|
||||
- make translate
|
||||
- make style
|
||||
|
||||
after_success:
|
||||
- coveralls
|
||||
@@ -6,7 +6,7 @@ No pushing to master! New featues must be submitted in a separate branch (one br
|
||||
|
||||
## Include Migration Files
|
||||
|
||||
Any required migration files **must** be included in the commit, or the pull-request will be rejected. If you change the underlying database schema, make sure you run `make migrate` and commit the migration files before submitting the PR.
|
||||
Any required migration files **must** be included in the commit, or the pull-request will be rejected. If you change the underlying database schema, make sure you run `invoke migrate` and commit the migration files before submitting the PR.
|
||||
|
||||
## Update Translation Files
|
||||
|
||||
@@ -14,7 +14,7 @@ Any PRs which update translatable strings (i.e. text strings that will appear in
|
||||
|
||||
*This does not mean that all translations must be provided, but that the translation files must include locations for the translated strings to be written.*
|
||||
|
||||
To perform this step, simply run `make_translate` from the top level directory before submitting the PR.
|
||||
To perform this step, simply run `invoke translate` from the top level directory before submitting the PR.
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -22,9 +22,8 @@ Any new code should be covered by unit tests - a submitted PR may not be accepte
|
||||
|
||||
## Documentation
|
||||
|
||||
New features or updates to existing features should be accompanied by user documentation.
|
||||
A PR with associated documentation should link to the matching PR at https://github.com/inventree/InvenTree.github.io
|
||||
New features or updates to existing features should be accompanied by user documentation. A PR with associated documentation should link to the matching PR at https://github.com/inventree/inventree-docs/
|
||||
|
||||
## Code Style
|
||||
|
||||
Sumbitted Python code is automatically checked against PEP style guidelines. Locally you can run `make style` to ensure the style checks will pass, before submitting the PR.
|
||||
Sumbitted Python code is automatically checked against PEP style guidelines. Locally you can run `invoke style` to ensure the style checks will pass, before submitting the PR.
|
||||
|
||||
@@ -5,7 +5,9 @@ Main JSON interface views
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.http import JsonResponse
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
@@ -16,15 +18,16 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .views import AjaxView
|
||||
from .version import inventreeVersion, inventreeInstanceName
|
||||
from .version import inventreeVersion, inventreeApiVersion, inventreeInstanceName
|
||||
from .status import is_worker_running
|
||||
|
||||
from plugins import plugins as inventree_plugins
|
||||
|
||||
# Load barcode plugins
|
||||
print("Loading barcode plugins")
|
||||
barcode_plugins = inventree_plugins.load_barcode_plugins()
|
||||
|
||||
print("Loading action plugins")
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
logger.info("Loading action plugins...")
|
||||
action_plugins = inventree_plugins.load_action_plugins()
|
||||
|
||||
|
||||
@@ -33,17 +36,38 @@ class InfoView(AjaxView):
|
||||
Use to confirm that the server is running, etc.
|
||||
"""
|
||||
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
data = {
|
||||
'server': 'InvenTree',
|
||||
'version': inventreeVersion(),
|
||||
'instance': inventreeInstanceName(),
|
||||
'apiVersion': inventreeApiVersion(),
|
||||
'worker_running': is_worker_running(),
|
||||
}
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
class NotFoundView(AjaxView):
|
||||
"""
|
||||
Simple JSON view when accessing an invalid API view.
|
||||
"""
|
||||
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
data = {
|
||||
'details': _('API endpoint not found'),
|
||||
'url': request.build_absolute_uri(),
|
||||
}
|
||||
|
||||
return JsonResponse(data, status=404)
|
||||
|
||||
|
||||
class AttachmentMixin:
|
||||
"""
|
||||
Mixin for creating attachment objects,
|
||||
@@ -100,66 +124,3 @@ class ActionPluginView(APIView):
|
||||
'error': _("No matching action found"),
|
||||
"action": action,
|
||||
})
|
||||
|
||||
|
||||
class BarcodePluginView(APIView):
|
||||
"""
|
||||
Endpoint for handling barcode scan requests.
|
||||
|
||||
Barcode data are decoded by the client application,
|
||||
and sent to this endpoint (as a JSON object) for validation.
|
||||
|
||||
A barcode could follow the internal InvenTree barcode format,
|
||||
or it could match to a third-party barcode format (e.g. Digikey).
|
||||
|
||||
"""
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
]
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
response = {}
|
||||
|
||||
barcode_data = request.data.get('barcode', None)
|
||||
|
||||
print("Barcode data:")
|
||||
print(barcode_data)
|
||||
|
||||
if barcode_data is None:
|
||||
response['error'] = _('No barcode data provided')
|
||||
else:
|
||||
# Look for a barcode plugin that knows how to handle the data
|
||||
for plugin_class in barcode_plugins:
|
||||
|
||||
# Instantiate the plugin with the provided plugin data
|
||||
plugin = plugin_class(barcode_data)
|
||||
|
||||
if plugin.validate():
|
||||
|
||||
# Plugin should return a dict response
|
||||
response = plugin.decode()
|
||||
|
||||
if type(response) is dict:
|
||||
if 'success' not in response.keys() and 'error' not in response.keys():
|
||||
response['success'] = _('Barcode successfully decoded')
|
||||
else:
|
||||
response = {
|
||||
'error': _('Barcode plugin returned incorrect response')
|
||||
}
|
||||
|
||||
response['plugin'] = plugin.plugin_name()
|
||||
response['hash'] = plugin.hash()
|
||||
|
||||
break
|
||||
|
||||
if 'error' not in response and 'success' not in response:
|
||||
response = {
|
||||
'error': _('Unknown barcode format'),
|
||||
}
|
||||
|
||||
# Include the original barcode data
|
||||
response['barcode_data'] = barcode_data
|
||||
|
||||
return Response(response)
|
||||
|
||||
94
InvenTree/InvenTree/api_tester.py
Normal file
94
InvenTree/InvenTree/api_tester.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Helper functions for performing API unit tests
|
||||
"""
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
|
||||
class InvenTreeAPITestCase(APITestCase):
|
||||
"""
|
||||
Base class for running InvenTree API tests
|
||||
"""
|
||||
|
||||
# User information
|
||||
username = 'testuser'
|
||||
password = 'mypassword'
|
||||
email = 'test@testing.com'
|
||||
|
||||
superuser = False
|
||||
auto_login = True
|
||||
|
||||
# Set list of roles automatically associated with the user
|
||||
roles = []
|
||||
|
||||
def setUp(self):
|
||||
|
||||
super().setUp()
|
||||
|
||||
# Create a user to log in with
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
email=self.email
|
||||
)
|
||||
|
||||
# Create a group for the user
|
||||
self.group = Group.objects.create(name='my_test_group')
|
||||
self.user.groups.add(self.group)
|
||||
|
||||
if self.superuser:
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
|
||||
for role in self.roles:
|
||||
self.assignRole(role)
|
||||
|
||||
if self.auto_login:
|
||||
self.client.login(username=self.username, password=self.password)
|
||||
|
||||
def assignRole(self, role):
|
||||
"""
|
||||
Set the user roles for the registered user
|
||||
"""
|
||||
|
||||
# role is of the format 'rule.permission' e.g. 'part.add'
|
||||
|
||||
rule, perm = role.split('.')
|
||||
|
||||
for ruleset in self.group.rule_sets.all():
|
||||
|
||||
if ruleset.name == rule:
|
||||
|
||||
if perm == 'view':
|
||||
ruleset.can_view = True
|
||||
elif perm == 'change':
|
||||
ruleset.can_change = True
|
||||
elif perm == 'delete':
|
||||
ruleset.can_delete = True
|
||||
elif perm == 'add':
|
||||
ruleset.can_add = True
|
||||
|
||||
ruleset.save()
|
||||
break
|
||||
|
||||
def get(self, url, data={}, code=200):
|
||||
"""
|
||||
Issue a GET request
|
||||
"""
|
||||
|
||||
response = self.client.get(url, data, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, code)
|
||||
|
||||
return response
|
||||
|
||||
def post(self, url, data):
|
||||
"""
|
||||
Issue a POST request
|
||||
"""
|
||||
|
||||
response = self.client.post(url, data=data, format='json')
|
||||
|
||||
return response
|
||||
44
InvenTree/InvenTree/apps.py
Normal file
44
InvenTree/InvenTree/apps.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.core.exceptions import AppRegistryNotReady
|
||||
|
||||
import InvenTree.tasks
|
||||
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
class InvenTreeConfig(AppConfig):
|
||||
name = 'InvenTree'
|
||||
|
||||
def ready(self):
|
||||
|
||||
self.start_background_tasks()
|
||||
|
||||
def start_background_tasks(self):
|
||||
|
||||
try:
|
||||
from django_q.models import Schedule
|
||||
except (AppRegistryNotReady):
|
||||
return
|
||||
|
||||
logger.info("Starting background tasks...")
|
||||
|
||||
InvenTree.tasks.schedule_task(
|
||||
'InvenTree.tasks.delete_successful_tasks',
|
||||
schedule_type=Schedule.DAILY,
|
||||
)
|
||||
|
||||
InvenTree.tasks.schedule_task(
|
||||
'InvenTree.tasks.check_for_updates',
|
||||
schedule_type=Schedule.DAILY
|
||||
)
|
||||
|
||||
InvenTree.tasks.schedule_task(
|
||||
'InvenTree.tasks.heartbeat',
|
||||
schedule_type=Schedule.MINUTES,
|
||||
minutes=15
|
||||
)
|
||||
@@ -7,8 +7,56 @@ Provides extra global data to all templates.
|
||||
from InvenTree.status_codes import SalesOrderStatus, PurchaseOrderStatus
|
||||
from InvenTree.status_codes import BuildStatus, StockStatus
|
||||
|
||||
import InvenTree.status
|
||||
|
||||
from users.models import RuleSet
|
||||
|
||||
|
||||
def health_status(request):
|
||||
"""
|
||||
Provide system health status information to the global context.
|
||||
|
||||
- Not required for AJAX requests
|
||||
- Do not provide if it is already provided to the context
|
||||
"""
|
||||
|
||||
if request.path.endswith('.js'):
|
||||
# Do not provide to script requests
|
||||
return {}
|
||||
|
||||
if hasattr(request, '_inventree_health_status'):
|
||||
# Do not duplicate efforts
|
||||
return {}
|
||||
|
||||
request._inventree_health_status = True
|
||||
|
||||
status = {
|
||||
'django_q_running': InvenTree.status.is_worker_running(),
|
||||
}
|
||||
|
||||
all_healthy = True
|
||||
|
||||
for k in status.keys():
|
||||
if status[k] is not True:
|
||||
all_healthy = False
|
||||
|
||||
status['system_healthy'] = all_healthy
|
||||
|
||||
status['up_to_date'] = InvenTree.version.isInvenTreeUpToDate()
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def status_codes(request):
|
||||
"""
|
||||
Provide status code enumerations.
|
||||
"""
|
||||
|
||||
if hasattr(request, '_inventree_status_codes'):
|
||||
# Do not duplicate efforts
|
||||
return {}
|
||||
|
||||
request._inventree_status_codes = True
|
||||
|
||||
return {
|
||||
# Expose the StatusCode classes to the templates
|
||||
@@ -17,3 +65,52 @@ def status_codes(request):
|
||||
'BuildStatus': BuildStatus,
|
||||
'StockStatus': StockStatus,
|
||||
}
|
||||
|
||||
|
||||
def user_roles(request):
|
||||
"""
|
||||
Return a map of the current roles assigned to the user.
|
||||
|
||||
Roles are denoted by their simple names, and then the permission type.
|
||||
|
||||
Permissions can be access as follows:
|
||||
|
||||
- roles.part.view
|
||||
- roles.build.delete
|
||||
|
||||
Each value will return a boolean True / False
|
||||
"""
|
||||
|
||||
user = request.user
|
||||
|
||||
roles = {
|
||||
}
|
||||
|
||||
if user.is_superuser:
|
||||
for ruleset in RuleSet.RULESET_MODELS.keys():
|
||||
roles[ruleset] = {
|
||||
'view': True,
|
||||
'add': True,
|
||||
'change': True,
|
||||
'delete': True,
|
||||
}
|
||||
else:
|
||||
for group in user.groups.all():
|
||||
for rule in group.rule_sets.all():
|
||||
|
||||
# Ensure the role name is in the dict
|
||||
if rule.name not in roles:
|
||||
roles[rule.name] = {
|
||||
'view': user.is_superuser,
|
||||
'add': user.is_superuser,
|
||||
'change': user.is_superuser,
|
||||
'delete': user.is_superuser
|
||||
}
|
||||
|
||||
# Roles are additive across groups
|
||||
roles[rule.name]['view'] |= rule.can_view
|
||||
roles[rule.name]['add'] |= rule.can_add
|
||||
roles[rule.name]['change'] |= rule.can_change
|
||||
roles[rule.name]['delete'] |= rule.can_delete
|
||||
|
||||
return {'roles': roles}
|
||||
|
||||
21
InvenTree/InvenTree/exchange.py
Normal file
21
InvenTree/InvenTree/exchange.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from djmoney.contrib.exchange.backends.base import BaseExchangeBackend
|
||||
|
||||
|
||||
class InvenTreeManualExchangeBackend(BaseExchangeBackend):
|
||||
"""
|
||||
Backend for manually updating currency exchange rates
|
||||
|
||||
See the documentation for django-money: https://github.com/django-money/django-money
|
||||
|
||||
Specifically: https://github.com/django-money/django-money/tree/master/djmoney/contrib/exchange/backends
|
||||
"""
|
||||
|
||||
name = "inventree"
|
||||
url = None
|
||||
|
||||
def get_rates(self, **kwargs):
|
||||
"""
|
||||
Do not get any rates...
|
||||
"""
|
||||
|
||||
return {}
|
||||
@@ -5,13 +5,16 @@ from __future__ import unicode_literals
|
||||
|
||||
from .validators import allowable_url_schemes
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.forms.fields import URLField as FormURLField
|
||||
from django.db import models as models
|
||||
from django.core import validators
|
||||
from django import forms
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from InvenTree.helpers import normalize
|
||||
import InvenTree.helpers
|
||||
|
||||
|
||||
class InvenTreeURLFormField(FormURLField):
|
||||
@@ -31,6 +34,34 @@ class InvenTreeURLField(models.URLField):
|
||||
})
|
||||
|
||||
|
||||
class DatePickerFormField(forms.DateField):
|
||||
"""
|
||||
Custom date-picker field
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
help_text = kwargs.get('help_text', _('Enter date'))
|
||||
label = kwargs.get('label', None)
|
||||
required = kwargs.get('required', False)
|
||||
initial = kwargs.get('initial', None)
|
||||
|
||||
widget = forms.DateInput(
|
||||
attrs={
|
||||
'type': 'date',
|
||||
}
|
||||
)
|
||||
|
||||
forms.DateField.__init__(
|
||||
self,
|
||||
required=required,
|
||||
initial=initial,
|
||||
help_text=help_text,
|
||||
widget=widget,
|
||||
label=label
|
||||
)
|
||||
|
||||
|
||||
def round_decimal(value, places):
|
||||
"""
|
||||
Round value to the specified number of places.
|
||||
@@ -55,7 +86,7 @@ class RoundingDecimalFormField(forms.DecimalField):
|
||||
"""
|
||||
|
||||
if type(value) == Decimal:
|
||||
return normalize(value)
|
||||
return InvenTree.helpers.normalize(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ Helper forms which subclass Django forms to provide additional functionality
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django import forms
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Field
|
||||
from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText
|
||||
from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText, StrictButton, Div
|
||||
from django.contrib.auth.models import User
|
||||
from common.models import ColorTheme
|
||||
from part.models import PartCategory
|
||||
|
||||
|
||||
class HelperForm(forms.ModelForm):
|
||||
@@ -26,6 +28,7 @@ class HelperForm(forms.ModelForm):
|
||||
self.helper = FormHelper()
|
||||
|
||||
self.helper.form_tag = False
|
||||
self.helper.form_show_errors = True
|
||||
|
||||
"""
|
||||
Create a default 'layout' for this form.
|
||||
@@ -37,6 +40,12 @@ class HelperForm(forms.ModelForm):
|
||||
|
||||
self.rebuild_layout()
|
||||
|
||||
def is_valid(self):
|
||||
|
||||
valid = super(HelperForm, self).is_valid()
|
||||
|
||||
return valid
|
||||
|
||||
def rebuild_layout(self):
|
||||
|
||||
layouts = []
|
||||
@@ -114,6 +123,7 @@ class DeleteForm(forms.Form):
|
||||
confirm_delete = forms.BooleanField(
|
||||
required=False,
|
||||
initial=False,
|
||||
label=_('Confirm delete'),
|
||||
help_text=_('Confirm item deletion')
|
||||
)
|
||||
|
||||
@@ -146,6 +156,7 @@ class SetPasswordForm(HelperForm):
|
||||
required=True,
|
||||
initial='',
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
|
||||
label=_('Enter password'),
|
||||
help_text=_('Enter new password'))
|
||||
|
||||
confirm_password = forms.CharField(max_length=100,
|
||||
@@ -153,6 +164,7 @@ class SetPasswordForm(HelperForm):
|
||||
required=True,
|
||||
initial='',
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
|
||||
label=_('Confirm password'),
|
||||
help_text=_('Confirm new password'))
|
||||
|
||||
class Meta:
|
||||
@@ -161,3 +173,66 @@ class SetPasswordForm(HelperForm):
|
||||
'enter_password',
|
||||
'confirm_password'
|
||||
]
|
||||
|
||||
|
||||
class ColorThemeSelectForm(forms.ModelForm):
|
||||
""" Form for setting color theme """
|
||||
|
||||
name = forms.ChoiceField(choices=(), required=False)
|
||||
|
||||
class Meta:
|
||||
model = ColorTheme
|
||||
fields = [
|
||||
'name'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ColorThemeSelectForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Populate color themes choices
|
||||
self.fields['name'].choices = ColorTheme.get_color_themes_choices()
|
||||
|
||||
self.helper = FormHelper()
|
||||
# Form rendering
|
||||
self.helper.form_show_labels = False
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
Div(Field('name'),
|
||||
css_class='col-sm-6',
|
||||
style='width: 200px;'),
|
||||
Div(StrictButton(_('Apply Theme'), css_class='btn btn-primary', type='submit'),
|
||||
css_class='col-sm-6',
|
||||
style='width: auto;'),
|
||||
css_class='row',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class SettingCategorySelectForm(forms.ModelForm):
|
||||
""" Form for setting category settings """
|
||||
|
||||
category = forms.ModelChoiceField(queryset=PartCategory.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = PartCategory
|
||||
fields = [
|
||||
'category'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SettingCategorySelectForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
# Form rendering
|
||||
self.helper.form_show_labels = False
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
Div(Field('category'),
|
||||
css_class='col-sm-6',
|
||||
style='width: 70%;'),
|
||||
Div(StrictButton(_('Select Category'), css_class='btn btn-primary', type='submit'),
|
||||
css_class='col-sm-6',
|
||||
style='width: 30%; padding-left: 0;'),
|
||||
css_class='row',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -12,13 +12,25 @@ from decimal import Decimal
|
||||
|
||||
from wsgiref.util import FileWrapper
|
||||
from django.http import StreamingHttpResponse
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.exceptions import ValidationError, FieldError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .version import inventreeVersion, inventreeInstanceName
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
import InvenTree.version
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
from .settings import MEDIA_URL, STATIC_URL
|
||||
|
||||
|
||||
def getSetting(key, backup_value=None):
|
||||
"""
|
||||
Shortcut for reading a setting value from the database
|
||||
"""
|
||||
|
||||
return InvenTreeSetting.get_setting(key, backup_value=backup_value)
|
||||
|
||||
|
||||
def generateTestKey(test_name):
|
||||
"""
|
||||
Generate a test 'key' for a given test name.
|
||||
@@ -108,6 +120,19 @@ def str2bool(text, test=True):
|
||||
return str(text).lower() in ['0', 'n', 'no', 'none', 'f', 'false', 'off', ]
|
||||
|
||||
|
||||
def is_bool(text):
|
||||
"""
|
||||
Determine if a string value 'looks' like a boolean.
|
||||
"""
|
||||
|
||||
if str2bool(text, True):
|
||||
return True
|
||||
elif str2bool(text, False):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def isNull(text):
|
||||
"""
|
||||
Test if a string 'looks' like a null value.
|
||||
@@ -242,7 +267,7 @@ def WrapWithQuotes(text, quote='"'):
|
||||
return text
|
||||
|
||||
|
||||
def MakeBarcode(object_name, object_data):
|
||||
def MakeBarcode(object_name, object_pk, object_data={}, **kwargs):
|
||||
""" Generate a string for a barcode. Adds some global InvenTree parameters.
|
||||
|
||||
Args:
|
||||
@@ -255,12 +280,20 @@ def MakeBarcode(object_name, object_data):
|
||||
json string of the supplied data plus some other data
|
||||
"""
|
||||
|
||||
data = {
|
||||
'tool': 'InvenTree',
|
||||
'version': inventreeVersion(),
|
||||
'instance': inventreeInstanceName(),
|
||||
object_name: object_data
|
||||
}
|
||||
brief = kwargs.get('brief', True)
|
||||
|
||||
data = {}
|
||||
|
||||
if brief:
|
||||
data[object_name] = object_pk
|
||||
else:
|
||||
data['tool'] = 'InvenTree'
|
||||
data['version'] = InvenTree.version.inventreeVersion()
|
||||
data['instance'] = InvenTree.version.inventreeInstanceName()
|
||||
|
||||
# Ensure PK is included
|
||||
object_data['id'] = object_pk
|
||||
data[object_name] = object_data
|
||||
|
||||
return json.dumps(data, sort_keys=True)
|
||||
|
||||
@@ -304,7 +337,7 @@ def DownloadFile(data, filename, content_type='application/text'):
|
||||
return response
|
||||
|
||||
|
||||
def ExtractSerialNumbers(serials, expected_quantity):
|
||||
def extract_serial_numbers(serials, expected_quantity):
|
||||
""" Attempt to extract serial numbers from an input string.
|
||||
- Serial numbers must be integer values
|
||||
- Serial numbers must be positive
|
||||
@@ -349,28 +382,24 @@ def ExtractSerialNumbers(serials, expected_quantity):
|
||||
if a < b:
|
||||
for n in range(a, b + 1):
|
||||
if n in numbers:
|
||||
errors.append(_('Duplicate serial: {n}'.format(n=n)))
|
||||
errors.append(_('Duplicate serial: {n}').format(n=n))
|
||||
else:
|
||||
numbers.append(n)
|
||||
else:
|
||||
errors.append(_("Invalid group: {g}".format(g=group)))
|
||||
errors.append(_("Invalid group: {g}").format(g=group))
|
||||
|
||||
except ValueError:
|
||||
errors.append(_("Invalid group: {g}".format(g=group)))
|
||||
errors.append(_("Invalid group: {g}").format(g=group))
|
||||
continue
|
||||
else:
|
||||
errors.append(_("Invalid group: {g}".format(g=group)))
|
||||
errors.append(_("Invalid group: {g}").format(g=group))
|
||||
continue
|
||||
|
||||
else:
|
||||
try:
|
||||
n = int(group)
|
||||
if n in numbers:
|
||||
errors.append(_("Duplicate serial: {n}".format(n=n)))
|
||||
else:
|
||||
numbers.append(n)
|
||||
except ValueError:
|
||||
errors.append(_("Invalid group: {g}".format(g=group)))
|
||||
if group in numbers:
|
||||
errors.append(_("Duplicate serial: {g}".format(g=group)))
|
||||
else:
|
||||
numbers.append(group)
|
||||
|
||||
if len(errors) > 0:
|
||||
raise ValidationError(errors)
|
||||
@@ -380,6 +409,155 @@ def ExtractSerialNumbers(serials, expected_quantity):
|
||||
|
||||
# The number of extracted serial numbers must match the expected quantity
|
||||
if not expected_quantity == len(numbers):
|
||||
raise ValidationError([_("Number of unique serial number ({s}) must match quantity ({q})".format(s=len(numbers), q=expected_quantity))])
|
||||
raise ValidationError([_("Number of unique serial number ({s}) must match quantity ({q})").format(s=len(numbers), q=expected_quantity)])
|
||||
|
||||
return numbers
|
||||
|
||||
|
||||
def validateFilterString(value, model=None):
|
||||
"""
|
||||
Validate that a provided filter string looks like a list of comma-separated key=value pairs
|
||||
|
||||
These should nominally match to a valid database filter based on the model being filtered.
|
||||
|
||||
e.g. "category=6, IPN=12"
|
||||
e.g. "part__name=widget"
|
||||
|
||||
The ReportTemplate class uses the filter string to work out which items a given report applies to.
|
||||
For example, an acceptance test report template might only apply to stock items with a given IPN,
|
||||
so the string could be set to:
|
||||
|
||||
filters = "IPN = ACME0001"
|
||||
|
||||
Returns a map of key:value pairs
|
||||
"""
|
||||
|
||||
# Empty results map
|
||||
results = {}
|
||||
|
||||
value = str(value).strip()
|
||||
|
||||
if not value or len(value) == 0:
|
||||
return results
|
||||
|
||||
groups = value.split(',')
|
||||
|
||||
for group in groups:
|
||||
group = group.strip()
|
||||
|
||||
pair = group.split('=')
|
||||
|
||||
if not len(pair) == 2:
|
||||
raise ValidationError(
|
||||
"Invalid group: {g}".format(g=group)
|
||||
)
|
||||
|
||||
k, v = pair
|
||||
|
||||
k = k.strip()
|
||||
v = v.strip()
|
||||
|
||||
if not k or not v:
|
||||
raise ValidationError(
|
||||
"Invalid group: {g}".format(g=group)
|
||||
)
|
||||
|
||||
results[k] = v
|
||||
|
||||
# If a model is provided, verify that the provided filters can be used against it
|
||||
if model is not None:
|
||||
try:
|
||||
model.objects.filter(**results)
|
||||
except FieldError as e:
|
||||
raise ValidationError(
|
||||
str(e),
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def addUserPermission(user, permission):
|
||||
"""
|
||||
Shortcut function for adding a certain permission to a user.
|
||||
"""
|
||||
|
||||
perm = Permission.objects.get(codename=permission)
|
||||
user.user_permissions.add(perm)
|
||||
|
||||
|
||||
def addUserPermissions(user, permissions):
|
||||
"""
|
||||
Shortcut function for adding multiple permissions to a user.
|
||||
"""
|
||||
|
||||
for permission in permissions:
|
||||
addUserPermission(user, permission)
|
||||
|
||||
|
||||
def getMigrationFileNames(app):
|
||||
"""
|
||||
Return a list of all migration filenames for provided app
|
||||
"""
|
||||
|
||||
local_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
migration_dir = os.path.join(local_dir, '..', app, 'migrations')
|
||||
|
||||
files = os.listdir(migration_dir)
|
||||
|
||||
# Regex pattern for migration files
|
||||
pattern = r"^[\d]+_.*\.py$"
|
||||
|
||||
migration_files = []
|
||||
|
||||
for f in files:
|
||||
if re.match(pattern, f):
|
||||
migration_files.append(f)
|
||||
|
||||
return migration_files
|
||||
|
||||
|
||||
def getOldestMigrationFile(app, exclude_extension=True, ignore_initial=True):
|
||||
"""
|
||||
Return the filename associated with the oldest migration
|
||||
"""
|
||||
|
||||
oldest_num = -1
|
||||
oldest_file = None
|
||||
|
||||
for f in getMigrationFileNames(app):
|
||||
|
||||
if ignore_initial and f.startswith('0001_initial'):
|
||||
continue
|
||||
|
||||
num = int(f.split('_')[0])
|
||||
|
||||
if oldest_file is None or num < oldest_num:
|
||||
oldest_num = num
|
||||
oldest_file = f
|
||||
|
||||
if exclude_extension:
|
||||
oldest_file = oldest_file.replace('.py', '')
|
||||
|
||||
return oldest_file
|
||||
|
||||
|
||||
def getNewestMigrationFile(app, exclude_extension=True):
|
||||
"""
|
||||
Return the filename associated with the newest migration
|
||||
"""
|
||||
|
||||
newest_file = None
|
||||
newest_num = -1
|
||||
|
||||
for f in getMigrationFileNames(app):
|
||||
num = int(f.split('_')[0])
|
||||
|
||||
if newest_file is None or num > newest_num:
|
||||
newest_num = num
|
||||
newest_file = f
|
||||
|
||||
if exclude_extension:
|
||||
newest_file = newest_file.replace('.py', '')
|
||||
|
||||
return newest_file
|
||||
|
||||
42
InvenTree/InvenTree/management/commands/wait_for_db.py
Normal file
42
InvenTree/InvenTree/management/commands/wait_for_db.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Custom management command, wait for the database to be ready!
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from django.db import connection
|
||||
from django.db.utils import OperationalError, ImproperlyConfigured
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
django command to pause execution until the database is ready
|
||||
"""
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
|
||||
self.stdout.write("Waiting for database...")
|
||||
|
||||
connected = False
|
||||
|
||||
while not connected:
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
try:
|
||||
connection.ensure_connection()
|
||||
|
||||
connected = True
|
||||
|
||||
except OperationalError as e:
|
||||
self.stdout.write(f"Could not connect to database: {e}")
|
||||
except ImproperlyConfigured as e:
|
||||
self.stdout.write(f"Improperly configured: {e}")
|
||||
else:
|
||||
if not connection.is_usable():
|
||||
self.stdout.write("Database configuration is not usable")
|
||||
|
||||
if connected:
|
||||
self.stdout.write("Database connection sucessful!")
|
||||
@@ -8,7 +8,7 @@ import operator
|
||||
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
class AuthRequiredMiddleware(object):
|
||||
@@ -47,7 +47,12 @@ class AuthRequiredMiddleware(object):
|
||||
|
||||
authorized = False
|
||||
|
||||
if 'Authorization' in request.headers.keys():
|
||||
# Allow static files to be accessed without auth
|
||||
# Important for e.g. login page
|
||||
if request.path_info.startswith('/static/'):
|
||||
authorized = True
|
||||
|
||||
elif 'Authorization' in request.headers.keys():
|
||||
auth = request.headers['Authorization'].strip()
|
||||
|
||||
if auth.startswith('Token') and len(auth.split()) == 2:
|
||||
@@ -56,7 +61,7 @@ class AuthRequiredMiddleware(object):
|
||||
# Does the provided token match a valid user?
|
||||
if Token.objects.filter(key=token).exists():
|
||||
|
||||
allowed = ['/api/', '/media/', '/static/']
|
||||
allowed = ['/api/', '/media/']
|
||||
|
||||
# Only allow token-auth for /media/ or /static/ dirs!
|
||||
if any([request.path_info.startswith(a) for a in allowed]):
|
||||
@@ -91,6 +96,8 @@ class QueryCountMiddleware(object):
|
||||
To enable this middleware, set 'log_queries: True' in the local InvenTree config file.
|
||||
|
||||
Reference: https://www.dabapps.com/blog/logging-sql-queries-django-13/
|
||||
|
||||
Note: 2020-08-15 - This is no longer used, instead we now rely on the django-debug-toolbar addon
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
|
||||
@@ -56,19 +56,20 @@ class InvenTreeAttachment(models.Model):
|
||||
def __str__(self):
|
||||
return os.path.basename(self.attachment.name)
|
||||
|
||||
attachment = models.FileField(upload_to=rename_attachment,
|
||||
attachment = models.FileField(upload_to=rename_attachment, verbose_name=_('Attachment'),
|
||||
help_text=_('Select file to attach'))
|
||||
|
||||
comment = models.CharField(blank=True, max_length=100, help_text=_('File comment'))
|
||||
comment = models.CharField(blank=True, max_length=100, verbose_name=_('Comment'), help_text=_('File comment'))
|
||||
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True, null=True,
|
||||
verbose_name=_('User'),
|
||||
help_text=_('User'),
|
||||
)
|
||||
|
||||
upload_date = models.DateField(auto_now_add=True, null=True, blank=True)
|
||||
upload_date = models.DateField(auto_now_add=True, null=True, blank=True, verbose_name=_('upload date'))
|
||||
|
||||
@property
|
||||
def basename(self):
|
||||
@@ -102,12 +103,16 @@ class InvenTreeTree(MPTTModel):
|
||||
name = models.CharField(
|
||||
blank=False,
|
||||
max_length=100,
|
||||
validators=[validate_tree_name]
|
||||
validators=[validate_tree_name],
|
||||
verbose_name=_("Name"),
|
||||
help_text=_("Name"),
|
||||
)
|
||||
|
||||
description = models.CharField(
|
||||
blank=False,
|
||||
max_length=250
|
||||
blank=True,
|
||||
max_length=250,
|
||||
verbose_name=_("Description"),
|
||||
help_text=_("Description (optional)")
|
||||
)
|
||||
|
||||
# When a category is deleted, graft the children onto its parent
|
||||
@@ -115,6 +120,7 @@ class InvenTreeTree(MPTTModel):
|
||||
on_delete=models.DO_NOTHING,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_("parent"),
|
||||
related_name='children')
|
||||
|
||||
@property
|
||||
|
||||
72
InvenTree/InvenTree/permissions.py
Normal file
72
InvenTree/InvenTree/permissions.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from rest_framework import permissions
|
||||
|
||||
import users.models
|
||||
|
||||
|
||||
class RolePermission(permissions.BasePermission):
|
||||
"""
|
||||
Role mixin for API endpoints, allowing us to specify the user "role"
|
||||
which is required for certain operations.
|
||||
|
||||
Each endpoint can have one or more of the following actions:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- PATCH
|
||||
- DELETE
|
||||
|
||||
Specify the required "role" using the role_required attribute.
|
||||
|
||||
e.g.
|
||||
|
||||
role_required = "part"
|
||||
|
||||
The RoleMixin class will then determine if the user has the required permission
|
||||
to perform the specified action.
|
||||
|
||||
For example, a DELETE action will be rejected unless the user has the "part.remove" permission
|
||||
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
"""
|
||||
Determine if the current user has the specified permissions
|
||||
"""
|
||||
|
||||
user = request.user
|
||||
|
||||
# Superuser can do it all
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
# Map the request method to a permission type
|
||||
rolemap = {
|
||||
'GET': 'view',
|
||||
'OPTIONS': 'view',
|
||||
'POST': 'add',
|
||||
'PUT': 'change',
|
||||
'PATCH': 'change',
|
||||
'DELETE': 'delete',
|
||||
}
|
||||
|
||||
permission = rolemap[request.method]
|
||||
|
||||
try:
|
||||
# Extract the model name associated with this request
|
||||
model = view.serializer_class.Meta.model
|
||||
|
||||
app_label = model._meta.app_label
|
||||
model_name = model._meta.model_name
|
||||
|
||||
table = f"{app_label}_{model_name}"
|
||||
except AttributeError:
|
||||
# We will assume that if the serializer class does *not* have a Meta,
|
||||
# then we don't need a permission
|
||||
return True
|
||||
|
||||
result = users.models.RuleSet.check_table_permission(user, table, permission)
|
||||
|
||||
return result
|
||||
43
InvenTree/InvenTree/plugins.py
Normal file
43
InvenTree/InvenTree/plugins.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import inspect
|
||||
import importlib
|
||||
import pkgutil
|
||||
|
||||
|
||||
def iter_namespace(pkg):
|
||||
|
||||
return pkgutil.iter_modules(pkg.__path__, pkg.__name__ + ".")
|
||||
|
||||
|
||||
def get_modules(pkg):
|
||||
# Return all modules in a given package
|
||||
return [importlib.import_module(name) for finder, name, ispkg in iter_namespace(pkg)]
|
||||
|
||||
|
||||
def get_classes(module):
|
||||
# Return all classes in a given module
|
||||
return inspect.getmembers(module, inspect.isclass)
|
||||
|
||||
|
||||
def get_plugins(pkg, baseclass):
|
||||
"""
|
||||
Return a list of all modules under a given package.
|
||||
|
||||
- Modules must be a subclass of the provided 'baseclass'
|
||||
- Modules must have a non-empty PLUGIN_NAME parameter
|
||||
"""
|
||||
|
||||
plugins = []
|
||||
|
||||
modules = get_modules(pkg)
|
||||
|
||||
# Iterate through each module in the package
|
||||
for mod in modules:
|
||||
# Iterate through each class in the module
|
||||
for item in get_classes(mod):
|
||||
plugin = item[1]
|
||||
if issubclass(plugin, baseclass) and plugin.PLUGIN_NAME:
|
||||
plugins.append(plugin)
|
||||
|
||||
return plugins
|
||||
@@ -8,6 +8,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
@@ -50,3 +53,32 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
|
||||
instance.clean()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class InvenTreeAttachmentSerializerField(serializers.FileField):
|
||||
"""
|
||||
Override the DRF native FileField serializer,
|
||||
to remove the leading server path.
|
||||
|
||||
For example, the FileField might supply something like:
|
||||
|
||||
http://127.0.0.1:8000/media/foo/bar.jpg
|
||||
|
||||
Whereas we wish to return:
|
||||
|
||||
/media/foo/bar.jpg
|
||||
|
||||
Why? You can't handle the why!
|
||||
|
||||
Actually, if the server process is serving the data at 127.0.0.1,
|
||||
but a proxy service (e.g. nginx) is then providing DNS lookup to the outside world,
|
||||
then an attachment which prefixes the "address" of the internal server
|
||||
will not be accessible from the outside world.
|
||||
"""
|
||||
|
||||
def to_representation(self, value):
|
||||
|
||||
if not value:
|
||||
return None
|
||||
|
||||
return os.path.join(str(settings.MEDIA_URL), str(value))
|
||||
|
||||
@@ -11,42 +11,152 @@ database setup in this file.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import yaml
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import yaml
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def eprint(*args, **kwargs):
|
||||
""" Print a warning message to stderr """
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
def _is_true(x):
|
||||
# Shortcut function to determine if a value "looks" like a boolean
|
||||
return str(x).lower() in ['1', 'y', 'yes', 't', 'true']
|
||||
|
||||
|
||||
def get_setting(environment_var, backup_val, default_value=None):
|
||||
"""
|
||||
Helper function for retrieving a configuration setting value
|
||||
|
||||
- First preference is to look for the environment variable
|
||||
- Second preference is to look for the value of the settings file
|
||||
- Third preference is the default value
|
||||
"""
|
||||
|
||||
val = os.getenv(environment_var)
|
||||
|
||||
if val is not None:
|
||||
return val
|
||||
|
||||
if backup_val is not None:
|
||||
return backup_val
|
||||
|
||||
return default_value
|
||||
|
||||
|
||||
# Determine if we are running in "test" mode e.g. "manage.py test"
|
||||
TESTING = 'test' in sys.argv
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
cfg_filename = os.path.join(BASE_DIR, 'config.yaml')
|
||||
# Specify where the "config file" is located.
|
||||
# By default, this is 'config.yaml'
|
||||
|
||||
cfg_filename = os.getenv('INVENTREE_CONFIG_FILE')
|
||||
|
||||
if cfg_filename:
|
||||
cfg_filename = cfg_filename.strip()
|
||||
cfg_filename = os.path.abspath(cfg_filename)
|
||||
|
||||
else:
|
||||
# Config file is *not* specified - use the default
|
||||
cfg_filename = os.path.join(BASE_DIR, 'config.yaml')
|
||||
|
||||
if not os.path.exists(cfg_filename):
|
||||
CONFIG = {}
|
||||
eprint("Warning: config.yaml not found - using default settings")
|
||||
else:
|
||||
with open(cfg_filename, 'r') as cfg:
|
||||
CONFIG = yaml.safe_load(cfg)
|
||||
print("InvenTree configuration file 'config.yaml' not found - creating default file")
|
||||
|
||||
# Read the autogenerated key-file
|
||||
key_file = open(os.path.join(BASE_DIR, 'secret_key.txt'), 'r')
|
||||
cfg_template = os.path.join(BASE_DIR, "config_template.yaml")
|
||||
shutil.copyfile(cfg_template, cfg_filename)
|
||||
print(f"Created config file {cfg_filename}")
|
||||
|
||||
SECRET_KEY = key_file.read().strip()
|
||||
with open(cfg_filename, 'r') as cfg:
|
||||
CONFIG = yaml.safe_load(cfg)
|
||||
|
||||
# Default action is to run the system in Debug mode
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = CONFIG.get('debug', True)
|
||||
DEBUG = _is_true(get_setting(
|
||||
'INVENTREE_DEBUG',
|
||||
CONFIG.get('debug', True)
|
||||
))
|
||||
|
||||
# Configure logging settings
|
||||
log_level = get_setting(
|
||||
'INVENTREE_LOG_LEVEL',
|
||||
CONFIG.get('log_level', 'DEBUG')
|
||||
)
|
||||
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format="%(asctime)s %(levelname)s %(message)s",
|
||||
)
|
||||
|
||||
if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
|
||||
log_level = 'WARNING'
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console'],
|
||||
'level': log_level,
|
||||
},
|
||||
}
|
||||
|
||||
# Get a logger instance for this setup file
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
"""
|
||||
Specify a secret key to be used by django.
|
||||
|
||||
Following options are tested, in descending order of preference:
|
||||
|
||||
A) Check for environment variable INVENTREE_SECRET_KEY => Use raw key data
|
||||
B) Check for environment variable INVENTREE_SECRET_KEY_FILE => Load key data from file
|
||||
C) Look for default key file "secret_key.txt"
|
||||
d) Create "secret_key.txt" if it does not exist
|
||||
"""
|
||||
|
||||
if os.getenv("INVENTREE_SECRET_KEY"):
|
||||
# Secret key passed in directly
|
||||
SECRET_KEY = os.getenv("INVENTREE_SECRET_KEY").strip()
|
||||
logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY")
|
||||
else:
|
||||
# Secret key passed in by file location
|
||||
key_file = os.getenv("INVENTREE_SECRET_KEY_FILE")
|
||||
|
||||
if key_file:
|
||||
key_file = os.path.abspath(key_file)
|
||||
else:
|
||||
# default secret key location
|
||||
key_file = os.path.join(BASE_DIR, "secret_key.txt")
|
||||
key_file = os.path.abspath(key_file)
|
||||
|
||||
if not os.path.exists(key_file):
|
||||
logger.info(f"Generating random key file at '{key_file}'")
|
||||
# Create a random key file
|
||||
with open(key_file, 'w') as f:
|
||||
options = string.digits + string.ascii_letters + string.punctuation
|
||||
key = ''.join([random.choice(options) for i in range(100)])
|
||||
f.write(key)
|
||||
|
||||
logger.info(f"Loading SECRET_KEY from '{key_file}'")
|
||||
|
||||
try:
|
||||
SECRET_KEY = open(key_file, "r").read().strip()
|
||||
except Exception:
|
||||
logger.exception(f"Couldn't load keyfile {key_file}")
|
||||
sys.exit(-1)
|
||||
|
||||
# List of allowed hosts (default = allow all)
|
||||
ALLOWED_HOSTS = CONFIG.get('allowed_hosts', ['*'])
|
||||
@@ -65,33 +175,40 @@ if cors_opt:
|
||||
if not CORS_ORIGIN_ALLOW_ALL:
|
||||
CORS_ORIGIN_WHITELIST = cors_opt.get('whitelist', [])
|
||||
|
||||
if DEBUG:
|
||||
# will output to your console
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s %(levelname)s %(message)s',
|
||||
# Web URL endpoint for served static files
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# The filesystem location for served static files
|
||||
STATIC_ROOT = os.path.abspath(
|
||||
get_setting(
|
||||
'INVENTREE_STATIC_ROOT',
|
||||
CONFIG.get('static_root', '/home/inventree/static')
|
||||
)
|
||||
)
|
||||
|
||||
# Does the user wish to use the sentry.io integration?
|
||||
sentry_opts = CONFIG.get('sentry', {})
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'InvenTree', 'static'),
|
||||
]
|
||||
|
||||
if sentry_opts.get('enabled', False):
|
||||
dsn = sentry_opts.get('dsn', None)
|
||||
# Color Themes Directory
|
||||
STATIC_COLOR_THEMES_DIR = os.path.join(STATIC_ROOT, 'css', 'color-themes')
|
||||
|
||||
if dsn is not None:
|
||||
# Try to import required modules (exit if not installed)
|
||||
try:
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
# Web URL endpoint for served media files
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
sentry_sdk.init(dsn=dsn, integrations=[DjangoIntegration()], send_default_pii=True)
|
||||
# The filesystem location for served static files
|
||||
MEDIA_ROOT = os.path.abspath(
|
||||
get_setting(
|
||||
'INVENTREE_MEDIA_ROOT',
|
||||
CONFIG.get('media_root', '/home/inventree/data/media')
|
||||
)
|
||||
)
|
||||
|
||||
except ModuleNotFoundError:
|
||||
print("sentry_sdk module not found. Install using 'pip install sentry-sdk'")
|
||||
sys.exit(-1)
|
||||
if DEBUG:
|
||||
logger.info("InvenTree running in DEBUG mode")
|
||||
|
||||
else:
|
||||
print("Warning: Sentry.io DSN not specified")
|
||||
logger.info(f"MEDIA_ROOT: '{MEDIA_ROOT}'")
|
||||
logger.info(f"STATIC_ROOT: '{STATIC_ROOT}'")
|
||||
|
||||
# Application definition
|
||||
|
||||
@@ -106,40 +223,37 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# InvenTree apps
|
||||
'common.apps.CommonConfig',
|
||||
'part.apps.PartConfig',
|
||||
'stock.apps.StockConfig',
|
||||
'company.apps.CompanyConfig',
|
||||
'build.apps.BuildConfig',
|
||||
'common.apps.CommonConfig',
|
||||
'company.apps.CompanyConfig',
|
||||
'label.apps.LabelConfig',
|
||||
'order.apps.OrderConfig',
|
||||
'part.apps.PartConfig',
|
||||
'report.apps.ReportConfig',
|
||||
'stock.apps.StockConfig',
|
||||
'users.apps.UsersConfig',
|
||||
'InvenTree.apps.InvenTreeConfig', # InvenTree app runs last
|
||||
|
||||
# Third part add-ons
|
||||
'django_filters', # Extended filter functionality
|
||||
'dbbackup', # Database backup / restore
|
||||
'rest_framework', # DRF (Django Rest Framework)
|
||||
'rest_framework.authtoken', # Token authentication for API
|
||||
'corsheaders', # Cross-origin Resource Sharing for DRF
|
||||
'crispy_forms', # Improved form rendering
|
||||
'import_export', # Import / export tables to file
|
||||
'django_cleanup', # Automatically delete orphaned MEDIA files
|
||||
'qr_code', # Generate QR codes
|
||||
'mptt', # Modified Preorder Tree Traversal
|
||||
'markdownx', # Markdown editing
|
||||
'markdownify', # Markdown template rendering
|
||||
'django_filters', # Extended filter functionality
|
||||
'dbbackup', # Database backup / restore
|
||||
'rest_framework', # DRF (Django Rest Framework)
|
||||
'rest_framework.authtoken', # Token authentication for API
|
||||
'corsheaders', # Cross-origin Resource Sharing for DRF
|
||||
'crispy_forms', # Improved form rendering
|
||||
'import_export', # Import / export tables to file
|
||||
'django_cleanup.apps.CleanupConfig', # Automatically delete orphaned MEDIA files
|
||||
'mptt', # Modified Preorder Tree Traversal
|
||||
'markdownx', # Markdown editing
|
||||
'markdownify', # Markdown template rendering
|
||||
'django_admin_shell', # Python shell for the admin interface
|
||||
'djmoney', # django-money integration
|
||||
'djmoney.contrib.exchange', # django-money exchange rates
|
||||
'error_report', # Error reporting in the admin interface
|
||||
'django_q',
|
||||
]
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
MIDDLEWARE = [
|
||||
MIDDLEWARE = CONFIG.get('middleware', [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
@@ -149,18 +263,33 @@ MIDDLEWARE = [
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'InvenTree.middleware.AuthRequiredMiddleware',
|
||||
]
|
||||
'InvenTree.middleware.AuthRequiredMiddleware'
|
||||
])
|
||||
|
||||
if CONFIG.get('log_queries', False):
|
||||
MIDDLEWARE.append('InvenTree.middleware.QueryCountMiddleware')
|
||||
# Error reporting middleware
|
||||
MIDDLEWARE.append('error_report.middleware.ExceptionProcessor')
|
||||
|
||||
AUTHENTICATION_BACKENDS = CONFIG.get('authentication_backends', [
|
||||
'django.contrib.auth.backends.ModelBackend'
|
||||
])
|
||||
|
||||
# If the debug toolbar is enabled, add the modules
|
||||
if DEBUG and CONFIG.get('debug_toolbar', False):
|
||||
logger.info("Running with DEBUG_TOOLBAR enabled")
|
||||
INSTALLED_APPS.append('debug_toolbar')
|
||||
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
|
||||
|
||||
ROOT_URLCONF = 'InvenTree.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'templates'),
|
||||
# Allow templates in the reporting directory to be accessed
|
||||
os.path.join(MEDIA_ROOT, 'report'),
|
||||
os.path.join(MEDIA_ROOT, 'label'),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
@@ -169,7 +298,9 @@ TEMPLATES = [
|
||||
'django.template.context_processors.i18n',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'InvenTree.context.health_status',
|
||||
'InvenTree.context.status_codes',
|
||||
'InvenTree.context.user_roles',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -183,11 +314,29 @@ REST_FRAMEWORK = {
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
),
|
||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
'rest_framework.permissions.DjangoModelPermissions',
|
||||
'InvenTree.permissions.RolePermission',
|
||||
),
|
||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
|
||||
}
|
||||
|
||||
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
||||
|
||||
# django-q configuration
|
||||
Q_CLUSTER = {
|
||||
'name': 'InvenTree',
|
||||
'workers': 4,
|
||||
'timeout': 90,
|
||||
'retry': 120,
|
||||
'queue_limit': 50,
|
||||
'bulk': 10,
|
||||
'orm': 'default',
|
||||
'sync': False,
|
||||
}
|
||||
|
||||
# Markdownx configuration
|
||||
# Ref: https://neutronx.github.io/django-markdownx/customization/
|
||||
MARKDOWNX_MEDIA_PATH = datetime.now().strftime('markdownx/%Y/%m/%d')
|
||||
@@ -222,42 +371,78 @@ MARKDOWNIFY_BLEACH = False
|
||||
DATABASES = {}
|
||||
|
||||
"""
|
||||
When running unit tests, enforce usage of sqlite3 database,
|
||||
so that the tests can be run in RAM without any setup requirements
|
||||
"""
|
||||
if 'test' in sys.argv:
|
||||
eprint('InvenTree: Running tests - Using sqlite3 memory database')
|
||||
DATABASES['default'] = {
|
||||
# Ensure sqlite3 backend is being used
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
# Doesn't matter what the database is called, it is executed in RAM
|
||||
'NAME': 'ram_test_db.sqlite3',
|
||||
}
|
||||
Configure the database backend based on the user-specified values.
|
||||
|
||||
# Database backend selection
|
||||
else:
|
||||
if 'database' in CONFIG:
|
||||
DATABASES['default'] = CONFIG['database']
|
||||
else:
|
||||
eprint("Warning: Database backend not specified - using default (sqlite)")
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'inventree_db.sqlite3'),
|
||||
}
|
||||
- Primarily this configuration happens in the config.yaml file
|
||||
- However there may be reason to configure the DB via environmental variables
|
||||
- The following code lets the user "mix and match" database configuration
|
||||
"""
|
||||
|
||||
logger.info("Configuring database backend:")
|
||||
|
||||
# Extract database configuration from the config.yaml file
|
||||
db_config = CONFIG.get('database', {})
|
||||
|
||||
if not db_config:
|
||||
db_config = {}
|
||||
|
||||
# Environment variables take preference over config file!
|
||||
|
||||
db_keys = ['ENGINE', 'NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']
|
||||
|
||||
for key in db_keys:
|
||||
# First, check the environment variables
|
||||
env_key = f"INVENTREE_DB_{key}"
|
||||
env_var = os.environ.get(env_key, None)
|
||||
|
||||
if env_var:
|
||||
logger.info(f"{env_key}={env_var}")
|
||||
# Override configuration value
|
||||
db_config[key] = env_var
|
||||
|
||||
# Check that required database configuration options are specified
|
||||
reqiured_keys = ['ENGINE', 'NAME']
|
||||
|
||||
for key in reqiured_keys:
|
||||
if key not in db_config:
|
||||
error_msg = f'Missing required database configuration value {key}'
|
||||
logger.error(error_msg)
|
||||
|
||||
print('Error: ' + error_msg)
|
||||
sys.exit(-1)
|
||||
|
||||
"""
|
||||
Special considerations for the database 'ENGINE' setting.
|
||||
It can be specified in config.yaml (or envvar) as either (for example):
|
||||
- sqlite3
|
||||
- django.db.backends.sqlite3
|
||||
- django.db.backends.postgresql
|
||||
"""
|
||||
|
||||
db_engine = db_config['ENGINE']
|
||||
|
||||
if db_engine.lower() in ['sqlite3', 'postgresql', 'mysql']:
|
||||
# Prepend the required python module string
|
||||
db_engine = f'django.db.backends.{db_engine.lower()}'
|
||||
db_config['ENGINE'] = db_engine
|
||||
|
||||
db_name = db_config['NAME']
|
||||
db_host = db_config.get('HOST', "''")
|
||||
|
||||
print("InvenTree Database Configuration")
|
||||
print("================================")
|
||||
print(f"ENGINE: {db_engine}")
|
||||
print(f"NAME: {db_name}")
|
||||
print(f"HOST: {db_host}")
|
||||
|
||||
DATABASES['default'] = db_config
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
},
|
||||
'qr-code': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': 'qr-code-cache',
|
||||
'TIMEOUT': 3600
|
||||
}
|
||||
}
|
||||
|
||||
QR_CODE_CACHE_ALIAS = 'qr-code'
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
||||
|
||||
@@ -282,72 +467,74 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
EXTRA_URL_SCHEMES = CONFIG.get('extra_url_schemes', [])
|
||||
|
||||
if not type(EXTRA_URL_SCHEMES) in [list]:
|
||||
eprint("Warning: extra_url_schemes not correctly formatted")
|
||||
logger.warning("extra_url_schemes not correctly formatted")
|
||||
EXTRA_URL_SCHEMES = []
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||
# https://docs.djangoproject.com/en/dev/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = CONFIG.get('language', 'en-us')
|
||||
|
||||
# If a new language translation is supported, it must be added here
|
||||
LANGUAGES = [
|
||||
('en', _('English')),
|
||||
('de', _('German')),
|
||||
('fr', _('French')),
|
||||
('de', _('German')),
|
||||
('pk', _('Polish')),
|
||||
('tr', _('Turkish')),
|
||||
]
|
||||
|
||||
# Currencies available for use
|
||||
CURRENCIES = CONFIG.get(
|
||||
'currencies',
|
||||
[
|
||||
'AUD', 'CAD', 'EUR', 'GBP', 'JPY', 'NZD', 'USD',
|
||||
],
|
||||
)
|
||||
|
||||
# TODO - Allow live web-based backends in the future
|
||||
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend'
|
||||
|
||||
LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale/'),
|
||||
)
|
||||
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
TIME_ZONE = get_setting(
|
||||
'INVENTREE_TIMEZONE',
|
||||
CONFIG.get('timezone', 'UTC')
|
||||
)
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
# Do not use native timezone support in "test" mode
|
||||
# It generates a *lot* of cruft in the logs
|
||||
if not TESTING:
|
||||
USE_TZ = True
|
||||
|
||||
DATE_INPUT_FORMATS = [
|
||||
"%Y-%m-%d",
|
||||
]
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||
|
||||
# Web URL endpoint for served static files
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# The filesystem location for served static files
|
||||
STATIC_ROOT = os.path.abspath(CONFIG.get('static_root', os.path.join(BASE_DIR, 'static')))
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, 'InvenTree', 'static'),
|
||||
]
|
||||
|
||||
# Web URL endpoint for served media files
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# The filesystem location for served static files
|
||||
MEDIA_ROOT = os.path.abspath(CONFIG.get('media_root', os.path.join(BASE_DIR, 'media')))
|
||||
|
||||
if DEBUG:
|
||||
print("InvenTree running in DEBUG mode")
|
||||
print("MEDIA_ROOT:", MEDIA_ROOT)
|
||||
print("STATIC_ROOT:", STATIC_ROOT)
|
||||
|
||||
# crispy forms use the bootstrap templates
|
||||
CRISPY_TEMPLATE_PACK = 'bootstrap3'
|
||||
|
||||
# Use database transactions when importing / exporting data
|
||||
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||
|
||||
BACKUP_DIR = get_setting(
|
||||
'INVENTREE_BACKUP_DIR',
|
||||
CONFIG.get('backup_dir', tempfile.gettempdir()),
|
||||
)
|
||||
|
||||
# Settings for dbbsettings app
|
||||
DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
DBBACKUP_STORAGE_OPTIONS = {
|
||||
'location': CONFIG.get('backup_dir', tempfile.gettempdir()),
|
||||
'location': BACKUP_DIR,
|
||||
}
|
||||
|
||||
# Internal IP addresses allowed to see the debug toolbar
|
||||
INTERNAL_IPS = [
|
||||
'127.0.0.1',
|
||||
]
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @update: zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
!($ => {
|
||||
const diacriticsMap = {}
|
||||
const defaultAccentsDiacritics = [
|
||||
{base: 'A', letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'},
|
||||
{base: 'AA',letters: '\uA732'},
|
||||
{base: 'AE',letters: '\u00C6\u01FC\u01E2'},
|
||||
{base: 'AO',letters: '\uA734'},
|
||||
{base: 'AU',letters: '\uA736'},
|
||||
{base: 'AV',letters: '\uA738\uA73A'},
|
||||
{base: 'AY',letters: '\uA73C'},
|
||||
{base: 'B', letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'},
|
||||
{base: 'C', letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'},
|
||||
{base: 'D', letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779'},
|
||||
{base: 'DZ',letters: '\u01F1\u01C4'},
|
||||
{base: 'Dz',letters: '\u01F2\u01C5'},
|
||||
{base: 'E', letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'},
|
||||
{base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'},
|
||||
{base: 'G', letters: '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'},
|
||||
{base: 'H', letters: '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'},
|
||||
{base: 'I', letters: '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'},
|
||||
{base: 'J', letters: '\u004A\u24BF\uFF2A\u0134\u0248'},
|
||||
{base: 'K', letters: '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'},
|
||||
{base: 'L', letters: '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'},
|
||||
{base: 'LJ',letters: '\u01C7'},
|
||||
{base: 'Lj',letters: '\u01C8'},
|
||||
{base: 'M', letters: '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'},
|
||||
{base: 'N', letters: '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'},
|
||||
{base: 'NJ',letters: '\u01CA'},
|
||||
{base: 'Nj',letters: '\u01CB'},
|
||||
{base: 'O', letters: '\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'},
|
||||
{base: 'OI',letters: '\u01A2'},
|
||||
{base: 'OO',letters: '\uA74E'},
|
||||
{base: 'OU',letters: '\u0222'},
|
||||
{base: 'OE',letters: '\u008C\u0152'},
|
||||
{base: 'oe',letters: '\u009C\u0153'},
|
||||
{base: 'P', letters: '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'},
|
||||
{base: 'Q', letters: '\u0051\u24C6\uFF31\uA756\uA758\u024A'},
|
||||
{base: 'R', letters: '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'},
|
||||
{base: 'S', letters: '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'},
|
||||
{base: 'T', letters: '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'},
|
||||
{base: 'TZ',letters: '\uA728'},
|
||||
{base: 'U', letters: '\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'},
|
||||
{base: 'V', letters: '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'},
|
||||
{base: 'VY',letters: '\uA760'},
|
||||
{base: 'W', letters: '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'},
|
||||
{base: 'X', letters: '\u0058\u24CD\uFF38\u1E8A\u1E8C'},
|
||||
{base: 'Y', letters: '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'},
|
||||
{base: 'Z', letters: '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'},
|
||||
{base: 'a', letters: '\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'},
|
||||
{base: 'aa',letters: '\uA733'},
|
||||
{base: 'ae',letters: '\u00E6\u01FD\u01E3'},
|
||||
{base: 'ao',letters: '\uA735'},
|
||||
{base: 'au',letters: '\uA737'},
|
||||
{base: 'av',letters: '\uA739\uA73B'},
|
||||
{base: 'ay',letters: '\uA73D'},
|
||||
{base: 'b', letters: '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'},
|
||||
{base: 'c', letters: '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'},
|
||||
{base: 'd', letters: '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'},
|
||||
{base: 'dz',letters: '\u01F3\u01C6'},
|
||||
{base: 'e', letters: '\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'},
|
||||
{base: 'f', letters: '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'},
|
||||
{base: 'g', letters: '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'},
|
||||
{base: 'h', letters: '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'},
|
||||
{base: 'hv',letters: '\u0195'},
|
||||
{base: 'i', letters: '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'},
|
||||
{base: 'j', letters: '\u006A\u24D9\uFF4A\u0135\u01F0\u0249'},
|
||||
{base: 'k', letters: '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'},
|
||||
{base: 'l', letters: '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'},
|
||||
{base: 'lj',letters: '\u01C9'},
|
||||
{base: 'm', letters: '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'},
|
||||
{base: 'n', letters: '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'},
|
||||
{base: 'nj',letters: '\u01CC'},
|
||||
{base: 'o', letters: '\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'},
|
||||
{base: 'oi',letters: '\u01A3'},
|
||||
{base: 'ou',letters: '\u0223'},
|
||||
{base: 'oo',letters: '\uA74F'},
|
||||
{base: 'p',letters: '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'},
|
||||
{base: 'q',letters: '\u0071\u24E0\uFF51\u024B\uA757\uA759'},
|
||||
{base: 'r',letters: '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'},
|
||||
{base: 's',letters: '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'},
|
||||
{base: 't',letters: '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'},
|
||||
{base: 'tz',letters: '\uA729'},
|
||||
{base: 'u',letters: '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'},
|
||||
{base: 'v',letters: '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'},
|
||||
{base: 'vy',letters: '\uA761'},
|
||||
{base: 'w',letters: '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'},
|
||||
{base: 'x',letters: '\u0078\u24E7\uFF58\u1E8B\u1E8D'},
|
||||
{base: 'y',letters: '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'},
|
||||
{base: 'z',letters: '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'}
|
||||
]
|
||||
|
||||
const initNeutraliser = () => {
|
||||
for (const diacritic of defaultAccentsDiacritics) {
|
||||
const letters = diacritic.letters
|
||||
for (let i = 0; i < letters.length; i++) {
|
||||
diacriticsMap[letters[i]] = diacritic.base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable no-control-regex */
|
||||
const removeDiacritics = str => str.replace(/[^\u0000-\u007E]/g, a => diacriticsMap[a] || a)
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
searchAccentNeutralise: false
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
init () {
|
||||
if (this.options.searchAccentNeutralise) {
|
||||
initNeutraliser()
|
||||
}
|
||||
super.init()
|
||||
}
|
||||
|
||||
initSearch () {
|
||||
if (this.options.sidePagination !== 'server') {
|
||||
let s = this.searchText && this.searchText.toLowerCase()
|
||||
const f = $.isEmptyObject(this.filterColumns) ? null : this.filterColumns
|
||||
|
||||
// Check filter
|
||||
this.data = f ? this.options.data.filter((item, i) => {
|
||||
for (const key in f) {
|
||||
if (item[key] !== f[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}) : this.options.data
|
||||
|
||||
this.data = s ? this.options.data.filter((item, i) => {
|
||||
for (let [key, value] of Object.entries(item)) {
|
||||
key = $.isNumeric(key) ? parseInt(key, 10) : key
|
||||
const column = this.columns[this.fieldsColumnsIndex[key]]
|
||||
const j = this.header.fields.indexOf(key)
|
||||
|
||||
if (column && column.searchFormatter) {
|
||||
value = $.fn.bootstrapTable.utils.calculateObjectValue(column,
|
||||
this.header.formatters[j], [value, item, i], value)
|
||||
}
|
||||
|
||||
const index = this.header.fields.indexOf(key)
|
||||
if (index !== -1 && this.header.searchables[index] && typeof value === 'string') {
|
||||
if (this.options.searchAccentNeutralise) {
|
||||
value = removeDiacritics(value)
|
||||
s = removeDiacritics(s)
|
||||
}
|
||||
if (this.options.strictSearch) {
|
||||
if ((`${value}`).toLowerCase() === s) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if ((`${value}`).toLowerCase().includes(s)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}) : this.data
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Accent Neutralise",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to neutralise the words.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/accent-neutralise",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-accent-neutralise",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/accent-neutralise"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
116
InvenTree/InvenTree/static/bootstrap-table/extensions/addrbar/bootstrap-table-addrbar.js
vendored
Normal file
116
InvenTree/InvenTree/static/bootstrap-table/extensions/addrbar/bootstrap-table-addrbar.js
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @author: general
|
||||
* @website: note.generals.space
|
||||
* @email: generals.space@gmail.com
|
||||
* @github: https://github.com/generals-space/bootstrap-table-addrbar
|
||||
* @update: zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
($ => {
|
||||
/*
|
||||
* function: 获取浏览器地址栏中的指定参数.
|
||||
* key: 参数名
|
||||
* url: 默认为当前地址栏
|
||||
*/
|
||||
function _GET (key, url = window.location.search) {
|
||||
/*
|
||||
* 注意这里正则表达式的书写方法
|
||||
* (^|&)key匹配: 直接以key开始或以&key开始的字符串
|
||||
* 同理(&|$)表示以&结束或是直接结束的字符串
|
||||
* ...当然, 我并不知道这种用法.
|
||||
*/
|
||||
const reg = new RegExp(`(^|&)${key}=([^&]*)(&|$)`)
|
||||
const result = url.substr(1).match(reg)
|
||||
|
||||
if (result) {
|
||||
return decodeURIComponent(result[2])
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/*
|
||||
* function: 根据给定参数生成url地址
|
||||
* var dic = {name: 'genreal', age: 24}
|
||||
* var url = 'https://www.baidu.com?age=22';
|
||||
* _buildUrl(dic, url);
|
||||
* 将得到"https://www.baidu.com?age=24&name=genreal"
|
||||
* 哦, 忽略先后顺序吧...
|
||||
*
|
||||
* 补充: 可以参考浏览器URLSearchParams对象, 更加方便和强大.
|
||||
* 考虑到兼容性, 暂时不使用这个工具.
|
||||
*/
|
||||
|
||||
function _buildUrl (dict, url = window.location.search) {
|
||||
for (const [key, val] of Object.entries(dict)) {
|
||||
// 搜索name=general这种形式的字符串(&是分隔符)
|
||||
const pattern = `${key}=([^&]*)`
|
||||
const targetStr = `${key}=${val}`
|
||||
|
||||
/*
|
||||
* 如果目标url中包含了key键, 我们需要将它替换成我们自己的val
|
||||
* 不然就直接添加好了.
|
||||
*/
|
||||
if (url.match(pattern)) {
|
||||
const tmp = new RegExp(`(${key}=)([^&]*)`, 'gi')
|
||||
url = url.replace(tmp, targetStr)
|
||||
} else {
|
||||
const seperator = url.match('[?]') ? '&' : '?'
|
||||
url = url + seperator + targetStr
|
||||
}
|
||||
}
|
||||
if (location.hash) {
|
||||
url += location.hash
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
init () {
|
||||
// 拥有addrbar选项并且其值为true的才会继续执行
|
||||
if (this.options.addrbar) {
|
||||
// 标志位, 初始加载后关闭
|
||||
this.addrbarInit = true
|
||||
const _prefix = this.options.addrPrefix || ''
|
||||
|
||||
// 优先级排序: 用户指定值最优先, 未指定时从地址栏获取, 未获取到时采用默认值
|
||||
this.options.pageSize = this.options.pageSize || (
|
||||
_GET(`${_prefix}limit`) ? parseInt(_GET(`${_prefix}limit`)) : $.BootstrapTable.DEFAULTS.pageSize
|
||||
)
|
||||
this.options.pageNumber = this.options.pageNumber || (
|
||||
_GET(`${_prefix}page`) ? parseInt(_GET(`${_prefix}page`)) : $.BootstrapTable.DEFAULTS.pageNumber
|
||||
)
|
||||
this.options.sortOrder = this.options.sortOrder || (
|
||||
_GET(`${_prefix}order`) ? _GET(`${_prefix}order`) : $.BootstrapTable.DEFAULTS.sortOrder
|
||||
)
|
||||
this.options.sortName = this.options.sortName || (
|
||||
_GET(`${_prefix}sort`) ? _GET(`${_prefix}sort`) : 'id'
|
||||
)
|
||||
this.options.searchText = this.options.searchText || (
|
||||
_GET(`${_prefix}search`) ? _GET(`${_prefix}search`) : $.BootstrapTable.DEFAULTS.searchText
|
||||
)
|
||||
|
||||
const _onLoadSuccess = this.options.onLoadSuccess
|
||||
|
||||
this.options.onLoadSuccess = data => {
|
||||
if (this.addrbarInit) {
|
||||
this.addrbarInit = false
|
||||
} else {
|
||||
const params = {}
|
||||
params[`${_prefix}page`] = this.options.pageNumber,
|
||||
params[`${_prefix}limit`] = this.options.pageSize,
|
||||
params[`${_prefix}order`] = this.options.sortOrder,
|
||||
params[`${_prefix}sort`] = this.options.sortName,
|
||||
params[`${_prefix}search`] = this.options.searchText
|
||||
// h5提供的修改浏览器地址栏的方法
|
||||
window.history.pushState({}, '', _buildUrl(params))
|
||||
}
|
||||
|
||||
if (_onLoadSuccess) {
|
||||
_onLoadSuccess.call(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @author: Alec Fenichel
|
||||
* @webSite: https://fenichelar.com
|
||||
* @update: zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
($ => {
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
autoRefresh: false,
|
||||
autoRefreshInterval: 60,
|
||||
autoRefreshSilent: true,
|
||||
autoRefreshStatus: true,
|
||||
autoRefreshFunction: null
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, {
|
||||
autoRefresh: Utils.bootstrapVersion === 4 ? 'fa-clock' : 'glyphicon-time icon-time'
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatAutoRefresh () {
|
||||
return 'Auto Refresh'
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
init (...args) {
|
||||
super.init(...args)
|
||||
|
||||
if (this.options.autoRefresh && this.options.autoRefreshStatus) {
|
||||
this.options.autoRefreshFunction = setInterval(() => {
|
||||
this.refresh({silent: this.options.autoRefreshSilent})
|
||||
}, this.options.autoRefreshInterval * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
initToolbar (...args) {
|
||||
super.initToolbar(...args)
|
||||
|
||||
if (this.options.autoRefresh) {
|
||||
const $btnGroup = this.$toolbar.find('>.btn-group')
|
||||
let $btnAutoRefresh = $btnGroup.find('.auto-refresh')
|
||||
|
||||
if (!$btnAutoRefresh.length) {
|
||||
$btnAutoRefresh = $(`
|
||||
<button class="auto-refresh btn${Utils.sprintf(' btn-%s', this.options.buttonsClass)}
|
||||
${Utils.sprintf(' btn-%s', this.options.iconSize)}
|
||||
${this.options.autoRefreshStatus ? 'active' : ''}"
|
||||
type="button" title="${this.options.formatAutoRefresh()}">
|
||||
<i class="${this.options.iconsPrefix} ${this.options.icons.autoRefresh}"></i>
|
||||
</button>
|
||||
`).appendTo($btnGroup)
|
||||
|
||||
$btnAutoRefresh.on('click', $.proxy(this.toggleAutoRefresh, this))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoRefresh () {
|
||||
if (this.options.autoRefresh) {
|
||||
if (this.options.autoRefreshStatus) {
|
||||
clearInterval(this.options.autoRefreshFunction)
|
||||
this.$toolbar.find('>.btn-group').find('.auto-refresh').removeClass('active')
|
||||
} else {
|
||||
this.options.autoRefreshFunction = setInterval(() => {
|
||||
this.refresh({silent: this.options.autoRefreshSilent})
|
||||
}, this.options.autoRefreshInterval * 1000)
|
||||
this.$toolbar.find('>.btn-group').find('.auto-refresh').addClass('active')
|
||||
}
|
||||
this.options.autoRefreshStatus = !this.options.autoRefreshStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Auto Refresh",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to automatically refresh the table on an interval.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/auto-refresh",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-auto-refresh",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/auto-refresh"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "fenichaler",
|
||||
"image": "https://avatars.githubusercontent.com/u/3437075"
|
||||
}
|
||||
}
|
||||
401
InvenTree/InvenTree/static/bootstrap-table/extensions/cookie/bootstrap-table-cookie.js
vendored
Normal file
401
InvenTree/InvenTree/static/bootstrap-table/extensions/cookie/bootstrap-table-cookie.js
vendored
Normal file
@@ -0,0 +1,401 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
($ => {
|
||||
const UtilsCookie = {
|
||||
cookieIds: {
|
||||
sortOrder: 'bs.table.sortOrder',
|
||||
sortName: 'bs.table.sortName',
|
||||
pageNumber: 'bs.table.pageNumber',
|
||||
pageList: 'bs.table.pageList',
|
||||
columns: 'bs.table.columns',
|
||||
searchText: 'bs.table.searchText',
|
||||
filterControl: 'bs.table.filterControl',
|
||||
filterBy: 'bs.table.filterBy'
|
||||
},
|
||||
getCurrentHeader (that) {
|
||||
let header = that.$header
|
||||
if (that.options.height) {
|
||||
header = that.$tableHeader
|
||||
}
|
||||
|
||||
return header
|
||||
},
|
||||
getCurrentSearchControls (that) {
|
||||
let searchControls = 'select, input'
|
||||
if (that.options.height) {
|
||||
searchControls = 'table select, table input'
|
||||
}
|
||||
|
||||
return searchControls
|
||||
},
|
||||
cookieEnabled () {
|
||||
return !!(navigator.cookieEnabled)
|
||||
},
|
||||
inArrayCookiesEnabled (cookieName, cookiesEnabled) {
|
||||
let index = -1
|
||||
|
||||
for (let i = 0; i < cookiesEnabled.length; i++) {
|
||||
if (cookieName.toLowerCase() === cookiesEnabled[i].toLowerCase()) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return index
|
||||
},
|
||||
setCookie (that, cookieName, cookieValue) {
|
||||
if ((!that.options.cookie) || (!UtilsCookie.cookieEnabled()) || (that.options.cookieIdTable === '')) {
|
||||
return
|
||||
}
|
||||
|
||||
if (UtilsCookie.inArrayCookiesEnabled(cookieName, that.options.cookiesEnabled) === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
cookieName = `${that.options.cookieIdTable}.${cookieName}`
|
||||
|
||||
switch (that.options.cookieStorage) {
|
||||
case 'cookieStorage':
|
||||
document.cookie = [
|
||||
cookieName, '=', encodeURIComponent(cookieValue),
|
||||
`; expires=${UtilsCookie.calculateExpiration(that.options.cookieExpire)}`,
|
||||
that.options.cookiePath ? `; path=${that.options.cookiePath}` : '',
|
||||
that.options.cookieDomain ? `; domain=${that.options.cookieDomain}` : '',
|
||||
that.options.cookieSecure ? '; secure' : ''
|
||||
].join('')
|
||||
break
|
||||
case 'localStorage':
|
||||
localStorage.setItem(cookieName, cookieValue)
|
||||
break
|
||||
case 'sessionStorage':
|
||||
sessionStorage.setItem(cookieName, cookieValue)
|
||||
break
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
getCookie (that, tableName, cookieName) {
|
||||
if (!cookieName) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (UtilsCookie.inArrayCookiesEnabled(cookieName, that.options.cookiesEnabled) === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
cookieName = `${tableName}.${cookieName}`
|
||||
|
||||
switch (that.options.cookieStorage) {
|
||||
case 'cookieStorage':
|
||||
const value = `; ${document.cookie}`
|
||||
const parts = value.split(`; ${cookieName}=`)
|
||||
return parts.length === 2 ? decodeURIComponent(parts.pop().split(';').shift()) : null
|
||||
case 'localStorage':
|
||||
return localStorage.getItem(cookieName)
|
||||
case 'sessionStorage':
|
||||
return sessionStorage.getItem(cookieName)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
deleteCookie (that, tableName, cookieName) {
|
||||
cookieName = `${tableName}.${cookieName}`
|
||||
|
||||
switch (that.options.cookieStorage) {
|
||||
case 'cookieStorage':
|
||||
document.cookie = [
|
||||
encodeURIComponent(cookieName), '=',
|
||||
'; expires=Thu, 01 Jan 1970 00:00:00 GMT',
|
||||
that.options.cookiePath ? `; path=${that.options.cookiePath}` : '',
|
||||
that.options.cookieDomain ? `; domain=${that.options.cookieDomain}` : ''
|
||||
].join('')
|
||||
break
|
||||
case 'localStorage':
|
||||
localStorage.removeItem(cookieName)
|
||||
break
|
||||
case 'sessionStorage':
|
||||
sessionStorage.removeItem(cookieName)
|
||||
break
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
calculateExpiration (cookieExpire) {
|
||||
const time = cookieExpire.replace(/[0-9]*/, '') // s,mi,h,d,m,y
|
||||
cookieExpire = cookieExpire.replace(/[A-Za-z]{1,2}/, '') // number
|
||||
|
||||
switch (time.toLowerCase()) {
|
||||
case 's':
|
||||
cookieExpire = +cookieExpire
|
||||
break
|
||||
case 'mi':
|
||||
cookieExpire *= 60
|
||||
break
|
||||
case 'h':
|
||||
cookieExpire = cookieExpire * 60 * 60
|
||||
break
|
||||
case 'd':
|
||||
cookieExpire = cookieExpire * 24 * 60 * 60
|
||||
break
|
||||
case 'm':
|
||||
cookieExpire = cookieExpire * 30 * 24 * 60 * 60
|
||||
break
|
||||
case 'y':
|
||||
cookieExpire = cookieExpire * 365 * 24 * 60 * 60
|
||||
break
|
||||
default:
|
||||
cookieExpire = undefined
|
||||
break
|
||||
}
|
||||
if (!cookieExpire) {
|
||||
return ''
|
||||
}
|
||||
const d = new Date()
|
||||
d.setTime(d.getTime() + cookieExpire * 1000)
|
||||
return d.toGMTString()
|
||||
},
|
||||
initCookieFilters (bootstrapTable) {
|
||||
setTimeout(() => {
|
||||
const parsedCookieFilters = JSON.parse(UtilsCookie.getCookie(bootstrapTable, bootstrapTable.options.cookieIdTable, UtilsCookie.cookieIds.filterControl))
|
||||
|
||||
if (!bootstrapTable.options.filterControlValuesLoaded && parsedCookieFilters) {
|
||||
|
||||
const cachedFilters = {}
|
||||
const header = UtilsCookie.getCurrentHeader(bootstrapTable)
|
||||
const searchControls = UtilsCookie.getCurrentSearchControls(bootstrapTable)
|
||||
|
||||
const applyCookieFilters = (element, filteredCookies) => {
|
||||
$(filteredCookies).each((i, cookie) => {
|
||||
if (cookie.text !== '') {
|
||||
$(element).val(cookie.text)
|
||||
cachedFilters[cookie.field] = cookie.text
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
header.find(searchControls).each(function () {
|
||||
const field = $(this).closest('[data-field]').data('field')
|
||||
const filteredCookies = parsedCookieFilters.filter(cookie => cookie.field === field)
|
||||
|
||||
applyCookieFilters(this, filteredCookies)
|
||||
})
|
||||
|
||||
bootstrapTable.initColumnSearch(cachedFilters)
|
||||
bootstrapTable.options.filterControlValuesLoaded = true
|
||||
bootstrapTable.initServer()
|
||||
}
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
cookie: false,
|
||||
cookieExpire: '2h',
|
||||
cookiePath: null,
|
||||
cookieDomain: null,
|
||||
cookieSecure: null,
|
||||
cookieIdTable: '',
|
||||
cookiesEnabled: [
|
||||
'bs.table.sortOrder', 'bs.table.sortName',
|
||||
'bs.table.pageNumber', 'bs.table.pageList',
|
||||
'bs.table.columns', 'bs.table.searchText',
|
||||
'bs.table.filterControl', 'bs.table.filterBy'
|
||||
],
|
||||
cookieStorage: 'cookieStorage', // localStorage, sessionStorage
|
||||
// internal variable
|
||||
filterControls: [],
|
||||
filterControlValuesLoaded: false
|
||||
})
|
||||
|
||||
$.fn.bootstrapTable.methods.push('getCookies')
|
||||
$.fn.bootstrapTable.methods.push('deleteCookie')
|
||||
|
||||
$.extend($.fn.bootstrapTable.utils, {
|
||||
setCookie: UtilsCookie.setCookie,
|
||||
getCookie: UtilsCookie.getCookie
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
init () {
|
||||
// FilterBy logic
|
||||
const filterByCookie = JSON.parse(UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.filterBy))
|
||||
this.filterColumns = filterByCookie ? filterByCookie : {}
|
||||
|
||||
// FilterControl logic
|
||||
this.options.filterControls = []
|
||||
this.options.filterControlValuesLoaded = false
|
||||
|
||||
this.options.cookiesEnabled = typeof this.options.cookiesEnabled === 'string' ?
|
||||
this.options.cookiesEnabled.replace('[', '').replace(']', '')
|
||||
.replace(/ /g, '').toLowerCase().split(',') :
|
||||
this.options.cookiesEnabled
|
||||
|
||||
if (this.options.filterControl) {
|
||||
const that = this
|
||||
this.$el.on('column-search.bs.table', (e, field, text) => {
|
||||
let isNewField = true
|
||||
|
||||
for (let i = 0; i < that.options.filterControls.length; i++) {
|
||||
if (that.options.filterControls[i].field === field) {
|
||||
that.options.filterControls[i].text = text
|
||||
isNewField = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isNewField) {
|
||||
that.options.filterControls.push({
|
||||
field,
|
||||
text
|
||||
})
|
||||
}
|
||||
|
||||
UtilsCookie.setCookie(that, UtilsCookie.cookieIds.filterControl, JSON.stringify(that.options.filterControls))
|
||||
}).on('created-controls.bs.table', UtilsCookie.initCookieFilters(that))
|
||||
}
|
||||
super.init()
|
||||
}
|
||||
|
||||
initServer (...args) {
|
||||
if (
|
||||
this.options.cookie &&
|
||||
this.options.filterControl &&
|
||||
!this.options.filterControlValuesLoaded
|
||||
) {
|
||||
const cookie = JSON.parse(UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.filterControl))
|
||||
if (cookie) {
|
||||
return
|
||||
}
|
||||
}
|
||||
super.initServer(...args)
|
||||
}
|
||||
|
||||
initTable (...args) {
|
||||
super.initTable(...args)
|
||||
this.initCookie()
|
||||
}
|
||||
|
||||
onSort (...args) {
|
||||
super.onSort(...args)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.sortOrder, this.options.sortOrder)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.sortName, this.options.sortName)
|
||||
}
|
||||
|
||||
onPageNumber (...args) {
|
||||
super.onPageNumber(...args)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber)
|
||||
}
|
||||
|
||||
onPageListChange (...args) {
|
||||
super.onPageListChange(...args)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageList, this.options.pageSize)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber)
|
||||
}
|
||||
|
||||
onPagePre (...args) {
|
||||
super.onPagePre(...args)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber)
|
||||
}
|
||||
|
||||
onPageNext (...args) {
|
||||
super.onPageNext(...args)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber)
|
||||
}
|
||||
|
||||
toggleColumn (...args) {
|
||||
super.toggleColumn(...args)
|
||||
|
||||
const visibleColumns = []
|
||||
|
||||
$.each(this.columns, (i, column) => {
|
||||
if (column.visible) {
|
||||
visibleColumns.push(column.field)
|
||||
}
|
||||
})
|
||||
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.columns, JSON.stringify(visibleColumns))
|
||||
}
|
||||
|
||||
selectPage (page) {
|
||||
super.selectPage(page)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, page)
|
||||
}
|
||||
|
||||
onSearch (event) {
|
||||
super.onSearch(event)
|
||||
|
||||
if ($(event.currentTarget).parent().hasClass('search')) {
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.searchText, this.searchText)
|
||||
}
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber)
|
||||
}
|
||||
|
||||
filterBy (...args) {
|
||||
super.filterBy(...args)
|
||||
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.filterBy, JSON.stringify(this.filterColumns))
|
||||
}
|
||||
|
||||
initCookie () {
|
||||
if (!this.options.cookie) {
|
||||
return
|
||||
}
|
||||
|
||||
if ((this.options.cookieIdTable === '') || (this.options.cookieExpire === '') || (!UtilsCookie.cookieEnabled())) {
|
||||
console.error('Configuration error. Please review the cookieIdTable and the cookieExpire property. If the properties are correct, then this browser does not support cookies.')
|
||||
this.options.cookie = false // Make sure that the cookie extension is disabled
|
||||
return
|
||||
}
|
||||
|
||||
const sortOrderCookie = UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.sortOrder)
|
||||
const sortOrderNameCookie = UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.sortName)
|
||||
const pageNumberCookie = UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.pageNumber)
|
||||
const pageListCookie = UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.pageList)
|
||||
const columnsCookie = JSON.parse(UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.columns))
|
||||
const searchTextCookie = UtilsCookie.getCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds.searchText)
|
||||
|
||||
// sortOrder
|
||||
this.options.sortOrder = sortOrderCookie ? sortOrderCookie : this.options.sortOrder
|
||||
// sortName
|
||||
this.options.sortName = sortOrderNameCookie ? sortOrderNameCookie : this.options.sortName
|
||||
// pageNumber
|
||||
this.options.pageNumber = pageNumberCookie ? +pageNumberCookie : this.options.pageNumber
|
||||
// pageSize
|
||||
this.options.pageSize = pageListCookie ? pageListCookie === this.options.formatAllRows() ? pageListCookie : +pageListCookie : this.options.pageSize
|
||||
// searchText
|
||||
this.options.searchText = searchTextCookie ? searchTextCookie : ''
|
||||
|
||||
if (columnsCookie) {
|
||||
$.each(this.columns, (i, column) => {
|
||||
column.visible = $.inArray(column.field, columnsCookie) !== -1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getCookies () {
|
||||
const bootstrapTable = this
|
||||
const cookies = {}
|
||||
$.each(UtilsCookie.cookieIds, (key, value) => {
|
||||
cookies[key] = UtilsCookie.getCookie(bootstrapTable, bootstrapTable.options.cookieIdTable, value)
|
||||
if (key === 'columns') {
|
||||
cookies[key] = JSON.parse(cookies[key])
|
||||
}
|
||||
})
|
||||
return cookies
|
||||
}
|
||||
|
||||
deleteCookie (cookieName) {
|
||||
if ((cookieName === '') || (!UtilsCookie.cookieEnabled())) {
|
||||
return
|
||||
}
|
||||
|
||||
UtilsCookie.deleteCookie(this, this.options.cookieIdTable, UtilsCookie.cookieIds[cookieName])
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Cookie",
|
||||
"version": "1.2.1",
|
||||
"description": "Plugin to use the cookie of the browser.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/cookie",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/cookie.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-cookie",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/cookie"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
102
InvenTree/InvenTree/static/bootstrap-table/extensions/copy-rows/bootstrap-table-copy-rows.js
vendored
Normal file
102
InvenTree/InvenTree/static/bootstrap-table/extensions/copy-rows/bootstrap-table-copy-rows.js
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @author Homer Glascock <HopGlascock@gmail.com>
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
"use strict";
|
||||
|
||||
var calculateObjectValue = $.fn.bootstrapTable.utils.calculateObjectValue,
|
||||
sprintf = $.fn.bootstrapTable.utils.sprintf;
|
||||
|
||||
var copytext = function (text) {
|
||||
var textField = document.createElement('textarea');
|
||||
$(textField).html(text);
|
||||
document.body.appendChild(textField);
|
||||
textField.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Oops, unable to copy");
|
||||
}
|
||||
$(textField).remove();
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
copyBtn: false,
|
||||
copyWHiddenBtn: false,
|
||||
copyDelemeter: ", "
|
||||
});
|
||||
|
||||
$.fn.bootstrapTable.methods.push('copyColumnsToClipboard', 'copyColumnsToClipboardWithHidden');
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initToolbar = BootstrapTable.prototype.initToolbar;
|
||||
|
||||
BootstrapTable.prototype.initToolbar = function () {
|
||||
|
||||
_initToolbar.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
var that = this,
|
||||
$btnGroup = this.$toolbar.find('>.btn-group');
|
||||
|
||||
if (this.options.clickToSelect || this.options.singleSelect) {
|
||||
|
||||
if (this.options.copyBtn) {
|
||||
var copybtn = "<button class='btn btn-default' id='copyBtn'><span class='glyphicon glyphicon-copy icon-pencil'></span></button>";
|
||||
$btnGroup.append(copybtn);
|
||||
$btnGroup.find('#copyBtn').click(function () { that.copyColumnsToClipboard(); });
|
||||
}
|
||||
|
||||
if (this.options.copyWHiddenBtn) {
|
||||
var copyhiddenbtn = "<button class='btn btn-default' id='copyWHiddenBtn'><span class='badge'><span class='glyphicon glyphicon-copy icon-pencil'></span></span></button>";
|
||||
$btnGroup.append(copyhiddenbtn);
|
||||
$btnGroup.find('#copyWHiddenBtn').click(function () { that.copyColumnsToClipboardWithHidden(); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.copyColumnsToClipboard = function () {
|
||||
var that = this,
|
||||
ret = "",
|
||||
delimet = this.options.copyDelemeter;
|
||||
|
||||
$.each(that.getSelections(), function (index, row) {
|
||||
$.each(that.options.columns[0], function (indy, column) {
|
||||
if (column.field !== "state" && column.field !== "RowNumber" && column.visible) {
|
||||
if (row[column.field] !== null) {
|
||||
ret += calculateObjectValue(column, that.header.formatters[indy], [row[column.field], row, index], row[column.field]);
|
||||
}
|
||||
ret += delimet;
|
||||
}
|
||||
});
|
||||
|
||||
ret += "\r\n";
|
||||
});
|
||||
|
||||
copytext(ret);
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.copyColumnsToClipboardWithHidden = function () {
|
||||
var that = this,
|
||||
ret = "",
|
||||
delimet = this.options.copyDelemeter;
|
||||
|
||||
$.each(that.getSelections(), function (index, row) {
|
||||
$.each(that.options.columns[0], function (indy, column) {
|
||||
if (column.field != "state" && column.field !== "RowNumber") {
|
||||
if (row[column.field] !== null) {
|
||||
ret += calculateObjectValue(column, that.header.formatters[indy], [row[column.field], row, index], row[column.field]);
|
||||
}
|
||||
ret += delimet;
|
||||
}
|
||||
});
|
||||
|
||||
ret += "\r\n";
|
||||
});
|
||||
|
||||
copytext(ret);
|
||||
};
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Copy Rows",
|
||||
"version": "1.0.0",
|
||||
"description": "Allows pushing of selected column data to the clipboard.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/copy-rows",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/copy-rows.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "copy-rows",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/copy-rows"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "Homer Glascock",
|
||||
"image": "https://avatars1.githubusercontent.com/u/5546710"
|
||||
}
|
||||
}
|
||||
32
InvenTree/InvenTree/static/bootstrap-table/extensions/defer-url/bootstrap-table-defer-url.js
vendored
Normal file
32
InvenTree/InvenTree/static/bootstrap-table/extensions/defer-url/bootstrap-table-defer-url.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* When using server-side processing, the default mode of operation for
|
||||
* bootstrap-table is to simply throw away any data that currently exists in the
|
||||
* table and make a request to the server to get the first page of data to
|
||||
* display. This is fine for an empty table, but if you already have the first
|
||||
* page of data displayed in the plain HTML, it is a waste of resources. As
|
||||
* such, you can use data-defer-url instead of data-url to allow you to instruct
|
||||
* bootstrap-table to not make that initial request, rather it will use the data
|
||||
* already on the page.
|
||||
*
|
||||
* @author: Ruben Suarez
|
||||
* @webSite: http://rubensa.eu.org
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
deferUrl : undefined
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor, _init = BootstrapTable.prototype.init;
|
||||
|
||||
BootstrapTable.prototype.init = function() {
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (this.options.deferUrl) {
|
||||
this.options.url = this.options.deferUrl;
|
||||
}
|
||||
}
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "DeferURL",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to defer server side processing.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/defer-url",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/defer-url.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-defer-url",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/defer-url"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "rubensa",
|
||||
"image": "https://avatars1.githubusercontent.com/u/1469340"
|
||||
}
|
||||
}
|
||||
149
InvenTree/InvenTree/static/bootstrap-table/extensions/editable/bootstrap-table-editable.js
vendored
Normal file
149
InvenTree/InvenTree/static/bootstrap-table/extensions/editable/bootstrap-table-editable.js
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
* extensions: https://github.com/vitalets/x-editable
|
||||
*/
|
||||
|
||||
($ => {
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
editable: true,
|
||||
onEditableInit () {
|
||||
return false
|
||||
},
|
||||
onEditableSave (field, row, oldValue, $el) {
|
||||
return false
|
||||
},
|
||||
onEditableShown (field, row, $el, editable) {
|
||||
return false
|
||||
},
|
||||
onEditableHidden (field, row, $el, reason) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'editable-init.bs.table': 'onEditableInit',
|
||||
'editable-save.bs.table': 'onEditableSave',
|
||||
'editable-shown.bs.table': 'onEditableShown',
|
||||
'editable-hidden.bs.table': 'onEditableHidden'
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
initTable () {
|
||||
super.initTable()
|
||||
|
||||
if (!this.options.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
$.each(this.columns, (i, column) => {
|
||||
if (!column.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
const editableOptions = {}
|
||||
const editableDataMarkup = []
|
||||
const editableDataPrefix = 'editable-'
|
||||
const processDataOptions = (key, value) => {
|
||||
// Replace camel case with dashes.
|
||||
const dashKey = key.replace(/([A-Z])/g, $1 => `-${$1.toLowerCase()}`)
|
||||
if (dashKey.indexOf(editableDataPrefix) === 0) {
|
||||
editableOptions[dashKey.replace(editableDataPrefix, 'data-')] = value
|
||||
}
|
||||
}
|
||||
|
||||
$.each(this.options, processDataOptions)
|
||||
|
||||
column.formatter = column.formatter || (value => value)
|
||||
column._formatter = column._formatter ? column._formatter : column.formatter
|
||||
column.formatter = (value, row, index) => {
|
||||
const result = Utils.calculateObjectValue(column,
|
||||
column._formatter, [value, row, index], value)
|
||||
|
||||
$.each(column, processDataOptions)
|
||||
|
||||
$.each(editableOptions, (key, value) => {
|
||||
editableDataMarkup.push(` ${key}="${value}"`)
|
||||
})
|
||||
|
||||
let _dont_edit_formatter = false
|
||||
if (column.editable.hasOwnProperty('noeditFormatter')) {
|
||||
_dont_edit_formatter = column.editable.noeditFormatter(value, row, index)
|
||||
}
|
||||
|
||||
if (_dont_edit_formatter === false) {
|
||||
return `<a href="javascript:void(0)"
|
||||
data-name="${column.field}"
|
||||
data-pk="${row[this.options.idField]}"
|
||||
data-value="${result}"
|
||||
${editableDataMarkup.join('')}></a>`
|
||||
}
|
||||
return _dont_edit_formatter
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
initBody (fixedScroll) {
|
||||
super.initBody(fixedScroll)
|
||||
|
||||
if (!this.options.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
$.each(this.columns, (i, column) => {
|
||||
if (!column.editable) {
|
||||
return
|
||||
}
|
||||
|
||||
const data = this.getData()
|
||||
const $field = this.$body.find(`a[data-name="${column.field}"]`)
|
||||
|
||||
$field.each((i, element) => {
|
||||
const $element = $(element)
|
||||
const $tr = $element.closest('tr')
|
||||
const index = $tr.data('index')
|
||||
const row = data[index]
|
||||
|
||||
const editableOpts = Utils.calculateObjectValue(column,
|
||||
column.editable, [index, row, $element], {})
|
||||
|
||||
$element.editable(editableOpts)
|
||||
})
|
||||
|
||||
$field.off('save').on('save', ({currentTarget}, {submitValue}) => {
|
||||
const $this = $(currentTarget)
|
||||
const data = this.getData()
|
||||
const index = $this.parents('tr[data-index]').data('index')
|
||||
const row = data[index]
|
||||
const oldValue = row[column.field]
|
||||
|
||||
$this.data('value', submitValue)
|
||||
row[column.field] = submitValue
|
||||
this.trigger('editable-save', column.field, row, oldValue, $this)
|
||||
this.resetFooter()
|
||||
})
|
||||
|
||||
$field.off('shown').on('shown', ({currentTarget}, editable) => {
|
||||
const $this = $(currentTarget)
|
||||
const data = this.getData()
|
||||
const index = $this.parents('tr[data-index]').data('index')
|
||||
const row = data[index]
|
||||
|
||||
this.trigger('editable-shown', column.field, row, $this, editable)
|
||||
})
|
||||
|
||||
$field.off('hidden').on('hidden', ({currentTarget}, reason) => {
|
||||
const $this = $(currentTarget)
|
||||
const data = this.getData()
|
||||
const index = $this.parents('tr[data-index]').data('index')
|
||||
const row = data[index]
|
||||
|
||||
this.trigger('editable-hidden', column.field, row, $this, reason)
|
||||
})
|
||||
})
|
||||
this.trigger('editable-init')
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Table Editable",
|
||||
"version": "1.1.0",
|
||||
"description": "Use the x-editable to in-place editing your table.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/editable",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/editable.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "x-editable",
|
||||
"url": "https://github.com/vitalets/x-editable"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "wenzhixin",
|
||||
"image": "https://avatars1.githubusercontent.com/u/2117018"
|
||||
}
|
||||
}
|
||||
225
InvenTree/InvenTree/static/bootstrap-table/extensions/export/bootstrap-table-export.js
vendored
Normal file
225
InvenTree/InvenTree/static/bootstrap-table/extensions/export/bootstrap-table-export.js
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
* extensions: https://github.com/hhurz/tableExport.jquery.plugin
|
||||
*/
|
||||
|
||||
($ => {
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
const bootstrap = {
|
||||
3: {
|
||||
icons: {
|
||||
export: 'glyphicon-export icon-share'
|
||||
},
|
||||
html: {
|
||||
dropmenu: '<ul class="dropdown-menu" role="menu"></ul>',
|
||||
dropitem: '<li role="menuitem" data-type="%s"><a href="javascript:">%s</a></li>'
|
||||
}
|
||||
},
|
||||
4: {
|
||||
icons: {
|
||||
export: 'fa-download'
|
||||
},
|
||||
html: {
|
||||
dropmenu: '<div class="dropdown-menu dropdown-menu-right"></div>',
|
||||
dropitem: '<a class="dropdown-item" data-type="%s" href="javascript:">%s</a>'
|
||||
}
|
||||
}
|
||||
}[Utils.bootstrapVersion]
|
||||
|
||||
const TYPE_NAME = {
|
||||
json: 'JSON',
|
||||
xml: 'XML',
|
||||
png: 'PNG',
|
||||
csv: 'CSV',
|
||||
txt: 'TXT',
|
||||
sql: 'SQL',
|
||||
doc: 'MS-Word',
|
||||
excel: 'MS-Excel',
|
||||
xlsx: 'MS-Excel (OpenXML)',
|
||||
powerpoint: 'MS-Powerpoint',
|
||||
pdf: 'PDF'
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showExport: false,
|
||||
exportDataType: 'basic', // basic, all, selected
|
||||
exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'excel'],
|
||||
exportOptions: {},
|
||||
exportFooter: false
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, {
|
||||
export: bootstrap.icons.export
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatExport () {
|
||||
return 'Export data'
|
||||
}
|
||||
})
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
|
||||
|
||||
$.fn.bootstrapTable.methods.push('exportTable')
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
initToolbar () {
|
||||
const o = this.options
|
||||
|
||||
this.showToolbar = this.showToolbar || o.showExport
|
||||
|
||||
super.initToolbar()
|
||||
|
||||
if (!this.options.showExport) {
|
||||
return
|
||||
}
|
||||
const $btnGroup = this.$toolbar.find('>.btn-group')
|
||||
this.$export = $btnGroup.find('div.export')
|
||||
|
||||
if (this.$export.length) {
|
||||
this.updateExportButton()
|
||||
return
|
||||
}
|
||||
this.$export = $(`
|
||||
<div class="export btn-group">
|
||||
<button class="btn btn-${o.buttonsClass} btn-${o.iconSize} dropdown-toggle"
|
||||
aria-label="export type"
|
||||
title="${o.formatExport()}"
|
||||
data-toggle="dropdown"
|
||||
type="button">
|
||||
<i class="${o.iconsPrefix} ${o.icons.export}"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
${bootstrap.html.dropmenu}
|
||||
</div>
|
||||
`).appendTo($btnGroup)
|
||||
|
||||
this.updateExportButton()
|
||||
|
||||
const $menu = this.$export.find('.dropdown-menu')
|
||||
let exportTypes = o.exportTypes
|
||||
|
||||
if (typeof exportTypes === 'string') {
|
||||
const types = exportTypes.slice(1, -1).replace(/ /g, '').split(',')
|
||||
exportTypes = types.map(t => t.slice(1, -1))
|
||||
}
|
||||
for (const type of exportTypes) {
|
||||
if (TYPE_NAME.hasOwnProperty(type)) {
|
||||
$menu.append(Utils.sprintf(bootstrap.html.dropitem, type, TYPE_NAME[type]))
|
||||
}
|
||||
}
|
||||
|
||||
$menu.find('>li, >a').click(({currentTarget}) => {
|
||||
const type = $(currentTarget).data('type')
|
||||
const exportOptions = {
|
||||
type,
|
||||
escape: false
|
||||
}
|
||||
|
||||
this.exportTable(exportOptions)
|
||||
})
|
||||
}
|
||||
|
||||
exportTable (options) {
|
||||
const o = this.options
|
||||
const stateField = this.header.stateField
|
||||
const isCardView = o.cardView
|
||||
|
||||
const doExport = callback => {
|
||||
if (stateField) {
|
||||
this.hideColumn(stateField)
|
||||
}
|
||||
if (isCardView) {
|
||||
this.toggleView()
|
||||
}
|
||||
|
||||
const data = this.getData()
|
||||
if (o.exportFooter) {
|
||||
const $footerRow = this.$tableFooter.find('tr').first()
|
||||
const footerData = {}
|
||||
const footerHtml = []
|
||||
|
||||
$.each($footerRow.children(), (index, footerCell) => {
|
||||
const footerCellHtml = $(footerCell).children('.th-inner').first().html()
|
||||
footerData[this.columns[index].field] = footerCellHtml === ' ' ? null : footerCellHtml
|
||||
|
||||
// grab footer cell text into cell index-based array
|
||||
footerHtml.push(footerCellHtml)
|
||||
})
|
||||
|
||||
this.append(footerData)
|
||||
|
||||
const $lastTableRow = this.$body.children().last()
|
||||
|
||||
$.each($lastTableRow.children(), (index, lastTableRowCell) => {
|
||||
$(lastTableRowCell).html(footerHtml[index])
|
||||
})
|
||||
}
|
||||
|
||||
this.$el.tableExport($.extend({
|
||||
onAfterSaveToFile: () => {
|
||||
if (o.exportFooter) {
|
||||
this.load(data)
|
||||
}
|
||||
|
||||
if (stateField) {
|
||||
this.showColumn(stateField)
|
||||
}
|
||||
if (isCardView) {
|
||||
this.toggleView()
|
||||
}
|
||||
|
||||
callback()
|
||||
}
|
||||
}, o.exportOptions, options))
|
||||
}
|
||||
|
||||
if (o.exportDataType === 'all' && o.pagination) {
|
||||
const eventName = o.sidePagination === 'server'
|
||||
? 'post-body.bs.table' : 'page-change.bs.table'
|
||||
this.$el.one(eventName, () => {
|
||||
doExport(() => {
|
||||
this.togglePagination()
|
||||
})
|
||||
})
|
||||
this.togglePagination()
|
||||
} else if (o.exportDataType === 'selected') {
|
||||
let data = this.getData()
|
||||
let selectedData = this.getSelections()
|
||||
if (!selectedData.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (o.sidePagination === 'server') {
|
||||
data = {
|
||||
total: o.totalRows,
|
||||
[this.options.dataField]: data
|
||||
}
|
||||
selectedData = {
|
||||
total: selectedData.length,
|
||||
[this.options.dataField]: selectedData
|
||||
}
|
||||
}
|
||||
|
||||
this.load(selectedData)
|
||||
doExport(() => {
|
||||
this.load(data)
|
||||
})
|
||||
} else {
|
||||
doExport()
|
||||
}
|
||||
}
|
||||
|
||||
updateSelected () {
|
||||
super.updateSelected()
|
||||
this.updateExportButton()
|
||||
}
|
||||
|
||||
updateExportButton () {
|
||||
if (this.options.exportDataType === 'selected') {
|
||||
this.$export.find('> button')
|
||||
.prop('disabled', !this.getSelections().length)
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Table Export",
|
||||
"version": "1.1.0",
|
||||
"description": "Export your table data to JSON, XML, CSV, TXT, SQL, Word, Excel, PNG, PDF.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/export",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/export.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "tableExport.jquery.plugin",
|
||||
"url": "https://github.com/hhurz/tableExport.jquery.plugin"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "wenzhixin",
|
||||
"image": "https://avatars1.githubusercontent.com/u/2117018"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v2.1.1
|
||||
*/
|
||||
|
||||
.no-filter-control {
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
margin: 0 2px 2px 2px;
|
||||
}
|
||||
@@ -0,0 +1,917 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v2.2.0
|
||||
*/
|
||||
|
||||
($ => {
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
const UtilsFilterControl = {
|
||||
getOptionsFromSelectControl (selectControl) {
|
||||
return selectControl.get(selectControl.length - 1).options
|
||||
},
|
||||
|
||||
hideUnusedSelectOptions (selectControl, uniqueValues) {
|
||||
const options = UtilsFilterControl.getOptionsFromSelectControl(
|
||||
selectControl
|
||||
)
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (options[i].value !== '') {
|
||||
if (!uniqueValues.hasOwnProperty(options[i].value)) {
|
||||
selectControl
|
||||
.find(Utils.sprintf('option[value=\'%s\']', options[i].value))
|
||||
.hide()
|
||||
} else {
|
||||
selectControl
|
||||
.find(Utils.sprintf('option[value=\'%s\']', options[i].value))
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
addOptionToSelectControl (selectControl, _value, text) {
|
||||
const value = $.trim(_value)
|
||||
const $selectControl = $(selectControl.get(selectControl.length - 1))
|
||||
if (
|
||||
!UtilsFilterControl.existOptionInSelectControl(selectControl, value)
|
||||
) {
|
||||
$selectControl.append(
|
||||
$('<option></option>')
|
||||
.attr('value', value)
|
||||
.text(
|
||||
$('<div />')
|
||||
.html(text)
|
||||
.text()
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
sortSelectControl (selectControl) {
|
||||
const $selectControl = $(selectControl.get(selectControl.length - 1))
|
||||
const $opts = $selectControl.find('option:gt(0)')
|
||||
|
||||
$opts.sort((a, b) => {
|
||||
let aa = $(a).text().toLowerCase()
|
||||
let bb = $(b).text().toLowerCase()
|
||||
if ($.isNumeric(a) && $.isNumeric(b)) {
|
||||
// Convert numerical values from string to float.
|
||||
aa = parseFloat(aa)
|
||||
bb = parseFloat(bb)
|
||||
}
|
||||
return aa > bb ? 1 : aa < bb ? -1 : 0
|
||||
})
|
||||
|
||||
$selectControl.find('option:gt(0)').remove()
|
||||
$selectControl.append($opts)
|
||||
},
|
||||
existOptionInSelectControl (selectControl, value) {
|
||||
const options = UtilsFilterControl.getOptionsFromSelectControl(
|
||||
selectControl
|
||||
)
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (options[i].value === value.toString()) {
|
||||
// The value is not valid to add
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, the value is valid to add
|
||||
return false
|
||||
},
|
||||
fixHeaderCSS ({ $tableHeader }) {
|
||||
$tableHeader.css('height', '77px')
|
||||
},
|
||||
getCurrentHeader ({ $header, options, $tableHeader }) {
|
||||
let header = $header
|
||||
if (options.height) {
|
||||
header = $tableHeader
|
||||
}
|
||||
|
||||
return header
|
||||
},
|
||||
getCurrentSearchControls ({ options }) {
|
||||
let searchControls = 'select, input'
|
||||
if (options.height) {
|
||||
searchControls = 'table select, table input'
|
||||
}
|
||||
|
||||
return searchControls
|
||||
},
|
||||
getCursorPosition (el) {
|
||||
if (Utils.isIEBrowser()) {
|
||||
if ($(el).is('input[type=text]')) {
|
||||
let pos = 0
|
||||
if ('selectionStart' in el) {
|
||||
pos = el.selectionStart
|
||||
} else if ('selection' in document) {
|
||||
el.focus()
|
||||
const Sel = document.selection.createRange()
|
||||
const SelLength = document.selection.createRange().text.length
|
||||
Sel.moveStart('character', -el.value.length)
|
||||
pos = Sel.text.length - SelLength
|
||||
}
|
||||
return pos
|
||||
}
|
||||
return -1
|
||||
|
||||
}
|
||||
return -1
|
||||
},
|
||||
setCursorPosition (el) {
|
||||
$(el).val(el.value)
|
||||
},
|
||||
copyValues (that) {
|
||||
const header = UtilsFilterControl.getCurrentHeader(that)
|
||||
const searchControls = UtilsFilterControl.getCurrentSearchControls(that)
|
||||
|
||||
that.options.valuesFilterControl = []
|
||||
|
||||
header.find(searchControls).each(function () {
|
||||
that.options.valuesFilterControl.push({
|
||||
field: $(this)
|
||||
.closest('[data-field]')
|
||||
.data('field'),
|
||||
value: $(this).val(),
|
||||
position: UtilsFilterControl.getCursorPosition($(this).get(0)),
|
||||
hasFocus: $(this).is(':focus')
|
||||
})
|
||||
})
|
||||
},
|
||||
setValues (that) {
|
||||
let field = null
|
||||
let result = []
|
||||
const header = UtilsFilterControl.getCurrentHeader(that)
|
||||
const searchControls = UtilsFilterControl.getCurrentSearchControls(that)
|
||||
|
||||
if (that.options.valuesFilterControl.length > 0) {
|
||||
// Callback to apply after settings fields values
|
||||
let fieldToFocusCallback = null
|
||||
header.find(searchControls).each(function (index, ele) {
|
||||
field = $(this)
|
||||
.closest('[data-field]')
|
||||
.data('field')
|
||||
result = that.options.valuesFilterControl.filter(valueObj => valueObj.field === field)
|
||||
|
||||
if (result.length > 0) {
|
||||
$(this).val(result[0].value)
|
||||
if (result[0].hasFocus) {
|
||||
// set callback if the field had the focus.
|
||||
fieldToFocusCallback = ((fieldToFocus, carretPosition) => {
|
||||
// Closure here to capture the field and cursor position
|
||||
const closedCallback = () => {
|
||||
fieldToFocus.focus()
|
||||
UtilsFilterControl.setCursorPosition(fieldToFocus, carretPosition)
|
||||
}
|
||||
return closedCallback
|
||||
})($(this).get(0), result[0].position)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Callback call.
|
||||
if (fieldToFocusCallback !== null) {
|
||||
fieldToFocusCallback()
|
||||
}
|
||||
}
|
||||
},
|
||||
collectBootstrapCookies () {
|
||||
const cookies = []
|
||||
const foundCookies = document.cookie.match(/(?:bs.table.)(\w*)/g)
|
||||
|
||||
if (foundCookies) {
|
||||
$.each(foundCookies, (i, _cookie) => {
|
||||
let cookie = _cookie
|
||||
if (/./.test(cookie)) {
|
||||
cookie = cookie.split('.').pop()
|
||||
}
|
||||
|
||||
if ($.inArray(cookie, cookies) === -1) {
|
||||
cookies.push(cookie)
|
||||
}
|
||||
})
|
||||
return cookies
|
||||
}
|
||||
},
|
||||
escapeID (id) {
|
||||
return String(id).replace(/(:|\.|\[|\]|,)/g, '\\$1')
|
||||
},
|
||||
isColumnSearchableViaSelect ({ filterControl, searchable }) {
|
||||
return filterControl &&
|
||||
filterControl.toLowerCase() === 'select' &&
|
||||
searchable
|
||||
},
|
||||
isFilterDataNotGiven ({ filterData }) {
|
||||
return filterData === undefined ||
|
||||
filterData.toLowerCase() === 'column'
|
||||
},
|
||||
hasSelectControlElement (selectControl) {
|
||||
return selectControl && selectControl.length > 0
|
||||
},
|
||||
initFilterSelectControls (that) {
|
||||
const data = that.data
|
||||
const itemsPerPage = that.pageTo < that.options.data.length ? that.options.data.length : that.pageTo
|
||||
const z = that.options.pagination
|
||||
? that.options.sidePagination === 'server'
|
||||
? that.pageTo
|
||||
: that.options.totalRows
|
||||
: that.pageTo
|
||||
|
||||
$.each(that.header.fields, (j, field) => {
|
||||
const column = that.columns[that.fieldsColumnsIndex[field]]
|
||||
const selectControl = $(`.bootstrap-table-filter-control-${UtilsFilterControl.escapeID(column.field)}`)
|
||||
|
||||
if (
|
||||
UtilsFilterControl.isColumnSearchableViaSelect(column) &&
|
||||
UtilsFilterControl.isFilterDataNotGiven(column) &&
|
||||
UtilsFilterControl.hasSelectControlElement(selectControl)
|
||||
) {
|
||||
if (selectControl.get(selectControl.length - 1).options.length === 0) {
|
||||
// Added the default option
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, '', column.filterControlPlaceholder)
|
||||
}
|
||||
|
||||
const uniqueValues = {}
|
||||
for (let i = 0; i < z; i++) {
|
||||
// Added a new value
|
||||
const fieldValue = data[i][field]
|
||||
const formattedValue = Utils.calculateObjectValue(that.header, that.header.formatters[j], [fieldValue, data[i], i], fieldValue)
|
||||
|
||||
uniqueValues[formattedValue] = fieldValue
|
||||
}
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in uniqueValues) {
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, uniqueValues[key], key)
|
||||
}
|
||||
|
||||
UtilsFilterControl.sortSelectControl(selectControl)
|
||||
|
||||
if (that.options.hideUnusedSelectOptions) {
|
||||
UtilsFilterControl.hideUnusedSelectOptions(selectControl, uniqueValues)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
that.trigger('created-controls')
|
||||
},
|
||||
getFilterDataMethod (objFilterDataMethod, searchTerm) {
|
||||
const keys = Object.keys(objFilterDataMethod)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (keys[i] === searchTerm) {
|
||||
return objFilterDataMethod[searchTerm]
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
createControls (that, header) {
|
||||
let addedFilterControl = false
|
||||
let isVisible
|
||||
let html
|
||||
|
||||
$.each(that.columns, (i, column) => {
|
||||
isVisible = 'hidden'
|
||||
html = []
|
||||
|
||||
if (!column.visible) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!column.filterControl) {
|
||||
html.push('<div class="no-filter-control"></div>')
|
||||
} else {
|
||||
html.push('<div class="filter-control">')
|
||||
|
||||
const nameControl = column.filterControl.toLowerCase()
|
||||
if (column.searchable && that.options.filterTemplate[nameControl]) {
|
||||
addedFilterControl = true
|
||||
isVisible = 'visible'
|
||||
html.push(
|
||||
that.options.filterTemplate[nameControl](
|
||||
that,
|
||||
column.field,
|
||||
isVisible,
|
||||
column.filterControlPlaceholder
|
||||
? column.filterControlPlaceholder
|
||||
: '',
|
||||
`filter-control-${i}`
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
$.each(header.children().children(), (i, tr) => {
|
||||
const $tr = $(tr)
|
||||
if ($tr.data('field') === column.field) {
|
||||
$tr.find('.fht-cell').append(html.join(''))
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (
|
||||
column.filterData !== undefined &&
|
||||
column.filterData.toLowerCase() !== 'column'
|
||||
) {
|
||||
const filterDataType = UtilsFilterControl.getFilterDataMethod(
|
||||
/* eslint-disable no-use-before-define */
|
||||
filterDataMethods,
|
||||
column.filterData.substring(0, column.filterData.indexOf(':'))
|
||||
)
|
||||
let filterDataSource
|
||||
let selectControl
|
||||
|
||||
if (filterDataType !== null) {
|
||||
filterDataSource = column.filterData.substring(
|
||||
column.filterData.indexOf(':') + 1,
|
||||
column.filterData.length
|
||||
)
|
||||
selectControl = $(
|
||||
`.bootstrap-table-filter-control-${UtilsFilterControl.escapeID(column.field)}`
|
||||
)
|
||||
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, '', column.filterControlPlaceholder)
|
||||
filterDataType(filterDataSource, selectControl)
|
||||
} else {
|
||||
throw new SyntaxError(
|
||||
'Error. You should use any of these allowed filter data methods: var, json, url.' +
|
||||
' Use like this: var: {key: "value"}'
|
||||
)
|
||||
}
|
||||
|
||||
let variableValues
|
||||
let key
|
||||
// eslint-disable-next-line default-case
|
||||
switch (filterDataType) {
|
||||
case 'url':
|
||||
$.ajax({
|
||||
url: filterDataSource,
|
||||
dataType: 'json',
|
||||
success (data) {
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in data) {
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, key, data[key])
|
||||
}
|
||||
UtilsFilterControl.sortSelectControl(selectControl)
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'var':
|
||||
variableValues = window[filterDataSource]
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (key in variableValues) {
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, key, variableValues[key])
|
||||
}
|
||||
UtilsFilterControl.sortSelectControl(selectControl)
|
||||
break
|
||||
case 'jso':
|
||||
variableValues = JSON.parse(filterDataSource)
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (key in variableValues) {
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, key, variableValues[key])
|
||||
}
|
||||
UtilsFilterControl.sortSelectControl(selectControl)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (addedFilterControl) {
|
||||
header.off('keyup', 'input').on('keyup', 'input', (event, obj) => {
|
||||
// Simulate enter key action from clear button
|
||||
event.keyCode = obj ? obj.keyCode : event.keyCode
|
||||
|
||||
if (that.options.searchOnEnterKey && event.keyCode !== 13) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($.inArray(event.keyCode, [37, 38, 39, 40]) > -1) {
|
||||
return
|
||||
}
|
||||
|
||||
const $currentTarget = $(event.currentTarget)
|
||||
|
||||
if ($currentTarget.is(':checkbox') || $currentTarget.is(':radio')) {
|
||||
return
|
||||
}
|
||||
|
||||
clearTimeout(event.currentTarget.timeoutId || 0)
|
||||
event.currentTarget.timeoutId = setTimeout(() => {
|
||||
that.onColumnSearch(event)
|
||||
}, that.options.searchTimeOut)
|
||||
})
|
||||
|
||||
header.off('change', 'select').on('change', 'select', event => {
|
||||
if (that.options.searchOnEnterKey && event.keyCode !== 13) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($.inArray(event.keyCode, [37, 38, 39, 40]) > -1) {
|
||||
return
|
||||
}
|
||||
|
||||
clearTimeout(event.currentTarget.timeoutId || 0)
|
||||
event.currentTarget.timeoutId = setTimeout(() => {
|
||||
that.onColumnSearch(event)
|
||||
}, that.options.searchTimeOut)
|
||||
})
|
||||
|
||||
header.off('mouseup', 'input').on('mouseup', 'input', function (event) {
|
||||
const $input = $(this)
|
||||
const oldValue = $input.val()
|
||||
|
||||
if (oldValue === '') {
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const newValue = $input.val()
|
||||
|
||||
if (newValue === '') {
|
||||
clearTimeout(event.currentTarget.timeoutId || 0)
|
||||
event.currentTarget.timeoutId = setTimeout(() => {
|
||||
that.onColumnSearch(event)
|
||||
}, that.options.searchTimeOut)
|
||||
}
|
||||
}, 1)
|
||||
})
|
||||
|
||||
if (header.find('.date-filter-control').length > 0) {
|
||||
$.each(that.columns, (i, { filterControl, field, filterDatepickerOptions }) => {
|
||||
if (
|
||||
filterControl !== undefined &&
|
||||
filterControl.toLowerCase() === 'datepicker'
|
||||
) {
|
||||
header
|
||||
.find(
|
||||
`.date-filter-control.bootstrap-table-filter-control-${field}`
|
||||
)
|
||||
.datepicker(filterDatepickerOptions)
|
||||
.on('changeDate', ({ currentTarget }) => {
|
||||
$(currentTarget).val(
|
||||
currentTarget.value
|
||||
)
|
||||
// Fired the keyup event
|
||||
$(currentTarget).keyup()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
header.find('.filterControl').hide()
|
||||
}
|
||||
},
|
||||
getDirectionOfSelectOptions (_alignment) {
|
||||
const alignment = _alignment === undefined ? 'left' : _alignment.toLowerCase()
|
||||
|
||||
switch (alignment) {
|
||||
case 'left':
|
||||
return 'ltr'
|
||||
case 'right':
|
||||
return 'rtl'
|
||||
case 'auto':
|
||||
return 'auto'
|
||||
default:
|
||||
return 'ltr'
|
||||
}
|
||||
}
|
||||
}
|
||||
const filterDataMethods = {
|
||||
var (filterDataSource, selectControl) {
|
||||
const variableValues = window[filterDataSource]
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in variableValues) {
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, key, variableValues[key])
|
||||
}
|
||||
UtilsFilterControl.sortSelectControl(selectControl)
|
||||
},
|
||||
url (filterDataSource, selectControl) {
|
||||
$.ajax({
|
||||
url: filterDataSource,
|
||||
dataType: 'json',
|
||||
success (data) {
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in data) {
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, key, data[key])
|
||||
}
|
||||
UtilsFilterControl.sortSelectControl(selectControl)
|
||||
}
|
||||
})
|
||||
},
|
||||
json (filterDataSource, selectControl) {
|
||||
const variableValues = JSON.parse(filterDataSource)
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in variableValues) {
|
||||
UtilsFilterControl.addOptionToSelectControl(selectControl, key, variableValues[key])
|
||||
}
|
||||
UtilsFilterControl.sortSelectControl(selectControl)
|
||||
}
|
||||
}
|
||||
|
||||
const bootstrap = {
|
||||
3: {
|
||||
icons: {
|
||||
clear: 'glyphicon-trash icon-clear'
|
||||
}
|
||||
},
|
||||
4: {
|
||||
icons: {
|
||||
clear: 'fa-trash icon-clear'
|
||||
}
|
||||
}
|
||||
}[Utils.bootstrapVersion]
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
filterControl: false,
|
||||
onColumnSearch (field, text) {
|
||||
return false
|
||||
},
|
||||
onCreatedControls () {
|
||||
return true
|
||||
},
|
||||
filterShowClear: false,
|
||||
alignmentSelectControlOptions: undefined,
|
||||
filterTemplate: {
|
||||
input (that, field, isVisible, placeholder) {
|
||||
return Utils.sprintf(
|
||||
'<input type="text" class="form-control bootstrap-table-filter-control-%s" style="width: 100%; visibility: %s" placeholder="%s">',
|
||||
field,
|
||||
isVisible,
|
||||
placeholder
|
||||
)
|
||||
},
|
||||
select ({ options }, field, isVisible) {
|
||||
return Utils.sprintf(
|
||||
'<select class="form-control bootstrap-table-filter-control-%s" style="width: 100%; visibility: %s" dir="%s"></select>',
|
||||
field,
|
||||
isVisible,
|
||||
UtilsFilterControl.getDirectionOfSelectOptions(
|
||||
options.alignmentSelectControlOptions
|
||||
)
|
||||
)
|
||||
},
|
||||
datepicker (that, field, isVisible) {
|
||||
return Utils.sprintf(
|
||||
'<input type="text" class="form-control date-filter-control bootstrap-table-filter-control-%s" style="width: 100%; visibility: %s">',
|
||||
field,
|
||||
isVisible
|
||||
)
|
||||
}
|
||||
},
|
||||
disableControlWhenSearch: false,
|
||||
searchOnEnterKey: false,
|
||||
// internal variables
|
||||
valuesFilterControl: []
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.columnDefaults, {
|
||||
filterControl: undefined,
|
||||
filterData: undefined,
|
||||
filterDatepickerOptions: undefined,
|
||||
filterStrictSearch: false,
|
||||
filterStartsWithSearch: false,
|
||||
filterControlPlaceholder: ''
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'column-search.bs.table': 'onColumnSearch',
|
||||
'created-controls.bs.table': 'onCreatedControls'
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, {
|
||||
clear: bootstrap.icons.clear
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatClearFilters () {
|
||||
return 'Clear Filters'
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
|
||||
|
||||
$.fn.bootstrapTable.methods.push('triggerSearch')
|
||||
$.fn.bootstrapTable.methods.push('clearFilterControl')
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
init () {
|
||||
// Make sure that the filterControl option is set
|
||||
if (this.options.filterControl) {
|
||||
const that = this
|
||||
|
||||
// Make sure that the internal variables are set correctly
|
||||
this.options.valuesFilterControl = []
|
||||
|
||||
this.$el
|
||||
.on('reset-view.bs.table', () => {
|
||||
// Create controls on $tableHeader if the height is set
|
||||
if (!that.options.height) {
|
||||
return
|
||||
}
|
||||
|
||||
// Avoid recreate the controls
|
||||
if (
|
||||
that.$tableHeader.find('select').length > 0 ||
|
||||
that.$tableHeader.find('input').length > 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
UtilsFilterControl.createControls(that, that.$tableHeader)
|
||||
})
|
||||
.on('post-header.bs.table', () => {
|
||||
UtilsFilterControl.setValues(that)
|
||||
})
|
||||
.on('post-body.bs.table', () => {
|
||||
if (that.options.height) {
|
||||
UtilsFilterControl.fixHeaderCSS(that)
|
||||
}
|
||||
})
|
||||
.on('column-switch.bs.table', () => {
|
||||
UtilsFilterControl.setValues(that)
|
||||
})
|
||||
.on('load-success.bs.table', () => {
|
||||
that.EnableControls(true)
|
||||
})
|
||||
.on('load-error.bs.table', () => {
|
||||
that.EnableControls(true)
|
||||
})
|
||||
}
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
initToolbar () {
|
||||
this.showToolbar =
|
||||
this.showToolbar ||
|
||||
(this.options.filterControl && this.options.filterShowClear)
|
||||
|
||||
super.initToolbar()
|
||||
|
||||
if (this.options.filterControl && this.options.filterShowClear) {
|
||||
const $btnGroup = this.$toolbar.find('>.btn-group')
|
||||
let $btnClear = $btnGroup.find('.filter-show-clear')
|
||||
|
||||
if (!$btnClear.length) {
|
||||
$btnClear = $(
|
||||
[
|
||||
Utils.sprintf(
|
||||
'<button class="btn btn-%s filter-show-clear" ',
|
||||
this.options.buttonsClass
|
||||
),
|
||||
Utils.sprintf(
|
||||
'type="button" title="%s">',
|
||||
this.options.formatClearFilters()
|
||||
),
|
||||
Utils.sprintf(
|
||||
'<i class="%s %s"></i> ',
|
||||
this.options.iconsPrefix,
|
||||
this.options.icons.clear
|
||||
),
|
||||
'</button>'
|
||||
].join('')
|
||||
).appendTo($btnGroup)
|
||||
|
||||
$btnClear
|
||||
.off('click')
|
||||
.on('click', $.proxy(this.clearFilterControl, this))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initHeader () {
|
||||
super.initHeader()
|
||||
|
||||
if (!this.options.filterControl) {
|
||||
return
|
||||
}
|
||||
UtilsFilterControl.createControls(this, this.$header)
|
||||
}
|
||||
initBody () {
|
||||
super.initBody()
|
||||
|
||||
UtilsFilterControl.initFilterSelectControls(this)
|
||||
}
|
||||
|
||||
initSearch () {
|
||||
const that = this
|
||||
const fp = $.isEmptyObject(that.filterColumnsPartial)
|
||||
? null
|
||||
: that.filterColumnsPartial
|
||||
|
||||
if (fp === null || Object.keys(fp).length <= 1) {
|
||||
super.initSearch()
|
||||
}
|
||||
|
||||
if (this.options.sidePagination === 'server') {
|
||||
return
|
||||
}
|
||||
|
||||
if (fp === null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check partial column filter
|
||||
that.data = fp
|
||||
? that.options.data.filter((item, i) => {
|
||||
const itemIsExpected = []
|
||||
Object.keys(item).forEach((key, index) => {
|
||||
const thisColumn = that.columns[that.fieldsColumnsIndex[key]]
|
||||
const fval = (fp[key] || '').toLowerCase()
|
||||
let value = item[key]
|
||||
|
||||
if (fval === '') {
|
||||
itemIsExpected.push(true)
|
||||
} else {
|
||||
// Fix #142: search use formated data
|
||||
if (thisColumn && thisColumn.searchFormatter) {
|
||||
value = $.fn.bootstrapTable.utils.calculateObjectValue(
|
||||
that.header,
|
||||
that.header.formatters[$.inArray(key, that.header.fields)],
|
||||
[value, item, i],
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
if ($.inArray(key, that.header.fields) !== -1) {
|
||||
if (typeof value === 'string' || typeof value === 'number') {
|
||||
if (thisColumn.filterStrictSearch) {
|
||||
if (value.toString().toLowerCase() === fval.toString().toLowerCase()) {
|
||||
itemIsExpected.push(true)
|
||||
} else {
|
||||
itemIsExpected.push(false)
|
||||
}
|
||||
} else if (thisColumn.filterStartsWithSearch) {
|
||||
if ((`${value}`).toLowerCase().indexOf(fval) === 0) {
|
||||
itemIsExpected.push(true)
|
||||
} else {
|
||||
itemIsExpected.push(false)
|
||||
}
|
||||
} else {
|
||||
if ((`${value}`).toLowerCase().includes(fval)) {
|
||||
itemIsExpected.push(true)
|
||||
} else {
|
||||
itemIsExpected.push(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return !itemIsExpected.includes(false)
|
||||
})
|
||||
: that.data
|
||||
}
|
||||
|
||||
initColumnSearch (filterColumnsDefaults) {
|
||||
UtilsFilterControl.copyValues(this)
|
||||
|
||||
if (filterColumnsDefaults) {
|
||||
this.filterColumnsPartial = filterColumnsDefaults
|
||||
this.updatePagination()
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const filter in filterColumnsDefaults) {
|
||||
this.trigger('column-search', filter, filterColumnsDefaults[filter])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onColumnSearch (event) {
|
||||
if ($.inArray(event.keyCode, [37, 38, 39, 40]) > -1) {
|
||||
return
|
||||
}
|
||||
|
||||
UtilsFilterControl.copyValues(this)
|
||||
const text = $.trim($(event.currentTarget).val())
|
||||
const $field = $(event.currentTarget)
|
||||
.closest('[data-field]')
|
||||
.data('field')
|
||||
|
||||
if ($.isEmptyObject(this.filterColumnsPartial)) {
|
||||
this.filterColumnsPartial = {}
|
||||
}
|
||||
if (text) {
|
||||
this.filterColumnsPartial[$field] = text
|
||||
} else {
|
||||
delete this.filterColumnsPartial[$field]
|
||||
}
|
||||
|
||||
// if the searchText is the same as the previously selected column value,
|
||||
// bootstrapTable will not try searching again (even though the selected column
|
||||
// may be different from the previous search). As a work around
|
||||
// we're manually appending some text to bootrap's searchText field
|
||||
// to guarantee that it will perform a search again when we call this.onSearch(event)
|
||||
this.searchText += 'randomText'
|
||||
|
||||
this.options.pageNumber = 1
|
||||
this.EnableControls(false)
|
||||
this.onSearch(event)
|
||||
this.trigger('column-search', $field, text)
|
||||
}
|
||||
|
||||
clearFilterControl () {
|
||||
if (this.options.filterControl && this.options.filterShowClear) {
|
||||
const that = this
|
||||
const cookies = UtilsFilterControl.collectBootstrapCookies()
|
||||
const header = UtilsFilterControl.getCurrentHeader(that)
|
||||
const table = header.closest('table')
|
||||
const controls = header.find(UtilsFilterControl.getCurrentSearchControls(that))
|
||||
const search = that.$toolbar.find('.search input')
|
||||
let hasValues = false
|
||||
let timeoutId = 0
|
||||
|
||||
$.each(that.options.valuesFilterControl, (i, item) => {
|
||||
hasValues = hasValues ? true : item.value !== ''
|
||||
item.value = ''
|
||||
})
|
||||
|
||||
UtilsFilterControl.setValues(that)
|
||||
|
||||
// clear cookies once the filters are clean
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => {
|
||||
if (cookies && cookies.length > 0) {
|
||||
$.each(cookies, (i, item) => {
|
||||
if (that.deleteCookie !== undefined) {
|
||||
that.deleteCookie(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, that.options.searchTimeOut)
|
||||
|
||||
// If there is not any value in the controls exit this method
|
||||
if (!hasValues) {
|
||||
return
|
||||
}
|
||||
|
||||
// Clear each type of filter if it exists.
|
||||
// Requires the body to reload each time a type of filter is found because we never know
|
||||
// which ones are going to be present.
|
||||
if (controls.length > 0) {
|
||||
this.filterColumnsPartial = {}
|
||||
$(controls[0]).trigger(
|
||||
controls[0].tagName === 'INPUT' ? 'keyup' : 'change', { keyCode: 13 }
|
||||
)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if (search.length > 0) {
|
||||
that.resetSearch()
|
||||
}
|
||||
|
||||
// use the default sort order if it exists. do nothing if it does not
|
||||
if (
|
||||
that.options.sortName !== table.data('sortName') ||
|
||||
that.options.sortOrder !== table.data('sortOrder')
|
||||
) {
|
||||
const sorter = header.find(
|
||||
Utils.sprintf(
|
||||
'[data-field="%s"]',
|
||||
$(controls[0])
|
||||
.closest('table')
|
||||
.data('sortName')
|
||||
)
|
||||
)
|
||||
if (sorter.length > 0) {
|
||||
that.onSort({ type: 'keypress', currentTarget: sorter })
|
||||
$(sorter)
|
||||
.find('.sortable')
|
||||
.trigger('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
triggerSearch () {
|
||||
const header = UtilsFilterControl.getCurrentHeader(this)
|
||||
const searchControls = UtilsFilterControl.getCurrentSearchControls(this)
|
||||
|
||||
header.find(searchControls).each(function () {
|
||||
const el = $(this)
|
||||
if (el.is('select')) {
|
||||
el.change()
|
||||
} else {
|
||||
el.keyup()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
EnableControls (enable) {
|
||||
if (
|
||||
this.options.disableControlWhenSearch &&
|
||||
this.options.sidePagination === 'server'
|
||||
) {
|
||||
const header = UtilsFilterControl.getCurrentHeader(this)
|
||||
const searchControls = UtilsFilterControl.getCurrentSearchControls(this)
|
||||
|
||||
if (!enable) {
|
||||
header.find(searchControls).prop('disabled', 'disabled')
|
||||
} else {
|
||||
header.find(searchControls).removeProp('disabled')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Filter Control",
|
||||
"version": "2.1.0",
|
||||
"description": "Plugin to add input/select element on the top of the columns in order to filter the data.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/filter-control",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/filter-control.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-filter-control",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/filter-control"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.fixed-table-header-columns,
|
||||
.fixed-table-body-columns {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.fixed-table-header-columns {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.fixed-table-header-columns .table,
|
||||
.fixed-table-body-columns .table {
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.fixed-table-header-columns .table.table-no-bordered,
|
||||
.fixed-table-body-columns .table.table-no-bordered {
|
||||
border-right: 1px solid transparent;
|
||||
}
|
||||
|
||||
.fixed-table-body-columns table {
|
||||
position: absolute;
|
||||
animation: none;
|
||||
}
|
||||
115
InvenTree/InvenTree/static/bootstrap-table/extensions/fixed-columns/bootstrap-table-fixed-columns.js
vendored
Normal file
115
InvenTree/InvenTree/static/bootstrap-table/extensions/fixed-columns/bootstrap-table-fixed-columns.js
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
($ => {
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
fixedColumns: false,
|
||||
fixedNumber: 1
|
||||
})
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
|
||||
fitHeader (...args) {
|
||||
super.fitHeader(...args)
|
||||
|
||||
if (!this.options.fixedColumns) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.$el.is(':hidden')) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$container.find('.fixed-table-header-columns').remove()
|
||||
this.$fixedHeader = $('<div class="fixed-table-header-columns"></div>')
|
||||
this.$fixedHeader.append(this.$tableHeader.find('>table').clone(true))
|
||||
this.$tableHeader.after(this.$fixedHeader)
|
||||
|
||||
const width = this.getFixedColumnsWidth()
|
||||
|
||||
this.$fixedHeader.css({
|
||||
top: 0,
|
||||
width,
|
||||
height: this.$tableHeader.outerHeight(true)
|
||||
})
|
||||
|
||||
this.initFixedColumnsBody()
|
||||
|
||||
this.$fixedBody.css({
|
||||
top: this.$tableHeader.outerHeight(true),
|
||||
width,
|
||||
height: this.$tableBody.outerHeight(true) - 1
|
||||
})
|
||||
|
||||
this.initFixedColumnsEvents()
|
||||
}
|
||||
|
||||
initBody (...args) {
|
||||
super.initBody(...args)
|
||||
|
||||
if (!this.options.fixedColumns) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.options.showHeader && this.options.height) {
|
||||
return
|
||||
}
|
||||
|
||||
this.initFixedColumnsBody()
|
||||
|
||||
this.$fixedBody.css({
|
||||
top: 0,
|
||||
width: this.getFixedColumnsWidth(),
|
||||
height: this.$tableHeader.outerHeight(true) + this.$tableBody.outerHeight(true)
|
||||
})
|
||||
|
||||
this.initFixedColumnsEvents()
|
||||
}
|
||||
|
||||
initFixedColumnsBody () {
|
||||
this.$container.find('.fixed-table-body-columns').remove()
|
||||
this.$fixedBody = $('<div class="fixed-table-body-columns"></div>')
|
||||
this.$fixedBody.append(this.$tableBody.find('>table').clone(true))
|
||||
this.$tableBody.after(this.$fixedBody)
|
||||
}
|
||||
|
||||
getFixedColumnsWidth () {
|
||||
const visibleFields = this.getVisibleFields()
|
||||
let width = 0
|
||||
|
||||
for (let i = 0; i < this.options.fixedNumber; i++) {
|
||||
width += this.$header.find(`th[data-field="${visibleFields[i]}"]`).outerWidth(true)
|
||||
}
|
||||
|
||||
return width + 1
|
||||
}
|
||||
|
||||
initFixedColumnsEvents () {
|
||||
// events
|
||||
this.$tableBody.off('scroll.fixed-columns').on('scroll.fixed-columns', e => {
|
||||
this.$fixedBody.find('table').css('top', -$(e.currentTarget).scrollTop())
|
||||
})
|
||||
|
||||
this.$body.find('> tr[data-index]').off('hover').hover(e => {
|
||||
const index = $(e.currentTarget).data('index')
|
||||
this.$fixedBody.find(`tr[data-index="${index}"]`)
|
||||
.css('background-color', $(e.currentTarget).css('background-color'))
|
||||
}, e => {
|
||||
const index = $(e.currentTarget).data('index')
|
||||
const $tr = this.$fixedBody.find(`tr[data-index="${index}"]`)
|
||||
$tr.attr('style', $tr.attr('style').replace(/background-color:.*;/, ''))
|
||||
})
|
||||
|
||||
this.$fixedBody.find('tr[data-index]').off('hover').hover(e => {
|
||||
const index = $(e.currentTarget).data('index')
|
||||
this.$body.find(`tr[data-index="${index}"]`)
|
||||
.css('background-color', $(e.currentTarget).css('background-color'))
|
||||
}, e => {
|
||||
const index = $(e.currentTarget).data('index')
|
||||
const $tr = this.$body.find(`> tr[data-index="${index}"]`)
|
||||
$tr.attr('style', $tr.attr('style').replace(/background-color:.*;/, ''))
|
||||
})
|
||||
}
|
||||
}
|
||||
})(jQuery)
|
||||
11
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.css
vendored
Normal file
11
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.css
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.bootstrap-table .table > tbody > tr.groupBy {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bootstrap-table .table > tbody > tr.groupBy.expanded {
|
||||
|
||||
}
|
||||
|
||||
.bootstrap-table .table > tbody > tr.hidden + tr.detail-view {
|
||||
display: none;
|
||||
}
|
||||
230
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.js
vendored
Normal file
230
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.js
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* @author: Yura Knoxville
|
||||
* @version: v1.1.0
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var initBodyCaller,
|
||||
tableGroups;
|
||||
|
||||
// it only does '%s', and return '' when arguments are undefined
|
||||
var sprintf = function (str) {
|
||||
var args = arguments,
|
||||
flag = true,
|
||||
i = 1;
|
||||
|
||||
str = str.replace(/%s/g, function () {
|
||||
var arg = args[i++];
|
||||
|
||||
if (typeof arg === 'undefined') {
|
||||
flag = false;
|
||||
return '';
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
return flag ? str : '';
|
||||
};
|
||||
|
||||
var groupBy = function (array , f) {
|
||||
var groups = {};
|
||||
array.forEach(function(o) {
|
||||
var group = f(o);
|
||||
groups[group] = groups[group] || [];
|
||||
groups[group].push(o);
|
||||
});
|
||||
|
||||
return groups;
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
groupBy: false,
|
||||
groupByField: '',
|
||||
groupByFormatter: undefined
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initSort = BootstrapTable.prototype.initSort,
|
||||
_initBody = BootstrapTable.prototype.initBody,
|
||||
_updateSelected = BootstrapTable.prototype.updateSelected;
|
||||
|
||||
BootstrapTable.prototype.initSort = function () {
|
||||
_initSort.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
var that = this;
|
||||
tableGroups = [];
|
||||
|
||||
if ((this.options.groupBy) && (this.options.groupByField !== '')) {
|
||||
|
||||
if ((this.options.sortName != this.options.groupByField)) {
|
||||
this.data.sort(function(a, b) {
|
||||
return a[that.options.groupByField].localeCompare(b[that.options.groupByField]);
|
||||
});
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var groups = groupBy(that.data, function (item) {
|
||||
return [item[that.options.groupByField]];
|
||||
});
|
||||
|
||||
var index = 0;
|
||||
$.each(groups, function(key, value) {
|
||||
tableGroups.push({
|
||||
id: index,
|
||||
name: key,
|
||||
data: value
|
||||
});
|
||||
|
||||
value.forEach(function(item) {
|
||||
if (!item._data) {
|
||||
item._data = {};
|
||||
}
|
||||
|
||||
item._data['parent-index'] = index;
|
||||
});
|
||||
|
||||
index++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BootstrapTable.prototype.initBody = function () {
|
||||
initBodyCaller = true;
|
||||
|
||||
_initBody.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if ((this.options.groupBy) && (this.options.groupByField !== '')) {
|
||||
var that = this,
|
||||
checkBox = false,
|
||||
visibleColumns = 0;
|
||||
|
||||
this.columns.forEach(function(column) {
|
||||
if (column.checkbox) {
|
||||
checkBox = true;
|
||||
} else {
|
||||
if (column.visible) {
|
||||
visibleColumns += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.options.detailView && !this.options.cardView) {
|
||||
visibleColumns += 1;
|
||||
}
|
||||
|
||||
tableGroups.forEach(function(item){
|
||||
var html = [];
|
||||
|
||||
html.push(sprintf('<tr class="info groupBy expanded" data-group-index="%s">', item.id));
|
||||
|
||||
if (that.options.detailView && !that.options.cardView) {
|
||||
html.push('<td class="detail"></td>');
|
||||
}
|
||||
|
||||
if (checkBox) {
|
||||
html.push('<td class="bs-checkbox">',
|
||||
'<input name="btSelectGroup" type="checkbox" />',
|
||||
'</td>'
|
||||
);
|
||||
}
|
||||
var formattedValue = item.name;
|
||||
if (typeof(that.options.groupByFormatter) == "function") {
|
||||
formattedValue = that.options.groupByFormatter(item.name, item.id, item.data);
|
||||
}
|
||||
html.push('<td',
|
||||
sprintf(' colspan="%s"', visibleColumns),
|
||||
'>', formattedValue, '</td>'
|
||||
);
|
||||
|
||||
html.push('</tr>');
|
||||
|
||||
that.$body.find('tr[data-parent-index='+item.id+']:first').before($(html.join('')));
|
||||
});
|
||||
|
||||
this.$selectGroup = [];
|
||||
this.$body.find('[name="btSelectGroup"]').each(function() {
|
||||
var self = $(this);
|
||||
|
||||
that.$selectGroup.push({
|
||||
group: self,
|
||||
item: that.$selectItem.filter(function () {
|
||||
return ($(this).closest('tr').data('parent-index') ===
|
||||
self.closest('tr').data('group-index'));
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
this.$container.off('click', '.groupBy')
|
||||
.on('click', '.groupBy', function() {
|
||||
$(this).toggleClass('expanded');
|
||||
that.$body.find('tr[data-parent-index='+$(this).closest('tr').data('group-index')+']').toggleClass('hidden');
|
||||
});
|
||||
|
||||
this.$container.off('click', '[name="btSelectGroup"]')
|
||||
.on('click', '[name="btSelectGroup"]', function (event) {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
var self = $(this);
|
||||
var checked = self.prop('checked');
|
||||
that[checked ? 'checkGroup' : 'uncheckGroup']($(this).closest('tr').data('group-index'));
|
||||
});
|
||||
}
|
||||
|
||||
initBodyCaller = false;
|
||||
this.updateSelected();
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.updateSelected = function () {
|
||||
if (!initBodyCaller) {
|
||||
_updateSelected.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if ((this.options.groupBy) && (this.options.groupByField !== '')) {
|
||||
this.$selectGroup.forEach(function (item) {
|
||||
var checkGroup = item.item.filter(':enabled').length ===
|
||||
item.item.filter(':enabled').filter(':checked').length;
|
||||
|
||||
item.group.prop('checked', checkGroup);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.getGroupSelections = function (index) {
|
||||
var that = this;
|
||||
|
||||
return $.grep(this.data, function (row) {
|
||||
return (row[that.header.stateField] && (row._data['parent-index'] === index));
|
||||
});
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.checkGroup = function (index) {
|
||||
this.checkGroup_(index, true);
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.uncheckGroup = function (index) {
|
||||
this.checkGroup_(index, false);
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.checkGroup_ = function (index, checked) {
|
||||
var rows;
|
||||
var filter = function() {
|
||||
return ($(this).closest('tr').data('parent-index') === index);
|
||||
};
|
||||
|
||||
if (!checked) {
|
||||
rows = this.getGroupSelections(index);
|
||||
}
|
||||
|
||||
this.$selectItem.filter(filter).prop('checked', checked);
|
||||
|
||||
this.updateRows();
|
||||
this.updateSelected();
|
||||
if (checked) {
|
||||
rows = this.getGroupSelections(index);
|
||||
}
|
||||
this.trigger(checked ? 'check-all' : 'uncheck-all', rows);
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Group By V2",
|
||||
"version": "1.0.0",
|
||||
"description": "Group the data by field",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/group-by-v2",
|
||||
"example": "",
|
||||
"plugins": [],
|
||||
"author": {
|
||||
"name": "Knoxvillekm",
|
||||
"image": "https://avatars3.githubusercontent.com/u/11072464"
|
||||
}
|
||||
}
|
||||
53
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by/bootstrap-table-group-by.css
vendored
Normal file
53
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by/bootstrap-table-group-by.css
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
table.treetable tbody tr td {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
table.treetable span {
|
||||
background-position: center left;
|
||||
background-repeat: no-repeat;
|
||||
padding: .2em 0 .2em 1.5em;
|
||||
}
|
||||
|
||||
table.treetable tr.collapsed span.indenter a {
|
||||
background-image: url(data:image/png;base64,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);
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
table.treetable tr.expanded span.indenter a {
|
||||
background-image: url(data:image/png;base64,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);
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
table.treetable tr.branch {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
table.treetable tr.selected {
|
||||
background-color: #3875d7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
table.treetable tr span.indenter a {
|
||||
outline: none; /* Expander shows outline after upgrading to 3.0 (#141) */
|
||||
}
|
||||
|
||||
table.treetable tr.collapsed.selected span.indenter a {
|
||||
background-image: url(data:image/png;base64,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);
|
||||
}
|
||||
|
||||
table.treetable tr.expanded.selected span.indenter a {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAFtJREFUeNpi/P//PwMlgImBQsA44C6giQENDAwM//HgBmLCAF/AMBLjBUeixf///48L7/+PCvZjU4fPAAc0AxywqcMXCwegGJ1NckL6jx5wpKYDxqGXEkkCgAEAmrqBIejdgngAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
table.treetable tr.accept {
|
||||
background-color: #a3bce4;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
table.treetable tr.collapsed.accept td span.indenter a {
|
||||
background-image: url(data:image/x-png;base64,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);
|
||||
}
|
||||
|
||||
table.treetable tr.expanded.accept td span.indenter a {
|
||||
background-image: url(data:image/x-png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAFtJREFUeNpi/P//PwMlgImBQsA44C6giQENDAwM//HgBmLCAF/AMBLjBUeixf///48L7/+PCvZjU4fPAAc0AxywqcMXCwegGJ1NckL6jx5wpKYDxqGXEkkCgAEAmrqBIejdgngAAAAASUVORK5CYII=);
|
||||
}
|
||||
243
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by/bootstrap-table-group-by.js
vendored
Normal file
243
InvenTree/InvenTree/static/bootstrap-table/extensions/group-by/bootstrap-table-group-by.js
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v1.1.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var originalRowAttr,
|
||||
dataTTId = 'data-tt-id',
|
||||
dataTTParentId = 'data-tt-parent-id',
|
||||
obj = {},
|
||||
parentId = undefined;
|
||||
|
||||
var getParentRowId = function (that, id) {
|
||||
var parentRows = that.$body.find('tr').not('[' + 'data-tt-parent-id]');
|
||||
|
||||
for (var i = 0; i < parentRows.length; i++) {
|
||||
if (i === id) {
|
||||
return $(parentRows[i]).attr('data-tt-id');
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
var sumData = function (that, data) {
|
||||
var sumRow = {};
|
||||
$.each(data, function (i, row) {
|
||||
if (!row.IsParent) {
|
||||
for (var prop in row) {
|
||||
if (!isNaN(parseFloat(row[prop]))) {
|
||||
if (that.columns[that.fieldsColumnsIndex[prop]].groupBySumGroup) {
|
||||
if (sumRow[prop] === undefined) {
|
||||
sumRow[prop] = 0;
|
||||
}
|
||||
sumRow[prop] += +row[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return sumRow;
|
||||
};
|
||||
|
||||
var rowAttr = function (row, index) {
|
||||
//Call the User Defined Function
|
||||
originalRowAttr.apply([row, index]);
|
||||
|
||||
obj[dataTTId.toString()] = index;
|
||||
|
||||
if (!row.IsParent) {
|
||||
obj[dataTTParentId.toString()] = parentId === undefined ? index : parentId;
|
||||
} else {
|
||||
parentId = index;
|
||||
delete obj[dataTTParentId.toString()];
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
var setObjectKeys = function () {
|
||||
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
|
||||
Object.keys = function (o) {
|
||||
if (o !== Object(o)) {
|
||||
throw new TypeError('Object.keys called on a non-object');
|
||||
}
|
||||
var k = [],
|
||||
p;
|
||||
for (p in o) {
|
||||
if (Object.prototype.hasOwnProperty.call(o, p)) {
|
||||
k.push(p);
|
||||
}
|
||||
}
|
||||
return k;
|
||||
}
|
||||
};
|
||||
|
||||
var getDataArrayFromItem = function (that, item) {
|
||||
var itemDataArray = [];
|
||||
for (var i = 0; i < that.options.groupByField.length; i++) {
|
||||
itemDataArray.push(item[that.options.groupByField[i]]);
|
||||
}
|
||||
|
||||
return itemDataArray;
|
||||
};
|
||||
|
||||
var getNewRow = function (that, result, index) {
|
||||
var newRow = {};
|
||||
for (var i = 0; i < that.options.groupByField.length; i++) {
|
||||
newRow[that.options.groupByField[i].toString()] = result[index][0][that.options.groupByField[i]];
|
||||
}
|
||||
|
||||
newRow.IsParent = true;
|
||||
|
||||
return newRow;
|
||||
};
|
||||
|
||||
var groupBy = function (array, f) {
|
||||
var groups = {};
|
||||
$.each(array, function (i, o) {
|
||||
var group = JSON.stringify(f(o));
|
||||
groups[group] = groups[group] || [];
|
||||
groups[group].push(o);
|
||||
});
|
||||
return Object.keys(groups).map(function (group) {
|
||||
return groups[group];
|
||||
});
|
||||
};
|
||||
|
||||
var makeGrouped = function (that, data) {
|
||||
var newData = [],
|
||||
sumRow = {};
|
||||
|
||||
var result = groupBy(data, function (item) {
|
||||
return getDataArrayFromItem(that, item);
|
||||
});
|
||||
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
result[i].unshift(getNewRow(that, result, i));
|
||||
if (that.options.groupBySumGroup) {
|
||||
sumRow = sumData(that, result[i]);
|
||||
if (!$.isEmptyObject(sumRow)) {
|
||||
result[i].push(sumRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newData = newData.concat.apply(newData, result);
|
||||
|
||||
if (!that.options.loaded && newData.length > 0) {
|
||||
that.options.loaded = true;
|
||||
that.options.originalData = that.options.data;
|
||||
that.options.data = newData;
|
||||
}
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
groupBy: false,
|
||||
groupByField: [],
|
||||
groupBySumGroup: false,
|
||||
groupByInitExpanded: undefined, //node, 'all'
|
||||
//internal variables
|
||||
loaded: false,
|
||||
originalData: undefined
|
||||
});
|
||||
|
||||
$.fn.bootstrapTable.methods.push('collapseAll', 'expandAll', 'refreshGroupByField');
|
||||
|
||||
$.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
|
||||
groupBySumGroup: false
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init,
|
||||
_initData = BootstrapTable.prototype.initData;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
//Temporal validation
|
||||
if (!this.options.sortName) {
|
||||
if ((this.options.groupBy) && (this.options.groupByField.length > 0)) {
|
||||
var that = this;
|
||||
|
||||
// Compatibility: IE < 9 and old browsers
|
||||
if (!Object.keys) {
|
||||
$.fn.bootstrapTable.utils.objectKeys();
|
||||
}
|
||||
|
||||
//Make sure that the internal variables are set correctly
|
||||
this.options.loaded = false;
|
||||
this.options.originalData = undefined;
|
||||
|
||||
originalRowAttr = this.options.rowAttributes;
|
||||
this.options.rowAttributes = rowAttr;
|
||||
this.$el.off('post-body.bs.table').on('post-body.bs.table', function () {
|
||||
that.$el.treetable({
|
||||
expandable: true,
|
||||
onNodeExpand: function () {
|
||||
if (that.options.height) {
|
||||
that.resetHeader();
|
||||
}
|
||||
},
|
||||
onNodeCollapse: function () {
|
||||
if (that.options.height) {
|
||||
that.resetHeader();
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (that.options.groupByInitExpanded !== undefined) {
|
||||
if (typeof that.options.groupByInitExpanded === 'number') {
|
||||
that.expandNode(that.options.groupByInitExpanded);
|
||||
} else if (that.options.groupByInitExpanded.toLowerCase() === 'all') {
|
||||
that.expandAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.initData = function (data, type) {
|
||||
//Temporal validation
|
||||
if (!this.options.sortName) {
|
||||
if ((this.options.groupBy) && (this.options.groupByField.length > 0)) {
|
||||
|
||||
this.options.groupByField = typeof this.options.groupByField === 'string' ?
|
||||
this.options.groupByField.replace('[', '').replace(']', '')
|
||||
.replace(/ /g, '').toLowerCase().split(',') : this.options.groupByField;
|
||||
|
||||
data = makeGrouped(this, data ? data : this.options.data);
|
||||
}
|
||||
}
|
||||
_initData.apply(this, [data, type]);
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.expandAll = function () {
|
||||
this.$el.treetable('expandAll');
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.collapseAll = function () {
|
||||
this.$el.treetable('collapseAll');
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.expandNode = function (id) {
|
||||
id = getParentRowId(this, id);
|
||||
if (id !== undefined) {
|
||||
this.$el.treetable('expandNode', id);
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.refreshGroupByField = function (groupByFields) {
|
||||
if (!$.fn.bootstrapTable.utils.compareObjects(this.options.groupByField, groupByFields)) {
|
||||
this.options.groupByField = groupByFields;
|
||||
this.load(this.options.originalData);
|
||||
}
|
||||
};
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Group By",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to group the data by fields.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/group-by",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-group-by",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/group-by"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @author: Jewway
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
'use strict';
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor;
|
||||
|
||||
BootstrapTable.prototype.changeTitle = function (locale) {
|
||||
$.each(this.options.columns, function (idx, columnList) {
|
||||
$.each(columnList, function (idx, column) {
|
||||
if (column.field) {
|
||||
column.title = locale[column.field];
|
||||
}
|
||||
});
|
||||
});
|
||||
this.initHeader();
|
||||
this.initBody();
|
||||
this.initToolbar();
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.changeLocale = function (localeId) {
|
||||
this.options.locale = localeId;
|
||||
this.initLocale();
|
||||
this.initPagination();
|
||||
this.initBody();
|
||||
this.initToolbar();
|
||||
};
|
||||
|
||||
$.fn.bootstrapTable.methods.push('changeTitle');
|
||||
$.fn.bootstrapTable.methods.push('changeLocale');
|
||||
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "i18n Enhance",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to add i18n API in order to change column's title and table locale.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/i18n-enhance",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/i18n-enhance.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-i18n-enhance",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/i18n-enhance"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "Jewway",
|
||||
"image": "https://avatars0.githubusercontent.com/u/3501899"
|
||||
}
|
||||
}
|
||||
80
InvenTree/InvenTree/static/bootstrap-table/extensions/key-events/bootstrap-table-key-events.js
vendored
Normal file
80
InvenTree/InvenTree/static/bootstrap-table/extensions/key-events/bootstrap-table-key-events.js
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v1.0.0
|
||||
*
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
keyEvents: false
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
this.initKeyEvents();
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.initKeyEvents = function () {
|
||||
if (this.options.keyEvents) {
|
||||
var that = this;
|
||||
|
||||
$(document).off('keydown').on('keydown', function (e) {
|
||||
var $search = that.$toolbar.find('.search input'),
|
||||
$refresh = that.$toolbar.find('button[name="refresh"]'),
|
||||
$toggle = that.$toolbar.find('button[name="toggle"]'),
|
||||
$paginationSwitch = that.$toolbar.find('button[name="paginationSwitch"]');
|
||||
|
||||
if (document.activeElement === $search.get(0) || !$.contains(document.activeElement ,that.$toolbar.get(0))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 83: //s
|
||||
if (!that.options.search) {
|
||||
return;
|
||||
}
|
||||
$search.focus();
|
||||
return false;
|
||||
case 82: //r
|
||||
if (!that.options.showRefresh) {
|
||||
return;
|
||||
}
|
||||
$refresh.click();
|
||||
return false;
|
||||
case 84: //t
|
||||
if (!that.options.showToggle) {
|
||||
return;
|
||||
}
|
||||
$toggle.click();
|
||||
return false;
|
||||
case 80: //p
|
||||
if (!that.options.showPaginationSwitch) {
|
||||
return;
|
||||
}
|
||||
$paginationSwitch.click();
|
||||
return false;
|
||||
case 37: // left
|
||||
if (!that.options.pagination) {
|
||||
return;
|
||||
}
|
||||
that.prevPage();
|
||||
return false;
|
||||
case 39: // right
|
||||
if (!that.options.pagination) {
|
||||
return;
|
||||
}
|
||||
that.nextPage();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Key Events",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support the key events in the bootstrap table.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/key-events",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/key-events.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-key-events",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/key-events"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
136
InvenTree/InvenTree/static/bootstrap-table/extensions/mobile/bootstrap-table-mobile.js
vendored
Normal file
136
InvenTree/InvenTree/static/bootstrap-table/extensions/mobile/bootstrap-table-mobile.js
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v1.1.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var showHideColumns = function (that, checked) {
|
||||
if (that.options.columnsHidden.length > 0 ) {
|
||||
$.each(that.columns, function (i, column) {
|
||||
if (that.options.columnsHidden.indexOf(column.field) !== -1) {
|
||||
if (column.visible !== checked) {
|
||||
that.toggleColumn(that.fieldsColumnsIndex[column.field], checked, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var resetView = function (that) {
|
||||
if (that.options.height || that.options.showFooter) {
|
||||
setTimeout(function(){
|
||||
that.resetView.call(that);
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
var changeView = function (that, width, height) {
|
||||
if (that.options.minHeight) {
|
||||
if ((width <= that.options.minWidth) && (height <= that.options.minHeight)) {
|
||||
conditionCardView(that);
|
||||
} else if ((width > that.options.minWidth) && (height > that.options.minHeight)) {
|
||||
conditionFullView(that);
|
||||
}
|
||||
} else {
|
||||
if (width <= that.options.minWidth) {
|
||||
conditionCardView(that);
|
||||
} else if (width > that.options.minWidth) {
|
||||
conditionFullView(that);
|
||||
}
|
||||
}
|
||||
|
||||
resetView(that);
|
||||
};
|
||||
|
||||
var conditionCardView = function (that) {
|
||||
changeTableView(that, false);
|
||||
showHideColumns(that, false);
|
||||
};
|
||||
|
||||
var conditionFullView = function (that) {
|
||||
changeTableView(that, true);
|
||||
showHideColumns(that, true);
|
||||
};
|
||||
|
||||
var changeTableView = function (that, cardViewState) {
|
||||
that.options.cardView = cardViewState;
|
||||
that.toggleView();
|
||||
};
|
||||
|
||||
var debounce = function(func,wait) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this,
|
||||
args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
func.apply(context,args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
mobileResponsive: false,
|
||||
minWidth: 562,
|
||||
minHeight: undefined,
|
||||
heightThreshold: 100, // just slightly larger than mobile chrome's auto-hiding toolbar
|
||||
checkOnInit: true,
|
||||
columnsHidden: []
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (!this.options.mobileResponsive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.minWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.minWidth < 100 && this.options.resizable) {
|
||||
console.log("The minWidth when the resizable extension is active should be greater or equal than 100");
|
||||
this.options.minWidth = 100;
|
||||
}
|
||||
|
||||
var that = this,
|
||||
old = {
|
||||
width: $(window).width(),
|
||||
height: $(window).height()
|
||||
};
|
||||
|
||||
$(window).on('resize orientationchange',debounce(function (evt) {
|
||||
// reset view if height has only changed by at least the threshold.
|
||||
var height = $(this).height(),
|
||||
width = $(this).width();
|
||||
|
||||
if (Math.abs(old.height - height) > that.options.heightThreshold || old.width != width) {
|
||||
changeView(that, width, height);
|
||||
old = {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
}
|
||||
},200));
|
||||
|
||||
if (this.options.checkOnInit) {
|
||||
var height = $(window).height(),
|
||||
width = $(window).width();
|
||||
changeView(this, width, height);
|
||||
old = {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
}
|
||||
};
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Mobile",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to support the responsive feature.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/mobile",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/mobile.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-mobile",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/mobile"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @author Homer Glascock <HopGlascock@gmail.com>
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
"use strict";
|
||||
|
||||
var sprintf = $.fn.bootstrapTable.utils.sprintf;
|
||||
|
||||
var reInit = function (self) {
|
||||
self.initHeader();
|
||||
self.initSearch();
|
||||
self.initPagination();
|
||||
self.initBody();
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showToggleBtn: false,
|
||||
multiToggleDefaults: [], //column names go here
|
||||
});
|
||||
|
||||
$.fn.bootstrapTable.methods.push('hideAllColumns', 'showAllColumns');
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initToolbar = BootstrapTable.prototype.initToolbar;
|
||||
|
||||
BootstrapTable.prototype.initToolbar = function () {
|
||||
|
||||
_initToolbar.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
var that = this,
|
||||
$btnGroup = this.$toolbar.find('>.btn-group');
|
||||
|
||||
if (typeof this.options.multiToggleDefaults === 'string') {
|
||||
this.options.multiToggleDefaults = JSON.parse(this.options.multiToggleDefaults);
|
||||
}
|
||||
|
||||
if (this.options.showToggleBtn && this.options.showColumns) {
|
||||
var showbtn = "<button class='btn btn-default hidden' id='showAllBtn'><span class='glyphicon glyphicon-resize-full icon-zoom-in'></span></button>",
|
||||
hidebtn = "<button class='btn btn-default' id='hideAllBtn'><span class='glyphicon glyphicon-resize-small icon-zoom-out'></span></button>";
|
||||
|
||||
$btnGroup.append(showbtn + hidebtn);
|
||||
|
||||
$btnGroup.find('#showAllBtn').click(function () { that.showAllColumns();
|
||||
$btnGroup.find('#hideAllBtn').toggleClass('hidden');
|
||||
$btnGroup.find('#showAllBtn').toggleClass('hidden');
|
||||
});
|
||||
$btnGroup.find('#hideAllBtn').click(function () { that.hideAllColumns();
|
||||
$btnGroup.find('#hideAllBtn').toggleClass('hidden');
|
||||
$btnGroup.find('#showAllBtn').toggleClass('hidden');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.hideAllColumns = function () {
|
||||
var that = this,
|
||||
defaults = that.options.multiToggleDefaults;
|
||||
|
||||
$.each(this.columns, function (index, column) {
|
||||
//if its one of the defaults dont touch it
|
||||
if (defaults.indexOf(column.field) == -1 && column.switchable) {
|
||||
column.visible = false;
|
||||
var $items = that.$toolbar.find('.keep-open input').prop('disabled', false);
|
||||
$items.filter(sprintf('[value="%s"]', index)).prop('checked', false);
|
||||
}
|
||||
});
|
||||
|
||||
reInit(that);
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.showAllColumns = function () {
|
||||
var that = this;
|
||||
$.each(this.columns, function (index, column) {
|
||||
if (column.switchable) {
|
||||
column.visible = true;
|
||||
}
|
||||
|
||||
var $items = that.$toolbar.find('.keep-open input').prop('disabled', false);
|
||||
$items.filter(sprintf('[value="%s"]', index)).prop('checked', true);
|
||||
});
|
||||
|
||||
reInit(that);
|
||||
|
||||
that.toggleColumn(0, that.columns[0].visible, false);
|
||||
};
|
||||
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Multi Column Toggle",
|
||||
"version": "1.0.0",
|
||||
"description": "Allows hiding and showing of multiple columns at once.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multi-column-toggle",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/multi-column-toggle.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "multi-column-toggle",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multi-column-toggle"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "Homer Glascock",
|
||||
"image": "https://avatars1.githubusercontent.com/u/5546710"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
multipleSearch: false,
|
||||
delimeter: " "
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initSearch = BootstrapTable.prototype.initSearch;
|
||||
|
||||
BootstrapTable.prototype.initSearch = function () {
|
||||
if (this.options.multipleSearch) {
|
||||
if (this.searchText === undefined) {
|
||||
return;
|
||||
}
|
||||
var strArray = this.searchText.split(this.options.delimeter),
|
||||
that = this,
|
||||
f = $.isEmptyObject(this.filterColumns) ? null : this.filterColumns,
|
||||
dataFiltered = [];
|
||||
|
||||
if (strArray.length === 1) {
|
||||
_initSearch.apply(this, Array.prototype.slice.apply(arguments));
|
||||
} else {
|
||||
for (var i = 0; i < strArray.length; i++) {
|
||||
var str = strArray[i].trim();
|
||||
dataFiltered = str ? $.grep(dataFiltered.length === 0 ? this.options.data : dataFiltered, function (item, i) {
|
||||
for (var key in item) {
|
||||
key = $.isNumeric(key) ? parseInt(key, 10) : key;
|
||||
var value = item[key],
|
||||
column = that.columns[that.fieldsColumnsIndex[key]],
|
||||
j = $.inArray(key, that.header.fields);
|
||||
|
||||
// Fix #142: search use formated data
|
||||
if (column && column.searchFormatter) {
|
||||
value = $.fn.bootstrapTable.utils.calculateObjectValue(column,
|
||||
that.header.formatters[j], [value, item, i], value);
|
||||
}
|
||||
|
||||
var index = $.inArray(key, that.header.fields);
|
||||
if (index !== -1 && that.header.searchables[index] && (typeof value === 'string' || typeof value === 'number')) {
|
||||
if (that.options.strictSearch) {
|
||||
if ((value + '').toLowerCase() === str) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ((value + '').toLowerCase().indexOf(str) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}) : this.data;
|
||||
}
|
||||
|
||||
this.data = dataFiltered;
|
||||
}
|
||||
} else {
|
||||
_initSearch.apply(this, Array.prototype.slice.apply(arguments));
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Multiple Search",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support the multiple search.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-search",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-multiple-search",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-search"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.multiple-select-row-selected {
|
||||
background: lightBlue
|
||||
}
|
||||
|
||||
.table tbody tr:hover td,
|
||||
.table tbody tr:hover th {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
.table-striped tbody tr:nth-child(odd):hover td {
|
||||
background-color: #F9F9F9;
|
||||
}
|
||||
|
||||
.fixed-table-container tbody .selected td {
|
||||
background: lightBlue;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
document.onselectstart = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
var getTableObjectFromCurrentTarget = function (currentTarget) {
|
||||
currentTarget = $(currentTarget);
|
||||
return currentTarget.is("table") ? currentTarget : currentTarget.parents().find(".table");
|
||||
};
|
||||
|
||||
var getRow = function (target) {
|
||||
target = $(target);
|
||||
return target.parent().parent();
|
||||
};
|
||||
|
||||
var onRowClick = function (e) {
|
||||
var that = getTableObjectFromCurrentTarget(e.currentTarget);
|
||||
|
||||
if (window.event.ctrlKey) {
|
||||
toggleRow(e.currentTarget, that, false, false);
|
||||
}
|
||||
|
||||
if (window.event.button === 0) {
|
||||
if (!window.event.ctrlKey && !window.event.shiftKey) {
|
||||
clearAll(that);
|
||||
toggleRow(e.currentTarget, that, false, false);
|
||||
}
|
||||
|
||||
if (window.event.shiftKey) {
|
||||
selectRowsBetweenIndexes([that.bootstrapTable("getOptions").multipleSelectRowLastSelectedRow.rowIndex, e.currentTarget.rowIndex], that)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var onCheckboxChange = function (e) {
|
||||
var that = getTableObjectFromCurrentTarget(e.currentTarget);
|
||||
clearAll(that);
|
||||
toggleRow(getRow(e.currentTarget), that, false, false);
|
||||
};
|
||||
|
||||
var toggleRow = function (row, that, clearAll, useShift) {
|
||||
if (clearAll) {
|
||||
row = $(row);
|
||||
that.bootstrapTable("getOptions").multipleSelectRowLastSelectedRow = undefined;
|
||||
row.removeClass(that.bootstrapTable("getOptions").multipleSelectRowCssClass);
|
||||
that.bootstrapTable("uncheck", row.data("index"));
|
||||
} else {
|
||||
that.bootstrapTable("getOptions").multipleSelectRowLastSelectedRow = row;
|
||||
row = $(row);
|
||||
if (useShift) {
|
||||
row.addClass(that.bootstrapTable("getOptions").multipleSelectRowCssClass);
|
||||
that.bootstrapTable("check", row.data("index"));
|
||||
} else {
|
||||
if(row.hasClass(that.bootstrapTable("getOptions").multipleSelectRowCssClass)) {
|
||||
row.removeClass(that.bootstrapTable("getOptions").multipleSelectRowCssClass)
|
||||
that.bootstrapTable("uncheck", row.data("index"));
|
||||
} else {
|
||||
row.addClass(that.bootstrapTable("getOptions").multipleSelectRowCssClass);
|
||||
that.bootstrapTable("check", row.data("index"));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var selectRowsBetweenIndexes = function (indexes, that) {
|
||||
indexes.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
for (var i = indexes[0]; i <= indexes[1]; i++) {
|
||||
toggleRow(that.bootstrapTable("getOptions").multipleSelectRowRows[i-1], that, false, true);
|
||||
}
|
||||
};
|
||||
|
||||
var clearAll = function (that) {
|
||||
for (var i = 0; i < that.bootstrapTable("getOptions").multipleSelectRowRows.length; i++) {
|
||||
toggleRow(that.bootstrapTable("getOptions").multipleSelectRowRows[i], that, true, false);
|
||||
}
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
multipleSelectRow: false,
|
||||
multipleSelectRowCssClass: 'multiple-select-row-selected',
|
||||
//internal variables used by the extension
|
||||
multipleSelectRowLastSelectedRow: undefined,
|
||||
multipleSelectRowRows: []
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init,
|
||||
_initBody = BootstrapTable.prototype.initBody;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
if (this.options.multipleSelectRow) {
|
||||
var that = this;
|
||||
|
||||
//Make sure that the internal variables have the correct value
|
||||
this.options.multipleSelectRowLastSelectedRow = undefined;
|
||||
this.options.multipleSelectRowRows = [];
|
||||
|
||||
this.$el.on("post-body.bs.table", function (e) {
|
||||
setTimeout(function () {
|
||||
that.options.multipleSelectRowRows = that.$body.children();
|
||||
that.options.multipleSelectRowRows.click(onRowClick);
|
||||
that.options.multipleSelectRowRows.find("input[type=checkbox]").change(onCheckboxChange);
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.clearAllMultipleSelectionRow = function () {
|
||||
clearAll(this);
|
||||
};
|
||||
|
||||
$.fn.bootstrapTable.methods.push('clearAllMultipleSelectionRow');
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Multiple Selection Row",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to enable the multiple selection row. You can use the ctrl+click to select one row or use ctrl+shift+click to select a range of rows.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-selection-row",
|
||||
"example": "",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-multiple-selection-row",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-selection-row"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
412
InvenTree/InvenTree/static/bootstrap-table/extensions/multiple-sort/bootstrap-table-multiple-sort.js
vendored
Normal file
412
InvenTree/InvenTree/static/bootstrap-table/extensions/multiple-sort/bootstrap-table-multiple-sort.js
vendored
Normal file
@@ -0,0 +1,412 @@
|
||||
/**
|
||||
* @author Nadim Basalamah <dimbslmh@gmail.com>
|
||||
* @version: v1.1.0
|
||||
* https://github.com/dimbslmh/bootstrap-table/tree/master/src/extensions/multiple-sort/bootstrap-table-multiple-sort.js
|
||||
* Modification: ErwannNevou <https://github.com/ErwannNevou>
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var isSingleSort = false;
|
||||
|
||||
var showSortModal = function(that) {
|
||||
var _selector = that.sortModalSelector,
|
||||
_id = '#' + _selector;
|
||||
|
||||
if (!$(_id).hasClass("modal")) {
|
||||
var sModal = ' <div class="modal fade" id="' + _selector + '" tabindex="-1" role="dialog" aria-labelledby="' + _selector + 'Label" aria-hidden="true">';
|
||||
sModal += ' <div class="modal-dialog">';
|
||||
sModal += ' <div class="modal-content">';
|
||||
sModal += ' <div class="modal-header">';
|
||||
sModal += ' <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>';
|
||||
sModal += ' <h4 class="modal-title" id="' + _selector + 'Label">' + that.options.formatMultipleSort() + '</h4>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' <div class="modal-body">';
|
||||
sModal += ' <div class="bootstrap-table">';
|
||||
sModal += ' <div class="fixed-table-toolbar">';
|
||||
sModal += ' <div class="bars">';
|
||||
sModal += ' <div id="toolbar">';
|
||||
sModal += ' <button id="add" type="button" class="btn btn-default"><i class="' + that.options.iconsPrefix + ' ' + that.options.icons.plus + '"></i> ' + that.options.formatAddLevel() + '</button>';
|
||||
sModal += ' <button id="delete" type="button" class="btn btn-default" disabled><i class="' + that.options.iconsPrefix + ' ' + that.options.icons.minus + '"></i> ' + that.options.formatDeleteLevel() + '</button>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' <div class="fixed-table-container">';
|
||||
sModal += ' <table id="multi-sort" class="table">';
|
||||
sModal += ' <thead>';
|
||||
sModal += ' <tr>';
|
||||
sModal += ' <th></th>';
|
||||
sModal += ' <th><div class="th-inner">' + that.options.formatColumn() + '</div></th>';
|
||||
sModal += ' <th><div class="th-inner">' + that.options.formatOrder() + '</div></th>';
|
||||
sModal += ' </tr>';
|
||||
sModal += ' </thead>';
|
||||
sModal += ' <tbody></tbody>';
|
||||
sModal += ' </table>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' <div class="modal-footer">';
|
||||
sModal += ' <button type="button" class="btn btn-default" data-dismiss="modal">' + that.options.formatCancel() + '</button>';
|
||||
sModal += ' <button type="button" class="btn btn-primary">' + that.options.formatSort() + '</button>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' </div>';
|
||||
sModal += ' </div>';
|
||||
|
||||
$('body').append($(sModal));
|
||||
|
||||
that.$sortModal = $(_id);
|
||||
var $rows = that.$sortModal.find('tbody > tr');
|
||||
|
||||
that.$sortModal.off('click', '#add').on('click', '#add', function() {
|
||||
var total = that.$sortModal.find('.multi-sort-name:first option').length,
|
||||
current = that.$sortModal.find('tbody tr').length;
|
||||
|
||||
if (current < total) {
|
||||
current++;
|
||||
that.addLevel();
|
||||
that.setButtonStates();
|
||||
}
|
||||
});
|
||||
|
||||
that.$sortModal.off('click', '#delete').on('click', '#delete', function() {
|
||||
var total = that.$sortModal.find('.multi-sort-name:first option').length,
|
||||
current = that.$sortModal.find('tbody tr').length;
|
||||
|
||||
if (current > 1 && current <= total) {
|
||||
current--;
|
||||
that.$sortModal.find('tbody tr:last').remove();
|
||||
that.setButtonStates();
|
||||
}
|
||||
});
|
||||
|
||||
that.$sortModal.off('click', '.btn-primary').on('click', '.btn-primary', function() {
|
||||
var $rows = that.$sortModal.find('tbody > tr'),
|
||||
$alert = that.$sortModal.find('div.alert'),
|
||||
fields = [],
|
||||
results = [];
|
||||
|
||||
|
||||
that.options.sortPriority = $.map($rows, function(row) {
|
||||
var $row = $(row),
|
||||
name = $row.find('.multi-sort-name').val(),
|
||||
order = $row.find('.multi-sort-order').val();
|
||||
|
||||
fields.push(name);
|
||||
|
||||
return {
|
||||
sortName: name,
|
||||
sortOrder: order
|
||||
};
|
||||
});
|
||||
|
||||
var sorted_fields = fields.sort();
|
||||
|
||||
for (var i = 0; i < fields.length - 1; i++) {
|
||||
if (sorted_fields[i + 1] == sorted_fields[i]) {
|
||||
results.push(sorted_fields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length > 0) {
|
||||
if ($alert.length === 0) {
|
||||
$alert = '<div class="alert alert-danger" role="alert"><strong>' + that.options.formatDuplicateAlertTitle() + '</strong> ' + that.options.formatDuplicateAlertDescription() + '</div>';
|
||||
$($alert).insertBefore(that.$sortModal.find('.bars'));
|
||||
}
|
||||
} else {
|
||||
if ($alert.length === 1) {
|
||||
$($alert).remove();
|
||||
}
|
||||
|
||||
that.$sortModal.modal('hide');
|
||||
that.options.sortName = '';
|
||||
|
||||
if (that.options.sidePagination === 'server') {
|
||||
var t = that.options.queryParams;
|
||||
that.options.queryParams = function(params) {
|
||||
params.multiSort = that.options.sortPriority;
|
||||
return t(params);
|
||||
};
|
||||
isSingleSort=false;
|
||||
that.initServer(that.options.silentSort);
|
||||
return;
|
||||
}
|
||||
that.onMultipleSort();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (that.options.sortPriority === null || that.options.sortPriority.length === 0) {
|
||||
if (that.options.sortName) {
|
||||
that.options.sortPriority = [{
|
||||
sortName: that.options.sortName,
|
||||
sortOrder: that.options.sortOrder
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if (that.options.sortPriority !== null && that.options.sortPriority.length > 0) {
|
||||
if ($rows.length < that.options.sortPriority.length && typeof that.options.sortPriority === 'object') {
|
||||
for (var i = 0; i < that.options.sortPriority.length; i++) {
|
||||
that.addLevel(i, that.options.sortPriority[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
that.addLevel(0);
|
||||
}
|
||||
|
||||
that.setButtonStates();
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.bootstrapTable.methods.push('multipleSort');
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showMultiSort: false,
|
||||
showMultiSortButton: true,
|
||||
sortPriority: null,
|
||||
onMultipleSort: function() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, {
|
||||
sort: 'glyphicon-sort',
|
||||
plus: 'glyphicon-plus',
|
||||
minus: 'glyphicon-minus'
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'multiple-sort.bs.table': 'onMultipleSort'
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatMultipleSort: function() {
|
||||
return 'Multiple Sort';
|
||||
},
|
||||
formatAddLevel: function() {
|
||||
return 'Add Level';
|
||||
},
|
||||
formatDeleteLevel: function() {
|
||||
return 'Delete Level';
|
||||
},
|
||||
formatColumn: function() {
|
||||
return 'Column';
|
||||
},
|
||||
formatOrder: function() {
|
||||
return 'Order';
|
||||
},
|
||||
formatSortBy: function() {
|
||||
return 'Sort by';
|
||||
},
|
||||
formatThenBy: function() {
|
||||
return 'Then by';
|
||||
},
|
||||
formatSort: function() {
|
||||
return 'Sort';
|
||||
},
|
||||
formatCancel: function() {
|
||||
return 'Cancel';
|
||||
},
|
||||
formatDuplicateAlertTitle: function() {
|
||||
return 'Duplicate(s) detected!';
|
||||
},
|
||||
formatDuplicateAlertDescription: function() {
|
||||
return 'Please remove or change any duplicate column.';
|
||||
},
|
||||
formatSortOrders: function() {
|
||||
return {
|
||||
asc: 'Ascending',
|
||||
desc: 'Descending'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales);
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initToolbar = BootstrapTable.prototype.initToolbar;
|
||||
|
||||
BootstrapTable.prototype.initToolbar = function() {
|
||||
this.showToolbar = this.showToolbar || this.options.showMultiSort;
|
||||
var that = this,
|
||||
sortModalSelector = 'sortModal_' + this.$el.attr('id'),
|
||||
sortModalId = '#' + sortModalSelector;
|
||||
this.$sortModal = $(sortModalId);
|
||||
this.sortModalSelector = sortModalSelector;
|
||||
|
||||
_initToolbar.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (that.options.sidePagination === 'server' && !isSingleSort && that.options.sortPriority !== null){
|
||||
var t = that.options.queryParams;
|
||||
that.options.queryParams = function(params) {
|
||||
params.multiSort = that.options.sortPriority;
|
||||
return t(params);
|
||||
};
|
||||
}
|
||||
|
||||
if (this.options.showMultiSort) {
|
||||
var $btnGroup = this.$toolbar.find('>.btn-group').first(),
|
||||
$multiSortBtn = this.$toolbar.find('div.multi-sort');
|
||||
|
||||
if (!$multiSortBtn.length && this.options.showMultiSortButton) {
|
||||
$multiSortBtn = ' <button class="multi-sort btn btn-default' + (this.options.iconSize === undefined ? '' : ' btn-' + this.options.iconSize) + '" type="button" data-toggle="modal" data-target="' + sortModalId + '" title="' + this.options.formatMultipleSort() + '">';
|
||||
$multiSortBtn += ' <i class="' + this.options.iconsPrefix + ' ' + this.options.icons.sort + '"></i>';
|
||||
$multiSortBtn += '</button>';
|
||||
|
||||
$btnGroup.append($multiSortBtn);
|
||||
|
||||
showSortModal(that);
|
||||
}
|
||||
|
||||
this.$el.on('sort.bs.table', function() {
|
||||
isSingleSort = true;
|
||||
});
|
||||
|
||||
this.$el.on('multiple-sort.bs.table', function() {
|
||||
isSingleSort = false;
|
||||
});
|
||||
|
||||
this.$el.on('load-success.bs.table', function() {
|
||||
if (!isSingleSort && that.options.sortPriority !== null && typeof that.options.sortPriority === 'object' && that.options.sidePagination !== 'server') {
|
||||
that.onMultipleSort();
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on('column-switch.bs.table', function(field, checked) {
|
||||
for (var i = 0; i < that.options.sortPriority.length; i++) {
|
||||
if (that.options.sortPriority[i].sortName === checked) {
|
||||
that.options.sortPriority.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
that.assignSortableArrows();
|
||||
that.$sortModal.remove();
|
||||
showSortModal(that);
|
||||
});
|
||||
|
||||
this.$el.on('reset-view.bs.table', function() {
|
||||
if (!isSingleSort && that.options.sortPriority !== null && typeof that.options.sortPriority === 'object') {
|
||||
that.assignSortableArrows();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.multipleSort = function() {
|
||||
var that = this;
|
||||
if (!isSingleSort && that.options.sortPriority !== null && typeof that.options.sortPriority === 'object' && that.options.sidePagination !== 'server') {
|
||||
that.onMultipleSort();
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.onMultipleSort = function() {
|
||||
var that = this;
|
||||
|
||||
var cmp = function(x, y) {
|
||||
return x > y ? 1 : x < y ? -1 : 0;
|
||||
};
|
||||
|
||||
var arrayCmp = function(a, b) {
|
||||
var arr1 = [],
|
||||
arr2 = [];
|
||||
|
||||
for (var i = 0; i < that.options.sortPriority.length; i++) {
|
||||
var order = that.options.sortPriority[i].sortOrder === 'desc' ? -1 : 1,
|
||||
aa = a[that.options.sortPriority[i].sortName],
|
||||
bb = b[that.options.sortPriority[i].sortName];
|
||||
|
||||
if (aa === undefined || aa === null) {
|
||||
aa = '';
|
||||
}
|
||||
if (bb === undefined || bb === null) {
|
||||
bb = '';
|
||||
}
|
||||
if ($.isNumeric(aa) && $.isNumeric(bb)) {
|
||||
aa = parseFloat(aa);
|
||||
bb = parseFloat(bb);
|
||||
}
|
||||
if (typeof aa !== 'string') {
|
||||
aa = aa.toString();
|
||||
}
|
||||
|
||||
arr1.push(
|
||||
order * cmp(aa, bb));
|
||||
arr2.push(
|
||||
order * cmp(bb, aa));
|
||||
}
|
||||
|
||||
return cmp(arr1, arr2);
|
||||
};
|
||||
|
||||
this.data.sort(function(a, b) {
|
||||
return arrayCmp(a, b);
|
||||
});
|
||||
|
||||
this.initBody();
|
||||
this.assignSortableArrows();
|
||||
this.trigger('multiple-sort');
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.addLevel = function(index, sortPriority) {
|
||||
var text = index === 0 ? this.options.formatSortBy() : this.options.formatThenBy();
|
||||
|
||||
this.$sortModal.find('tbody')
|
||||
.append($('<tr>')
|
||||
.append($('<td>').text(text))
|
||||
.append($('<td>').append($('<select class="form-control multi-sort-name">')))
|
||||
.append($('<td>').append($('<select class="form-control multi-sort-order">')))
|
||||
);
|
||||
|
||||
var $multiSortName = this.$sortModal.find('.multi-sort-name').last(),
|
||||
$multiSortOrder = this.$sortModal.find('.multi-sort-order').last();
|
||||
|
||||
$.each(this.columns, function(i, column) {
|
||||
if (column.sortable === false || column.visible === false) {
|
||||
return true;
|
||||
}
|
||||
$multiSortName.append('<option value="' + column.field + '">' + column.title + '</option>');
|
||||
});
|
||||
|
||||
$.each(this.options.formatSortOrders(), function(value, order) {
|
||||
$multiSortOrder.append('<option value="' + value + '">' + order + '</option>');
|
||||
});
|
||||
|
||||
if (sortPriority !== undefined) {
|
||||
$multiSortName.find('option[value="' + sortPriority.sortName + '"]').attr("selected", true);
|
||||
$multiSortOrder.find('option[value="' + sortPriority.sortOrder + '"]').attr("selected", true);
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.assignSortableArrows = function() {
|
||||
var that = this,
|
||||
headers = that.$header.find('th');
|
||||
|
||||
for (var i = 0; i < headers.length; i++) {
|
||||
for (var c = 0; c < that.options.sortPriority.length; c++) {
|
||||
if ($(headers[i]).data('field') === that.options.sortPriority[c].sortName) {
|
||||
$(headers[i]).find('.sortable').removeClass('desc asc').addClass(that.options.sortPriority[c].sortOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.setButtonStates = function() {
|
||||
var total = this.$sortModal.find('.multi-sort-name:first option').length,
|
||||
current = this.$sortModal.find('tbody tr').length;
|
||||
|
||||
if (current == total) {
|
||||
this.$sortModal.find('#add').attr('disabled', 'disabled');
|
||||
}
|
||||
if (current > 1) {
|
||||
this.$sortModal.find('#delete').removeAttr('disabled');
|
||||
}
|
||||
if (current < total) {
|
||||
this.$sortModal.find('#add').removeAttr('disabled');
|
||||
}
|
||||
if (current == 1) {
|
||||
this.$sortModal.find('#delete').attr('disabled', 'disabled');
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Multiple Sort",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to support the multiple sort.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-sort",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-multiple-sort",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/multiple-sort"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "dimbslmh",
|
||||
"image": "https://avatars1.githubusercontent.com/u/745635"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @author: Brian Huisman
|
||||
* @webSite: http://www.greywyvern.com
|
||||
* @version: v1.0.0
|
||||
* JS functions to allow natural sorting on bootstrap-table columns
|
||||
* add data-sorter="alphanum" or data-sorter="numericOnly" to any th
|
||||
*
|
||||
* @update Dennis Hernández <http://djhvscf.github.io/Blog>
|
||||
* @update Duane May
|
||||
*/
|
||||
|
||||
function alphanum(a, b) {
|
||||
function chunkify(t) {
|
||||
var tz = [],
|
||||
x = 0,
|
||||
y = -1,
|
||||
n = 0,
|
||||
i,
|
||||
j;
|
||||
|
||||
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
|
||||
var m = (i === 46 || (i >= 48 && i <= 57));
|
||||
if (m !== n) {
|
||||
tz[++y] = "";
|
||||
n = m;
|
||||
}
|
||||
tz[y] += j;
|
||||
}
|
||||
return tz;
|
||||
}
|
||||
|
||||
function stringfy(v) {
|
||||
if (typeof(v) === "number") {
|
||||
v = "" + v;
|
||||
}
|
||||
if (!v) {
|
||||
v = "";
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
var aa = chunkify(stringfy(a));
|
||||
var bb = chunkify(stringfy(b));
|
||||
|
||||
for (x = 0; aa[x] && bb[x]; x++) {
|
||||
if (aa[x] !== bb[x]) {
|
||||
var c = Number(aa[x]),
|
||||
d = Number(bb[x]);
|
||||
|
||||
if (c == aa[x] && d == bb[x]) {
|
||||
return c - d;
|
||||
} else {
|
||||
return (aa[x] > bb[x]) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aa.length - bb.length;
|
||||
}
|
||||
|
||||
function numericOnly(a, b) {
|
||||
function stripNonNumber(s) {
|
||||
s = s.replace(new RegExp(/[^0-9]/g), "");
|
||||
return parseInt(s, 10);
|
||||
}
|
||||
|
||||
return stripNonNumber(a) - stripNonNumber(b);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Natural Sorting",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support the natural sorting.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/natural-sorting",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-natural-sorting",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/natural-sorting"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "GreyWyvern",
|
||||
"image": "https://avatars1.githubusercontent.com/u/137631"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.jumpto input {
|
||||
height: 31px;
|
||||
width: 50px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @author Jay <jwang@dizsoft.com>
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
'use strict';
|
||||
var sprintf = $.fn.bootstrapTable.utils.sprintf;
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showJumpto: false,
|
||||
exportOptions: {}
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatJumpto: function () {
|
||||
return 'GO';
|
||||
}
|
||||
});
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales);
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initPagination = BootstrapTable.prototype.initPagination;
|
||||
|
||||
BootstrapTable.prototype.initPagination = function () {
|
||||
_initPagination.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (this.options.showJumpto) {
|
||||
var that = this,
|
||||
$pageGroup = this.$pagination.find('ul.pagination'),
|
||||
$jumpto = $pageGroup.find('li.jumpto');
|
||||
|
||||
if (!$jumpto.length) {
|
||||
$jumpto = $([
|
||||
'<li class="jumpto">',
|
||||
'<input type="text" class="form-control">',
|
||||
'<button class="btn' +
|
||||
sprintf(' btn-%s', this.options.buttonsClass) +
|
||||
sprintf(' btn-%s', this.options.iconSize) +
|
||||
'" title="' + this.options.formatJumpto() + '" ' +
|
||||
' type="button">'+this.options.formatJumpto(),
|
||||
'</button>',
|
||||
'</li>'].join('')).appendTo($pageGroup);
|
||||
|
||||
$jumpto.find('button').click(function () {
|
||||
that.selectPage(parseInt($jumpto.find('input').val()));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,21 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2019 doug-the-guy <badlydrawnsun@yahoo.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Bootstrap Table Pipelining
|
||||
|
||||
Use Plugin: [bootstrap-table-pipeline]
|
||||
|
||||
This plugin enables client side data caching for server side requests which will
|
||||
eliminate the need to issue a new request every page change. This will allow
|
||||
for a performance balance for a large data set between returning all data at once
|
||||
(client side paging) and a new server side request (server side paging).
|
||||
|
||||
There are two new options:
|
||||
- usePipeline: enables this feature
|
||||
- pipelineSize: the size of each cache window
|
||||
|
||||
The size of the pipeline must be evenly divisible by the current page size. This is
|
||||
assured by rounding up to the nearest evenly divisible value. For example, if
|
||||
the pipeline size is 4990 and the current page size is 25, then pipeline size will
|
||||
be dynamically set to 5000.
|
||||
|
||||
The cache windows are computed based on the pipeline size and the total number of rows
|
||||
returned by the server side query. For example, with pipeline size 500 and total rows
|
||||
1300, the cache windows will be:
|
||||
|
||||
[{'lower': 0, 'upper': 499}, {'lower': 500, 'upper': 999}, {'lower': 1000, 'upper': 1499}]
|
||||
|
||||
Using the limit (i.e. the pipelineSize) and offset parameters, the server side request
|
||||
**MUST** return only the data in the requested cache window **AND** the total number of rows.
|
||||
To wit, the server side code must use the offset and limit parameters to prepare the response
|
||||
data.
|
||||
|
||||
On a page change, the new offset is checked if it is within the current cache window. If so,
|
||||
the requested page data is returned from the cached data set. Otherwise, a new server side
|
||||
request will be issued for the new cache window.
|
||||
|
||||
The current cached data is only invalidated on these events:
|
||||
- sorting
|
||||
- searching
|
||||
- page size change
|
||||
- page change moves into a new cache window
|
||||
|
||||
There are two new events:
|
||||
- cached-data-hit.bs.table: issued when cached data is used on a page change
|
||||
- cached-data-reset.bs.table: issued when the cached data is invalidated and new server side request is issued
|
||||
|
||||
## Features
|
||||
|
||||
* Created with Bootstrap 4
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
# assumed import of bootstrap and bootstrap-table assets
|
||||
<script src="/path/to/bootstrap-table-pipeline.js"></script>
|
||||
...
|
||||
<table id="pipeline_table"
|
||||
class="table table-striped"
|
||||
data-method='post'
|
||||
data-use-pipeline="true"
|
||||
data-pipeline-size="5000"
|
||||
data-pagination="true"
|
||||
data-side-pagination="server"
|
||||
data-page-size="50">
|
||||
<thead><tr>
|
||||
<th data-field="type" data-sortable="true">Type</th>
|
||||
<th data-field="value" data-sortable="true">Value</th>
|
||||
<th data-field="date" data-sortable="true">Date</th>
|
||||
</tr></thead>
|
||||
</table>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### usePipeline
|
||||
|
||||
* type: Boolean
|
||||
* description: Set true to enable pipelining
|
||||
* default: `false`
|
||||
|
||||
## pipelineSize
|
||||
|
||||
* type: Integer
|
||||
* description: Size of each cache window. Must be greater than 0
|
||||
* default: `1000`
|
||||
|
||||
## Events
|
||||
|
||||
### onCachedDataHit(cached-data-hit.bs.table)
|
||||
|
||||
* Fires when paging was able to use the locally cached data.
|
||||
|
||||
### onCachedDataReset(cached-data-reset.bs.table)
|
||||
|
||||
* Fires when the locally cached data needed to be reset (i.e. on sorting, searching, page size change or paged out of current cache window)
|
||||
330
InvenTree/InvenTree/static/bootstrap-table/extensions/pipeline/bootstrap-table-pipeline.js
vendored
Normal file
330
InvenTree/InvenTree/static/bootstrap-table/extensions/pipeline/bootstrap-table-pipeline.js
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* @author doug-the-guy
|
||||
* @version v1.0.0
|
||||
*
|
||||
* Boostrap Table Pipeline
|
||||
* -----------------------
|
||||
*
|
||||
* This plugin enables client side data caching for server side requests which will
|
||||
* eliminate the need to issue a new request every page change. This will allow
|
||||
* for a performance balance for a large data set between returning all data at once
|
||||
* (client side paging) and a new server side request (server side paging).
|
||||
*
|
||||
* There are two new options:
|
||||
* - usePipeline: enables this feature
|
||||
* - pipelineSize: the size of each cache window
|
||||
*
|
||||
* The size of the pipeline must be evenly divisible by the current page size. This is
|
||||
* assured by rounding up to the nearest evenly divisible value. For example, if
|
||||
* the pipeline size is 4990 and the current page size is 25, then pipeline size will
|
||||
* be dynamically set to 5000.
|
||||
*
|
||||
* The cache windows are computed based on the pipeline size and the total number of rows
|
||||
* returned by the server side query. For example, with pipeline size 500 and total rows
|
||||
* 1300, the cache windows will be:
|
||||
*
|
||||
* [{'lower': 0, 'upper': 499}, {'lower': 500, 'upper': 999}, {'lower': 1000, 'upper': 1499}]
|
||||
*
|
||||
* Using the limit (i.e. the pipelineSize) and offset parameters, the server side request
|
||||
* **MUST** return only the data in the requested cache window **AND** the total number of rows.
|
||||
* To wit, the server side code must use the offset and limit parameters to prepare the response
|
||||
* data.
|
||||
*
|
||||
* On a page change, the new offset is checked if it is within the current cache window. If so,
|
||||
* the requested page data is returned from the cached data set. Otherwise, a new server side
|
||||
* request will be issued for the new cache window.
|
||||
*
|
||||
* The current cached data is only invalidated on these events:
|
||||
* * sorting
|
||||
* * searching
|
||||
* * page size change
|
||||
* * page change moves into a new cache window
|
||||
*
|
||||
* There are two new events:
|
||||
* - cached-data-hit.bs.table: issued when cached data is used on a page change
|
||||
* - cached-data-reset.bs.table: issued when the cached data is invalidated and a
|
||||
* new server side request is issued
|
||||
*
|
||||
**/
|
||||
|
||||
(function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var Utils = $.fn.bootstrapTable.utils;
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
usePipeline: false,
|
||||
pipelineSize: 1000,
|
||||
onCachedDataHit: function(data) {
|
||||
return false;
|
||||
},
|
||||
onCachedDataReset: function(data){
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'cached-data-hit.bs.table': 'onCachedDataHit',
|
||||
'cached-data-reset.bs.table': 'onCachedDataReset'
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init,
|
||||
_initServer = BootstrapTable.prototype.initServer,
|
||||
_onSearch = BootstrapTable.prototype.onSearch,
|
||||
_onSort = BootstrapTable.prototype.onSort,
|
||||
_onPageListChange = BootstrapTable.prototype.onPageListChange;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
// needs to be called before initServer()
|
||||
this.initPipeline();
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.initPipeline = function() {
|
||||
this.cacheRequestJSON = {};
|
||||
this.cacheWindows = [];
|
||||
this.currWindow = 0;
|
||||
this.resetCache = true;
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.onSearch = function(event) {
|
||||
/* force a cache reset on search */
|
||||
if (this.options.usePipeline) {
|
||||
this.resetCache = true;
|
||||
}
|
||||
_onSearch.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.onSort = function(event) {
|
||||
/* force a cache reset on sort */
|
||||
if (this.options.usePipeline) {
|
||||
this.resetCache = true;
|
||||
}
|
||||
_onSort.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.onPageListChange = function (event) {
|
||||
/* rebuild cache window on page size change */
|
||||
var target = $(event.currentTarget);
|
||||
var newPageSize = parseInt(target.text());
|
||||
this.options.pipelineSize = this.calculatePipelineSize(this.options.pipelineSize, newPageSize);
|
||||
this.resetCache = true;
|
||||
_onPageListChange.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.calculatePipelineSize = function(pipelineSize, pageSize) {
|
||||
/* calculate pipeline size by rounding up to the nearest value evenly divisible
|
||||
* by the pageSize */
|
||||
if(pageSize == 0) return 0;
|
||||
return Math.ceil(pipelineSize/pageSize) * pageSize;
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.setCacheWindows = function() {
|
||||
/* set cache windows based on the total number of rows returned by server side
|
||||
* request and the pipelineSize */
|
||||
this.cacheWindows = [];
|
||||
var numWindows = this.options.totalRows / this.options.pipelineSize;
|
||||
for(var i = 0; i <= numWindows; i++){
|
||||
var b = i * this.options.pipelineSize;
|
||||
this.cacheWindows[i] = {'lower': b, 'upper': b + this.options.pipelineSize - 1};
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.setCurrWindow = function(offset) {
|
||||
/* set the current cache window index, based on where the current offset falls */
|
||||
this.currWindow = 0;
|
||||
for(var i = 0; i < this.cacheWindows.length; i++){
|
||||
if(this.cacheWindows[i].lower <= offset && offset <= this.cacheWindows[i].upper){
|
||||
this.currWindow = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.drawFromCache = function(offset, limit) {
|
||||
/* draw rows from the cache using offset and limit */
|
||||
var res = $.extend(true, {}, this.cacheRequestJSON);
|
||||
var drawStart = offset - this.cacheWindows[this.currWindow].lower;
|
||||
var drawEnd = drawStart + limit;
|
||||
res.rows = res.rows.slice(drawStart, drawEnd);
|
||||
return res;
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.initServer = function(silent, query, url){
|
||||
/* determine if requested data is in cache (on paging) or if
|
||||
* a new ajax request needs to be issued (sorting, searching, paging
|
||||
* moving outside of cached data, page size change)
|
||||
* initial version of this extension will entirely override base initServer
|
||||
**/
|
||||
|
||||
var data = {};
|
||||
var index = this.header.fields.indexOf(this.options.sortName);
|
||||
|
||||
var params = {
|
||||
searchText: this.searchText,
|
||||
sortName: this.options.sortName,
|
||||
sortOrder: this.options.sortOrder
|
||||
};
|
||||
|
||||
var request = null;
|
||||
|
||||
if (this.header.sortNames[index]) {
|
||||
params.sortName = this.header.sortNames[index];
|
||||
}
|
||||
|
||||
if (this.options.pagination && this.options.sidePagination === 'server') {
|
||||
params.pageSize = this.options.pageSize === this.options.formatAllRows()
|
||||
? this.options.totalRows : this.options.pageSize
|
||||
params.pageNumber = this.options.pageNumber
|
||||
}
|
||||
|
||||
if (!(url || this.options.url) && !this.options.ajax) {
|
||||
return;
|
||||
}
|
||||
|
||||
var useAjax = true;
|
||||
if (this.options.queryParamsType === 'limit') {
|
||||
params = {
|
||||
searchText: params.searchText,
|
||||
sortName: params.sortName,
|
||||
sortOrder: params.sortOrder
|
||||
}
|
||||
if (this.options.pagination && this.options.sidePagination === 'server') {
|
||||
params.limit = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize;
|
||||
params.offset = (this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize) * (this.options.pageNumber - 1);
|
||||
if (this.options.usePipeline) {
|
||||
// if cacheWindows is empty, this is the initial request
|
||||
if(!this.cacheWindows.length){
|
||||
useAjax = true;
|
||||
params.drawOffset = params.offset;
|
||||
// cache exists: determine if the page request is entirely within the current cached window
|
||||
} else {
|
||||
var w = this.cacheWindows[this.currWindow];
|
||||
// case 1: reset cache but stay within current window (e.g. column sort)
|
||||
// case 2: move outside of the current window (e.g. search or paging)
|
||||
// since each cache window is aligned with the current page size
|
||||
// checking if params.offset is outside the current window is sufficient.
|
||||
// need to requery for preceding or succeeding cache window
|
||||
// also handle case
|
||||
if(this.resetCache || (params.offset < w.lower || params.offset > w.upper)){
|
||||
useAjax = true;
|
||||
this.setCurrWindow(params.offset);
|
||||
// store the relative offset for drawing the page data afterwards
|
||||
params.drawOffset = params.offset;
|
||||
// now set params.offset to the lower bound of the new cache window
|
||||
// the server will return that whole cache window
|
||||
params.offset = this.cacheWindows[this.currWindow].lower;
|
||||
// within current cache window
|
||||
} else {
|
||||
useAjax = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (params.limit === 0) {
|
||||
delete params.limit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// force an ajax call - this is on search, sort or page size change
|
||||
if (this.resetCache) {
|
||||
useAjax = true;
|
||||
this.resetCache = false;
|
||||
}
|
||||
|
||||
if(this.options.usePipeline && useAjax) {
|
||||
/* in this scenario limit is used on the server to get the cache window
|
||||
* and drawLimit is used to get the page data afterwards */
|
||||
params.drawLimit = params.limit;
|
||||
params.limit = this.options.pipelineSize;
|
||||
}
|
||||
|
||||
// cached results can be used
|
||||
if(!useAjax) {
|
||||
var res = this.drawFromCache(params.offset, params.limit);
|
||||
this.load(res);
|
||||
this.trigger('load-success', res);
|
||||
this.trigger('cached-data-hit', res);
|
||||
return;
|
||||
}
|
||||
// cached results can't be used
|
||||
// continue base initServer code
|
||||
if (!($.isEmptyObject(this.filterColumnsPartial))) {
|
||||
params.filter = JSON.stringify(this.filterColumnsPartial, null);
|
||||
}
|
||||
|
||||
data = Utils.calculateObjectValue(this.options, this.options.queryParams, [params], data);
|
||||
|
||||
$.extend(data, query || {});
|
||||
|
||||
// false to stop request
|
||||
if (data === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
this.$tableLoading.show();
|
||||
}
|
||||
var self = this;
|
||||
|
||||
request = $.extend({}, Utils.calculateObjectValue(null, this.options.ajaxOptions), {
|
||||
type: this.options.method,
|
||||
url: url || this.options.url,
|
||||
data: this.options.contentType === 'application/json' && this.options.method === 'post'
|
||||
? JSON.stringify(data) : data,
|
||||
cache: this.options.cache,
|
||||
contentType: this.options.contentType,
|
||||
dataType: this.options.dataType,
|
||||
success: function(res){
|
||||
res = Utils.calculateObjectValue(self.options, self.options.responseHandler, [res], res);
|
||||
// cache results if using pipelining
|
||||
if(self.options.usePipeline){
|
||||
// store entire request in cache
|
||||
self.cacheRequestJSON = $.extend(true, {}, res);
|
||||
// this gets set in load() also but needs to be set before
|
||||
// setting cacheWindows
|
||||
self.options.totalRows = res[self.options.totalField];
|
||||
// if this is a search, potentially less results will be returned
|
||||
// so cache windows need to be rebuilt. Otherwise it
|
||||
// will come out the same
|
||||
self.setCacheWindows();
|
||||
self.setCurrWindow(params.drawOffset);
|
||||
// just load data for the page
|
||||
res = self.drawFromCache(params.drawOffset, params.drawLimit);
|
||||
self.trigger('cached-data-reset', res);
|
||||
}
|
||||
self.load(res);
|
||||
self.trigger('load-success', res);
|
||||
if (!silent) self.$tableLoading.hide();
|
||||
},
|
||||
error: function(res){
|
||||
var data = [];
|
||||
if (self.options.sidePagination === 'server') {
|
||||
data = {};
|
||||
data[self.options.totalField] = 0;
|
||||
data[self.options.dataField] = [];
|
||||
}
|
||||
self.load(data);
|
||||
self.trigger('load-error', res.status, res);
|
||||
if (!silent) self.$tableLoading.hide();
|
||||
}
|
||||
});
|
||||
|
||||
if (this.options.ajax) {
|
||||
Utils.calculateObjectValue(this, this.options.ajax, [request], null);
|
||||
} else {
|
||||
if (this._xhr && this._xhr.readyState !== 4) {
|
||||
this._xhr.abort();
|
||||
}
|
||||
this._xhr = $.ajax(request);
|
||||
}
|
||||
}
|
||||
|
||||
$.fn.bootstrapTable.methods.push();
|
||||
|
||||
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Pipeline",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support a hybrid approach to server/client side paging.",
|
||||
"url": "",
|
||||
"example": "#",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-pipeline",
|
||||
"url": ""
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "doug-the-guy",
|
||||
"image": ""
|
||||
}
|
||||
}
|
||||
|
||||
149
InvenTree/InvenTree/static/bootstrap-table/extensions/print/bootstrap-table-print.js
vendored
Normal file
149
InvenTree/InvenTree/static/bootstrap-table/extensions/print/bootstrap-table-print.js
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
var sprintf = $.fn.bootstrapTable.utils.sprintf;
|
||||
|
||||
function printPageBuilderDefault(table) {
|
||||
return '<html><head>' +
|
||||
'<style type="text/css" media="print">' +
|
||||
' @page { size: auto; margin: 25px 0 25px 0; }' +
|
||||
'</style>' +
|
||||
'<style type="text/css" media="all">' +
|
||||
'table{border-collapse: collapse; font-size: 12px; }\n' +
|
||||
'table, th, td {border: 1px solid grey}\n' +
|
||||
'th, td {text-align: center; vertical-align: middle;}\n' +
|
||||
'p {font-weight: bold; margin-left:20px }\n' +
|
||||
'table { width:94%; margin-left:3%; margin-right:3%}\n' +
|
||||
'div.bs-table-print { text-align:center;}\n' +
|
||||
'</style></head><title>Print Table</title><body>' +
|
||||
'<p>Printed on: ' + new Date + ' </p>' +
|
||||
'<div class="bs-table-print">' + table + "</div></body></html>";
|
||||
}
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
showPrint: false,
|
||||
printAsFilteredAndSortedOnUI: true, //boolean, when true - print table as sorted and filtered on UI.
|
||||
//Please note that if true is set, along with explicit predefined print options for filtering and sorting (printFilter, printSortOrder, printSortColumn)- then they will be applied on data already filtered and sorted by UI controls.
|
||||
//For printing data as filtered and sorted on UI - do not set these 3 options:printFilter, printSortOrder, printSortColumn
|
||||
|
||||
printSortColumn: undefined , //String, set column field name to be sorted by
|
||||
printSortOrder: 'asc', //String: 'asc' , 'desc' - relevant only if printSortColumn is set
|
||||
printPageBuilder: function(table){return printPageBuilderDefault(table)} // function, receive html <table> element as string, returns html string for printing. by default delegates to function printPageBuilderDefault(table). used for styling and adding header or footer
|
||||
});
|
||||
$.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
|
||||
printFilter: undefined, //set value to filter by in print page
|
||||
printIgnore: false, //boolean, set true to ignore this column in the print page
|
||||
printFormatter:undefined //function(value, row, index), formats the cell value for this column in the printed table. Function behaviour is similar to the 'formatter' column option
|
||||
|
||||
});
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, {
|
||||
print: 'glyphicon-print icon-share'
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initToolbar = BootstrapTable.prototype.initToolbar;
|
||||
|
||||
BootstrapTable.prototype.initToolbar = function () {
|
||||
this.showToolbar = this.showToolbar || this.options.showPrint;
|
||||
|
||||
_initToolbar.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (this.options.showPrint) {
|
||||
var that = this,
|
||||
$btnGroup = this.$toolbar.find('>.btn-group'),
|
||||
$print = $btnGroup.find('button.bs-print');
|
||||
|
||||
if (!$print.length) {
|
||||
$print = $([
|
||||
'<button class="bs-print btn btn-default' + sprintf(' btn-%s"', this.options.iconSize) + ' name="print" title="print" type="button">',
|
||||
sprintf('<i class="%s %s"></i> ', this.options.iconsPrefix, this.options.icons.print),
|
||||
'</button>'].join('')).appendTo($btnGroup);
|
||||
|
||||
$print.click(function () {
|
||||
function formatValue(row, i, column ) {
|
||||
var value = row[column.field];
|
||||
if (typeof column.printFormatter === 'function') {
|
||||
return column.printFormatter.apply(column, [value, row, i]);
|
||||
}
|
||||
else {
|
||||
return typeof value === 'undefined' ? "-" : value;
|
||||
}
|
||||
}
|
||||
|
||||
function buildTable(data, columnsArray) {
|
||||
var html = ['<table><thead>'];
|
||||
for (var k = 0; k < columnsArray.length; k++) {
|
||||
var columns = columnsArray[k];
|
||||
html.push('<tr>');
|
||||
for (var h = 0; h < columns.length; h++) {
|
||||
if (!columns[h].printIgnore) {
|
||||
html.push(
|
||||
'<th',
|
||||
sprintf(' rowspan="%s"', columns[h].rowspan),
|
||||
sprintf(' colspan="%s"', columns[h].colspan),
|
||||
sprintf('>%s</th>', columns[h].title)
|
||||
);
|
||||
}
|
||||
}
|
||||
html.push('</tr>');
|
||||
}
|
||||
html.push('</thead><tbody>');
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
html.push('<tr>');
|
||||
for(var l = 0; l < columnsArray.length; l++) {
|
||||
var columns = columnsArray[l];
|
||||
for(var j = 0; j < columns.length; j++) {
|
||||
if (!columns[j].printIgnore && columns[j].field) {
|
||||
html.push('<td>', formatValue(data[i], i, columns[j]), '</td>');
|
||||
}
|
||||
}
|
||||
}
|
||||
html.push('</tr>');
|
||||
}
|
||||
html.push('</tbody></table>');
|
||||
return html.join('');
|
||||
}
|
||||
function sortRows(data,colName,sortOrder) {
|
||||
if(!colName){
|
||||
return data;
|
||||
}
|
||||
var reverse = sortOrder != 'asc';
|
||||
reverse = -((+reverse) || -1);
|
||||
return data.sort(function (a, b) {
|
||||
return reverse * (a[colName].localeCompare(b[colName]));
|
||||
});
|
||||
}
|
||||
function filterRow(row,filters) {
|
||||
for (var index = 0; index < filters.length; ++index) {
|
||||
if(row[filters[index].colName]!=filters[index].value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function filterRows(data,filters) {
|
||||
return data.filter(function (row) {
|
||||
return filterRow(row,filters)
|
||||
});
|
||||
}
|
||||
function getColumnFilters(columns) {
|
||||
return !columns || !columns[0] ? [] : columns[0].filter(function (col) {
|
||||
return col.printFilter;
|
||||
}).map(function (col) {
|
||||
return {colName:col.field, value:col.printFilter};
|
||||
});
|
||||
}
|
||||
var doPrint = function (data) {
|
||||
data=filterRows(data,getColumnFilters(that.options.columns));
|
||||
data=sortRows(data,that.options.printSortColumn,that.options.printSortOrder);
|
||||
var table=buildTable(data,that.options.columns);
|
||||
var newWin = window.open("");
|
||||
newWin.document.write(that.options.printPageBuilder.call(this, table));
|
||||
newWin.print();
|
||||
newWin.close();
|
||||
};
|
||||
doPrint(that.options.printAsFilteredAndSortedOnUI? that.getData() : that.options.data.slice(0));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v1.1.0
|
||||
*/
|
||||
|
||||
!function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
//From MDN site, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
|
||||
var filterFn = function () {
|
||||
if (!Array.prototype.filter) {
|
||||
Array.prototype.filter = function(fun/*, thisArg*/) {
|
||||
'use strict';
|
||||
|
||||
if (this === void 0 || this === null) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
var t = Object(this);
|
||||
var len = t.length >>> 0;
|
||||
if (typeof fun !== 'function') {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
var res = [];
|
||||
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (i in t) {
|
||||
var val = t[i];
|
||||
|
||||
// NOTE: Technically this should Object.defineProperty at
|
||||
// the next index, as push can be affected by
|
||||
// properties on Object.prototype and Array.prototype.
|
||||
// But that method's new, and collisions should be
|
||||
// rare, so use the more-compatible alternative.
|
||||
if (fun.call(thisArg, val, i, t)) {
|
||||
res.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
reorderableColumns: false,
|
||||
maxMovingRows: 10,
|
||||
onReorderColumn: function (headerFields) {
|
||||
return false;
|
||||
},
|
||||
dragaccept: null
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'reorder-column.bs.table': 'onReorderColumn'
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initHeader = BootstrapTable.prototype.initHeader,
|
||||
_toggleColumn = BootstrapTable.prototype.toggleColumn,
|
||||
_toggleView = BootstrapTable.prototype.toggleView,
|
||||
_resetView = BootstrapTable.prototype.resetView;
|
||||
|
||||
BootstrapTable.prototype.initHeader = function () {
|
||||
_initHeader.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.makeRowsReorderable();
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.toggleColumn = function () {
|
||||
_toggleColumn.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.makeRowsReorderable();
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.toggleView = function () {
|
||||
_toggleView.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.cardView) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.makeRowsReorderable();
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.resetView = function () {
|
||||
_resetView.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (!this.options.reorderableColumns) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.makeRowsReorderable();
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.makeRowsReorderable = function () {
|
||||
var that = this;
|
||||
try {
|
||||
$(this.$el).dragtable('destroy');
|
||||
} catch (e) {}
|
||||
$(this.$el).dragtable({
|
||||
maxMovingRows: that.options.maxMovingRows,
|
||||
dragaccept: that.options.dragaccept,
|
||||
clickDelay:200,
|
||||
beforeStop: function() {
|
||||
var ths = [],
|
||||
formatters = [],
|
||||
columns = [],
|
||||
columnsHidden = [],
|
||||
columnIndex = -1,
|
||||
optionsColumns = [];
|
||||
that.$header.find('th').each(function (i) {
|
||||
ths.push($(this).data('field'));
|
||||
formatters.push($(this).data('formatter'));
|
||||
});
|
||||
|
||||
//Exist columns not shown
|
||||
if (ths.length < that.columns.length) {
|
||||
columnsHidden = $.grep(that.columns, function (column) {
|
||||
return !column.visible;
|
||||
});
|
||||
for (var i = 0; i < columnsHidden.length; i++) {
|
||||
ths.push(columnsHidden[i].field);
|
||||
formatters.push(columnsHidden[i].formatter);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.length; i++ ) {
|
||||
columnIndex = that.fieldsColumnsIndex[ths[i]];
|
||||
if (columnIndex !== -1) {
|
||||
that.columns[columnIndex].fieldIndex = i;
|
||||
columns.push(that.columns[columnIndex]);
|
||||
that.columns.splice(columnIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
that.columns = that.columns.concat(columns);
|
||||
|
||||
filterFn(); //Support <IE9
|
||||
$.each(that.columns, function(i, column) {
|
||||
var found = false,
|
||||
field = column.field;
|
||||
that.options.columns[0].filter(function(item) {
|
||||
if(!found && item["field"] == field) {
|
||||
optionsColumns.push(item);
|
||||
found = true;
|
||||
return false;
|
||||
} else
|
||||
return true;
|
||||
})
|
||||
});
|
||||
|
||||
that.options.columns[0] = optionsColumns;
|
||||
|
||||
that.header.fields = ths;
|
||||
that.header.formatters = formatters;
|
||||
that.initHeader();
|
||||
that.initToolbar();
|
||||
that.initBody();
|
||||
that.resetView();
|
||||
that.trigger('reorder-column', ths);
|
||||
}
|
||||
});
|
||||
};
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Reorder Columns",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to support the reordering columns feature.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-columns",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/reorder-columns.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-reorder-columns",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-columns"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.reorder_rows_onDragClass td {
|
||||
background-color: #eee;
|
||||
-webkit-box-shadow: 11px 5px 12px 2px #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
|
||||
-webkit-box-shadow: 6px 3px 5px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
|
||||
-moz-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
|
||||
-box-shadow: 6px 4px 5px 1px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
|
||||
}
|
||||
|
||||
.reorder_rows_onDragClass td:last-child {
|
||||
-webkit-box-shadow: 8px 7px 12px 0 #333, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
|
||||
-webkit-box-shadow: 1px 8px 6px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset;
|
||||
-moz-box-shadow: 0 9px 4px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, -1px 0 0 #ccc inset;
|
||||
-box-shadow: 0 9px 4px -4px #555, 0 1px 0 #ccc inset, 0 -1px 0 #ccc inset, -1px 0 0 #ccc inset;
|
||||
}
|
||||
118
InvenTree/InvenTree/static/bootstrap-table/extensions/reorder-rows/bootstrap-table-reorder-rows.js
vendored
Normal file
118
InvenTree/InvenTree/static/bootstrap-table/extensions/reorder-rows/bootstrap-table-reorder-rows.js
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v1.0.1
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var isSearch = false;
|
||||
|
||||
var rowAttr = function (row, index) {
|
||||
return {
|
||||
id: 'customId_' + index
|
||||
};
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
reorderableRows: false,
|
||||
onDragStyle: null,
|
||||
onDropStyle: null,
|
||||
onDragClass: "reorder_rows_onDragClass",
|
||||
dragHandle: null,
|
||||
useRowAttrFunc: false,
|
||||
onReorderRowsDrag: function (table, row) {
|
||||
return false;
|
||||
},
|
||||
onReorderRowsDrop: function (table, row) {
|
||||
return false;
|
||||
},
|
||||
onReorderRow: function (newData) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'reorder-row.bs.table': 'onReorderRow'
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init,
|
||||
_initSearch = BootstrapTable.prototype.initSearch;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
|
||||
if (!this.options.reorderableRows) {
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
return;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
if (this.options.useRowAttrFunc) {
|
||||
this.options.rowAttributes = rowAttr;
|
||||
}
|
||||
|
||||
var onPostBody = this.options.onPostBody;
|
||||
this.options.onPostBody = function () {
|
||||
setTimeout(function () {
|
||||
that.makeRowsReorderable();
|
||||
onPostBody.apply();
|
||||
}, 1);
|
||||
};
|
||||
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.initSearch = function () {
|
||||
_initSearch.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (!this.options.reorderableRows) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Known issue after search if you reorder the rows the data is not display properly
|
||||
//isSearch = true;
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.makeRowsReorderable = function () {
|
||||
if (this.options.cardView) {
|
||||
return;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
this.$el.tableDnD({
|
||||
onDragStyle: that.options.onDragStyle,
|
||||
onDropStyle: that.options.onDropStyle,
|
||||
onDragClass: that.options.onDragClass,
|
||||
onDrop: that.onDrop,
|
||||
onDragStart: that.options.onReorderRowsDrag,
|
||||
dragHandle: that.options.dragHandle
|
||||
});
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.onDrop = function (table, droppedRow) {
|
||||
var tableBs = $(table),
|
||||
tableBsData = tableBs.data('bootstrap.table'),
|
||||
tableBsOptions = tableBs.data('bootstrap.table').options,
|
||||
row = null,
|
||||
newData = [];
|
||||
|
||||
for (var i = 0; i < table.tBodies[0].rows.length; i++) {
|
||||
row = $(table.tBodies[0].rows[i]);
|
||||
newData.push(tableBsOptions.data[row.data('index')]);
|
||||
row.data('index', i).attr('data-index', i);
|
||||
}
|
||||
|
||||
tableBsOptions.data = tableBsOptions.data.slice(0, tableBsData.pageFrom - 1)
|
||||
.concat(newData)
|
||||
.concat(tableBsOptions.data.slice(tableBsData.pageTo));
|
||||
|
||||
//Call the user defined function
|
||||
tableBsOptions.onReorderRowsDrop.apply(table, [table, droppedRow]);
|
||||
|
||||
//Call the event reorder-row
|
||||
tableBsData.trigger('reorder-row', newData);
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Reorder Rows",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support the reordering rows feature.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-rows",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/reorder-rows.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-reorder-rows",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/reorder-rows"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
72
InvenTree/InvenTree/static/bootstrap-table/extensions/resizable/bootstrap-table-resizable.js
vendored
Normal file
72
InvenTree/InvenTree/static/bootstrap-table/extensions/resizable/bootstrap-table-resizable.js
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v2.0.0
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
"use strict";
|
||||
|
||||
var initResizable = function(that) {
|
||||
if (that.options.resizable && !that.options.cardView && !isInit(that)) {
|
||||
that.$el.resizableColumns();
|
||||
}
|
||||
};
|
||||
|
||||
var reInitResizable = function(that) {
|
||||
destroy(that);
|
||||
initResizable(that);
|
||||
};
|
||||
|
||||
var destroy = function(that) {
|
||||
if (isInit(that)) {
|
||||
that.$el.data("resizableColumns").destroy();
|
||||
}
|
||||
};
|
||||
|
||||
var isInit = function(that) {
|
||||
return that.$el.data("resizableColumns") !== undefined;
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
resizable: false
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initBody = BootstrapTable.prototype.initBody,
|
||||
_toggleView = BootstrapTable.prototype.toggleView,
|
||||
_resetView = BootstrapTable.prototype.resetView;
|
||||
|
||||
BootstrapTable.prototype.initBody = function() {
|
||||
var that = this;
|
||||
_initBody.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
that.$el
|
||||
.off("column-switch.bs.table, page-change.bs.table")
|
||||
.on("column-switch.bs.table, page-change.bs.table", function() {
|
||||
reInitResizable(that);
|
||||
});
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.toggleView = function() {
|
||||
_toggleView.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (this.options.resizable && this.options.cardView) {
|
||||
//Destroy the plugin
|
||||
destroy(this);
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.resetView = function() {
|
||||
var that = this;
|
||||
|
||||
_resetView.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
if (this.options.resizable) {
|
||||
// because in fitHeader function, we use setTimeout(func, 100);
|
||||
setTimeout(function() {
|
||||
initResizable(that);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Resizable",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to support the resizable feature.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/resizable",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/resizable.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-resizable",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/resizable"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* @author: Jewway
|
||||
* @version: v1.1.1
|
||||
*/
|
||||
|
||||
! function ($) {
|
||||
'use strict';
|
||||
|
||||
function getCurrentHeader(that) {
|
||||
var header = that.$header;
|
||||
if (that.options.height) {
|
||||
header = that.$tableHeader;
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
function initFilterValues(that) {
|
||||
if (!$.isEmptyObject(that.filterColumnsPartial)) {
|
||||
var $header = getCurrentHeader(that);
|
||||
|
||||
$.each(that.columns, function (idx, column) {
|
||||
var value = that.filterColumnsPartial[column.field];
|
||||
|
||||
if (column.filter) {
|
||||
if (column.filter.setFilterValue) {
|
||||
var $filter = $header.find('[data-field=' + column.field + '] .filter');
|
||||
column.filter.setFilterValue($filter, column.field, value);
|
||||
} else {
|
||||
var $ele = $header.find('[data-filter-field=' + column.field + ']');
|
||||
switch (column.filter.type) {
|
||||
case 'input':
|
||||
$ele.val(value);
|
||||
case 'select':
|
||||
$ele.val(value).trigger('change');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createFilter(that, header) {
|
||||
var enableFilter = false,
|
||||
isVisible,
|
||||
html,
|
||||
timeoutId = 0;
|
||||
|
||||
$.each(that.columns, function (i, column) {
|
||||
isVisible = 'hidden';
|
||||
html = null;
|
||||
|
||||
if (!column.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!column.filter) {
|
||||
html = $('<div class="no-filter"></div>');
|
||||
} else {
|
||||
var filterClass = column.filter.class ? ' ' + column.filter.class : '';
|
||||
html = $('<div style="margin: 0px 2px 2px 2px;" class="filter' + filterClass + '">');
|
||||
|
||||
if (column.searchable) {
|
||||
enableFilter = true;
|
||||
isVisible = 'visible'
|
||||
}
|
||||
|
||||
if (column.filter.template) {
|
||||
html.append(column.filter.template(that, column, isVisible));
|
||||
} else {
|
||||
var $filter = $(that.options.filterTemplate[column.filter.type.toLowerCase()](that, column, isVisible));
|
||||
|
||||
switch (column.filter.type) {
|
||||
case 'input':
|
||||
var cpLock = true;
|
||||
$filter.off('compositionstart').on('compositionstart', function (event) {
|
||||
cpLock = false;
|
||||
});
|
||||
|
||||
$filter.off('compositionend').on('compositionend', function (event) {
|
||||
cpLock = true;
|
||||
var $input = $(this);
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(function () {
|
||||
that.onColumnSearch(event, column.field, $input.val());
|
||||
}, that.options.searchTimeOut);
|
||||
});
|
||||
|
||||
$filter.off('keyup').on('keyup', function (event) {
|
||||
if (cpLock) {
|
||||
var $input = $(this);
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(function () {
|
||||
that.onColumnSearch(event, column.field, $input.val());
|
||||
}, that.options.searchTimeOut);
|
||||
}
|
||||
});
|
||||
|
||||
$filter.off('mouseup').on('mouseup', function (event) {
|
||||
var $input = $(this),
|
||||
oldValue = $input.val();
|
||||
|
||||
if (oldValue === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
var newValue = $input.val();
|
||||
|
||||
if (newValue === "") {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(function () {
|
||||
that.onColumnSearch(event, column.field, newValue);
|
||||
}, that.options.searchTimeOut);
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
break;
|
||||
case 'select':
|
||||
$filter.on('select2:select', function (event) {
|
||||
that.onColumnSearch(event, column.field, $(this).val());
|
||||
});
|
||||
|
||||
$filter.on("select2:unselecting", function (event) {
|
||||
var $select2 = $(this);
|
||||
event.preventDefault();
|
||||
$select2.val(null).trigger('change');
|
||||
that.searchText = undefined;
|
||||
that.onColumnSearch(event, column.field, $select2.val());
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
html.append($filter);
|
||||
}
|
||||
}
|
||||
|
||||
$.each(header.children().children(), function (i, tr) {
|
||||
tr = $(tr);
|
||||
if (tr.data('field') === column.field) {
|
||||
tr.find('.fht-cell').append(html);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!enableFilter) {
|
||||
header.find('.filter').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function initSelect2(that) {
|
||||
var $header = getCurrentHeader(that);
|
||||
|
||||
$.each(that.columns, function (idx, column) {
|
||||
if (column.filter && column.filter.type === 'select') {
|
||||
var $selectEle = $header.find('select[data-filter-field="' + column.field + '"]');
|
||||
|
||||
if ($selectEle.length > 0 && !$selectEle.data().select2) {
|
||||
var select2Opts = {
|
||||
placeholder: "",
|
||||
allowClear: true,
|
||||
data: column.filter.data,
|
||||
dropdownParent: that.$el.closest(".bootstrap-table")
|
||||
};
|
||||
|
||||
$selectEle.select2(select2Opts);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
filter: false,
|
||||
filterValues: {},
|
||||
filterTemplate: {
|
||||
input: function (instance, column, isVisible) {
|
||||
return '<input type="text" class="form-control" data-filter-field="' + column.field + '" style="width: 100%; visibility:' + isVisible + '">';
|
||||
},
|
||||
select: function (instance, column, isVisible) {
|
||||
return '<select data-filter-field="' + column.field + '" style="width: 100%; visibility:' + isVisible + '"></select>';
|
||||
}
|
||||
},
|
||||
onColumnSearch: function (field, text) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
|
||||
filter: undefined
|
||||
});
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'column-search.bs.table': 'onColumnSearch'
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init,
|
||||
_initHeader = BootstrapTable.prototype.initHeader,
|
||||
_initSearch = BootstrapTable.prototype.initSearch;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
//Make sure that the filtercontrol option is set
|
||||
if (this.options.filter) {
|
||||
var that = this;
|
||||
|
||||
if (that.options.filterTemplate) {
|
||||
that.options.filterTemplate = $.extend({}, $.fn.bootstrapTable.defaults.filterTemplate, that.options.filterTemplate);
|
||||
}
|
||||
|
||||
if (!$.isEmptyObject(that.options.filterValues)) {
|
||||
that.filterColumnsPartial = that.options.filterValues;
|
||||
that.options.filterValues = {};
|
||||
}
|
||||
|
||||
this.$el.on('reset-view.bs.table', function () {
|
||||
//Create controls on $tableHeader if the height is set
|
||||
if (!that.options.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Avoid recreate the controls
|
||||
if (that.$tableHeader.find('select').length > 0 || that.$tableHeader.find('input').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
createFilter(that, that.$tableHeader);
|
||||
}).on('post-header.bs.table', function () {
|
||||
var timeoutId = 0;
|
||||
|
||||
initSelect2(that);
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(function () {
|
||||
initFilterValues(that);
|
||||
}, that.options.searchTimeOut - 1000);
|
||||
}).on('column-switch.bs.table', function (field, checked) {
|
||||
initFilterValues(that);
|
||||
});
|
||||
}
|
||||
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.initHeader = function () {
|
||||
_initHeader.apply(this, Array.prototype.slice.apply(arguments));
|
||||
if (this.options.filter) {
|
||||
createFilter(this, this.$header);
|
||||
}
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.initSearch = function () {
|
||||
var that = this,
|
||||
filterValues = that.filterColumnsPartial;
|
||||
|
||||
// Filter for client
|
||||
if (that.options.sidePagination === 'client') {
|
||||
this.data = $.grep(this.data, function (row, idx) {
|
||||
for (var field in filterValues) {
|
||||
var column = that.columns[that.fieldsColumnsIndex[field]],
|
||||
filterValue = filterValues[field].toLowerCase(),
|
||||
rowValue = row[field];
|
||||
|
||||
rowValue = $.fn.bootstrapTable.utils.calculateObjectValue(
|
||||
that.header,
|
||||
that.header.formatters[$.inArray(field, that.header.fields)], [rowValue, row, idx], rowValue);
|
||||
|
||||
if (column.filterStrictSearch) {
|
||||
if (!($.inArray(field, that.header.fields) !== -1 &&
|
||||
(typeof rowValue === 'string' || typeof rowValue === 'number') &&
|
||||
rowValue.toString().toLowerCase() === filterValue.toString().toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!($.inArray(field, that.header.fields) !== -1 &&
|
||||
(typeof rowValue === 'string' || typeof rowValue === 'number') &&
|
||||
(rowValue + '').toLowerCase().indexOf(filterValue) !== -1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
_initSearch.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.onColumnSearch = function (event, field, value) {
|
||||
if ($.isEmptyObject(this.filterColumnsPartial)) {
|
||||
this.filterColumnsPartial = {};
|
||||
}
|
||||
|
||||
if (value) {
|
||||
this.filterColumnsPartial[field] = value;
|
||||
} else {
|
||||
delete this.filterColumnsPartial[field];
|
||||
}
|
||||
|
||||
this.options.pageNumber = 1;
|
||||
this.onSearch(event);
|
||||
this.trigger('column-search', field, value);
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.setSelect2Data = function (field, data) {
|
||||
var that = this,
|
||||
$header = getCurrentHeader(that),
|
||||
$selectEle = $header.find('select[data-filter-field=\"' + field + '\"]');
|
||||
$selectEle.empty();
|
||||
$selectEle.select2({
|
||||
data: data,
|
||||
placeholder: "",
|
||||
allowClear: true,
|
||||
dropdownParent: that.$el.closest(".bootstrap-table")
|
||||
});
|
||||
|
||||
$.each(this.columns, function (idx, column) {
|
||||
if (column.field === field) {
|
||||
column.filter.data = data;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
BootstrapTable.prototype.setFilterValues = function (values) {
|
||||
this.filterColumnsPartial = values;
|
||||
};
|
||||
|
||||
$.fn.bootstrapTable.methods.push('setSelect2Data');
|
||||
$.fn.bootstrapTable.methods.push('setFilterValues');
|
||||
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Select2 Filter",
|
||||
"version": "1.1.0",
|
||||
"description": "Plugin to add select2 filter on the top of the columns in order to filter the data.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/select2-filter",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/select2-filter.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-select2-filter",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/select2-filter"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "Jewway",
|
||||
"image": "https://avatars0.githubusercontent.com/u/3501899"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @author vincent loh <vincent.ml@gmail.com>
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
.fix-sticky {
|
||||
position: fixed !important;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.fix-sticky table thead {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.fix-sticky table thead.thead-light {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.fix-sticky table thead.thead-light {
|
||||
background: #212529;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @author vincent loh <vincent.ml@gmail.com>
|
||||
* @update J Manuel Corona <jmcg92@gmail.com>
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
($ => {
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
stickyHeader: false,
|
||||
stickyHeaderOffsetY: 0
|
||||
})
|
||||
|
||||
const hiddenClass = Utils.bootstrapVersion === 4 ? 'd-none' : 'hidden'
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
initHeader (...args) {
|
||||
super.initHeader(...args)
|
||||
|
||||
if (!this.options.stickyHeader) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$el.before('<div class="sticky-header-container"></div>')
|
||||
this.$el.before('<div class="sticky_anchor_begin"></div>')
|
||||
this.$el.after('<div class="sticky_anchor_end"></div>')
|
||||
this.$header.addClass('sticky-header')
|
||||
|
||||
// clone header just once, to be used as sticky header
|
||||
// deep clone header, using source header affects tbody>td width
|
||||
this.$stickyContainer = this.$tableBody.find('.sticky-header-container')
|
||||
this.$stickyBegin = this.$tableBody.find('.sticky_anchor_begin')
|
||||
this.$stickyEnd = this.$tableBody.find('.sticky_anchor_end')
|
||||
this.$stickyHeader = this.$header.clone(true, true)
|
||||
|
||||
// render sticky on window scroll or resize
|
||||
$(window).on('resize.sticky-header-table', () => this.renderStickyHeader())
|
||||
$(window).on('scroll.sticky-header-table', () => this.renderStickyHeader())
|
||||
this.$tableBody.off('scroll').on('scroll', () => this.matchPositionX())
|
||||
}
|
||||
|
||||
renderStickyHeader () {
|
||||
const top = $(window).scrollTop()
|
||||
// top anchor scroll position, minus header height
|
||||
const start = this.$stickyBegin.offset().top - this.options.stickyHeaderOffsetY
|
||||
// bottom anchor scroll position, minus header height, minus sticky height
|
||||
const end = this.$stickyEnd.offset().top - this.options.stickyHeaderOffsetY - this.$header.height()
|
||||
// show sticky when top anchor touches header, and when bottom anchor not exceeded
|
||||
if (top > start && top <= end) {
|
||||
// ensure clone and source column widths are the same
|
||||
this.$stickyHeader.find('tr:eq(0)').find('th').each((index, el) => {
|
||||
$(el).css('min-width', this.$header.find('tr:eq(0)').find('th').eq(index).css('width'))
|
||||
})
|
||||
// match bootstrap table style
|
||||
this.$stickyContainer.removeClass(hiddenClass).addClass('fix-sticky fixed-table-container')
|
||||
// stick it in position
|
||||
this.$stickyContainer.css('top', `${this.options.stickyHeaderOffsetY}px`)
|
||||
// create scrollable container for header
|
||||
this.$stickyTable = $('<table/>')
|
||||
this.$stickyTable.addClass(this.options.classes)
|
||||
// append cloned header to dom
|
||||
this.$stickyContainer.html(this.$stickyTable.append(this.$stickyHeader))
|
||||
// match clone and source header positions when left-right scroll
|
||||
this.matchPositionX()
|
||||
} else {
|
||||
this.$stickyContainer.removeClass('fix-sticky').addClass(hiddenClass)
|
||||
}
|
||||
}
|
||||
|
||||
matchPositionX () {
|
||||
this.$stickyContainer.scrollLeft(this.$tableBody.scrollLeft())
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Sticky Header",
|
||||
"version": "1.0.0",
|
||||
"description": "An extension which provides a sticky header for table columns when scrolling on a long page and / or table. Works for tables with many columns and narrow width with horizontal scrollbars too.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/sticky-header",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/sticky-header.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-sticky-header",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/sticky-header"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "vinzloh",
|
||||
"image": "https://avatars0.githubusercontent.com/u/5501845"
|
||||
}
|
||||
}
|
||||
228
InvenTree/InvenTree/static/bootstrap-table/extensions/toolbar/bootstrap-table-toolbar.js
vendored
Normal file
228
InvenTree/InvenTree/static/bootstrap-table/extensions/toolbar/bootstrap-table-toolbar.js
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* @author: aperez <aperez@datadec.es>
|
||||
* @version: v2.0.0
|
||||
*
|
||||
* @update Dennis Hernández <http://djhvscf.github.io/Blog>
|
||||
* @update zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
($ => {
|
||||
const Utils = $.fn.bootstrapTable.utils
|
||||
|
||||
const bootstrap = {
|
||||
3: {
|
||||
icons: {
|
||||
advancedSearchIcon: 'glyphicon-chevron-down'
|
||||
},
|
||||
html: {
|
||||
modalHeader: `
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">%s</h4>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
},
|
||||
4: {
|
||||
icons: {
|
||||
advancedSearchIcon: 'fa-chevron-down'
|
||||
},
|
||||
html: {
|
||||
modalHeader: `
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">%s</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
}[Utils.bootstrapVersion]
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
advancedSearch: false,
|
||||
idForm: 'advancedSearch',
|
||||
actionForm: '',
|
||||
idTable: undefined,
|
||||
onColumnAdvancedSearch (field, text) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults.icons, {
|
||||
advancedSearchIcon: bootstrap.icons.advancedSearchIcon
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.Constructor.EVENTS, {
|
||||
'column-advanced-search.bs.table': 'onColumnAdvancedSearch'
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.locales, {
|
||||
formatAdvancedSearch () {
|
||||
return 'Advanced search'
|
||||
},
|
||||
formatAdvancedCloseButton () {
|
||||
return 'Close'
|
||||
}
|
||||
})
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)
|
||||
|
||||
$.BootstrapTable = class extends $.BootstrapTable {
|
||||
initToolbar () {
|
||||
const o = this.options
|
||||
|
||||
this.showToolbar = this.showToolbar ||
|
||||
(o.search &&
|
||||
o.advancedSearch &&
|
||||
o.idTable)
|
||||
|
||||
super.initToolbar()
|
||||
|
||||
if (!o.search || !o.advancedSearch || !o.idTable) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$toolbar.find('>.btn-group').append(`
|
||||
<button class="btn btn-default${Utils.sprintf(' btn-%s', o.buttonsClass)}${Utils.sprintf(' btn-%s', o.iconSize)}"
|
||||
type="button"
|
||||
name="advancedSearch"
|
||||
aria-label="advanced search"
|
||||
title="${o.formatAdvancedSearch()}">
|
||||
<i class="${o.iconsPrefix} ${o.icons.advancedSearchIcon}"></i>
|
||||
</button>
|
||||
`)
|
||||
|
||||
this.$toolbar.find('button[name="advancedSearch"]').off('click').on('click', () => this.showAvdSearch())
|
||||
}
|
||||
|
||||
showAvdSearch () {
|
||||
const o = this.options
|
||||
|
||||
if (!$(`#avdSearchModal_${o.idTable}`).hasClass('modal')) {
|
||||
$('body').append(`
|
||||
<div id="avdSearchModal_${o.idTable}" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xs">
|
||||
<div class="modal-content">
|
||||
${Utils.sprintf(bootstrap.html.modalHeader, o.formatAdvancedSearch())}
|
||||
<div class="modal-body modal-body-custom">
|
||||
<div class="container-fluid" id="avdSearchModalContent_${o.idTable}"
|
||||
style="padding-right: 0px; padding-left: 0px;" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="btnCloseAvd_${o.idTable}" class="btn btn-${o.buttonsClass}">
|
||||
${o.formatAdvancedCloseButton()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
|
||||
let timeoutId = 0
|
||||
|
||||
$(`#avdSearchModalContent_${o.idTable}`).append(this.createFormAvd().join(''))
|
||||
|
||||
$(`#${o.idForm}`).off('keyup blur', 'input').on('keyup blur', 'input', e => {
|
||||
if (o.sidePagination === 'server') {
|
||||
this.onColumnAdvancedSearch(e)
|
||||
} else {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => {
|
||||
this.onColumnAdvancedSearch(e)
|
||||
}, o.searchTimeOut)
|
||||
}
|
||||
})
|
||||
|
||||
$(`#btnCloseAvd_${o.idTable}`).click(() => {
|
||||
$(`#avdSearchModal_${o.idTable}`).modal('hide')
|
||||
if (o.sidePagination === 'server') {
|
||||
this.options.pageNumber = 1
|
||||
this.updatePagination()
|
||||
this.trigger('column-advanced-search', this.filterColumnsPartial)
|
||||
}
|
||||
})
|
||||
|
||||
$(`#avdSearchModal_${o.idTable}`).modal()
|
||||
} else {
|
||||
$(`#avdSearchModal_${o.idTable}`).modal()
|
||||
}
|
||||
}
|
||||
|
||||
createFormAvd () {
|
||||
const o = this.options
|
||||
const html = [`<form class="form-horizontal" id="${o.idForm}" action="${o.actionForm}">`]
|
||||
|
||||
for (const column of this.columns) {
|
||||
if (!column.checkbox && column.visible && column.searchable) {
|
||||
html.push(`
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 control-label">${column.title}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control input-md" name="${column.field}" placeholder="${column.title}" id="${column.field}">
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
html.push('</form>')
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
initSearch () {
|
||||
super.initSearch()
|
||||
|
||||
if (!this.options.advancedSearch || this.options.sidePagination === 'server') {
|
||||
return
|
||||
}
|
||||
|
||||
const fp = $.isEmptyObject(this.filterColumnsPartial) ? null : this.filterColumnsPartial
|
||||
|
||||
this.data = fp ? $.grep(this.data, (item, i) => {
|
||||
for (const [key, v] of Object.entries(fp)) {
|
||||
const fval = v.toLowerCase()
|
||||
let value = item[key]
|
||||
const index = this.header.fields.indexOf(key)
|
||||
value = Utils.calculateObjectValue(this.header,
|
||||
this.header.formatters[index], [value, item, i], value)
|
||||
|
||||
if (
|
||||
!(index !== -1 &&
|
||||
(typeof value === 'string' || typeof value === 'number') &&
|
||||
(`${value}`).toLowerCase().includes(fval))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}) : this.data
|
||||
}
|
||||
|
||||
onColumnAdvancedSearch (e) {
|
||||
const text = $.trim($(e.currentTarget).val())
|
||||
const $field = $(e.currentTarget)[0].id
|
||||
|
||||
if ($.isEmptyObject(this.filterColumnsPartial)) {
|
||||
this.filterColumnsPartial = {}
|
||||
}
|
||||
if (text) {
|
||||
this.filterColumnsPartial[$field] = text
|
||||
} else {
|
||||
delete this.filterColumnsPartial[$field]
|
||||
}
|
||||
|
||||
if (this.options.sidePagination !== 'server') {
|
||||
this.options.pageNumber = 1
|
||||
this.onSearch(e)
|
||||
this.updatePagination()
|
||||
this.trigger('column-advanced-search', $field, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
})(jQuery)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Toolbar",
|
||||
"version": "2.0.0",
|
||||
"description": "Plugin to support the advanced search.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/toolbar",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/toolbar.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-toolbar",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/toolbar"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "djhvscf",
|
||||
"image": "https://avatars1.githubusercontent.com/u/4496763"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.table:not(.table-condensed)>tbody>tr>td.treenode{padding-top:0;padding-bottom:0;border-bottom:solid #fff 1px}.table:not(.table-condensed)>tbody>tr:last-child>td.treenode{border-bottom:none}.treenode .text{float:left;display:block;padding-top:6px;padding-bottom:6px}.treenode .vertical,.treenode .vertical.last{float:left;display:block;width:1px;border-left:dashed silver 1px;height:38px;margin-left:8px}.treenode .vertical.last{height:15px}.treenode .space,.treenode .node{float:left;display:block;width:15px;height:5px;margin-top:15px}.treenode .node{border-top:dashed silver 1px}
|
||||
130
InvenTree/InvenTree/static/bootstrap-table/extensions/tree-column/bootstrap-table-tree-column.js
vendored
Normal file
130
InvenTree/InvenTree/static/bootstrap-table/extensions/tree-column/bootstrap-table-tree-column.js
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @author: KingYang
|
||||
* @webSite: https://github.com/kingyang
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
|
||||
! function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
treeShowField: null,
|
||||
idField: 'id',
|
||||
parentIdField: 'pid',
|
||||
treeVerticalcls: 'vertical',
|
||||
treeVerticalLastcls: 'vertical last',
|
||||
treeSpacecls: 'space',
|
||||
treeNodecls: 'node',
|
||||
treeCellcls: 'treenode',
|
||||
treeTextcls: 'text',
|
||||
onTreeFormatter: function (row) {
|
||||
var that = this,
|
||||
options = that.options,
|
||||
level = row._level || 0,
|
||||
plevel = row._parent && row._parent._level || 0,
|
||||
paddings = [];
|
||||
for (var i = 0; i < plevel; i++) {
|
||||
paddings.push('<i class="' + options.treeVerticalcls + '"></i>');
|
||||
paddings.push('<i class="' + options.treeSpacecls + '"></i>');
|
||||
}
|
||||
|
||||
for (var i = plevel; i < level; i++) {
|
||||
if (row._last && i === (level - 1)) {
|
||||
paddings.push('<i class="' + options.treeVerticalLastcls + '"></i>');
|
||||
} else {
|
||||
paddings.push('<i class="' + options.treeVerticalcls + '"></i>');
|
||||
}
|
||||
paddings.push('<i class="' + options.treeNodecls + '"></i>');
|
||||
}
|
||||
return paddings.join('');
|
||||
}, onGetNodes: function (row, data) {
|
||||
var that = this;
|
||||
var nodes = [];
|
||||
$.each(data, function (i, item) {
|
||||
if (row[that.options.idField] === item[that.options.parentIdField]) {
|
||||
nodes.push(item);
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
},
|
||||
onCheckLeaf: function (row, data) {
|
||||
if (row.isLeaf !== undefined) {
|
||||
return row.isLeaf;
|
||||
}
|
||||
return !row._nodes || !row._nodes.length;
|
||||
}, onCheckRoot: function (row, data) {
|
||||
var that = this;
|
||||
return !row[that.options.parentIdField];
|
||||
}
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_initRow = BootstrapTable.prototype.initRow,
|
||||
_initHeader = BootstrapTable.prototype.initHeader;
|
||||
|
||||
BootstrapTable.prototype.initHeader = function () {
|
||||
var that = this;
|
||||
_initHeader.apply(that, Array.prototype.slice.apply(arguments));
|
||||
var treeShowField = that.options.treeShowField;
|
||||
if (treeShowField) {
|
||||
$.each(this.header.fields, function (i, field) {
|
||||
if (treeShowField === field) {
|
||||
that.treeEnable = true;
|
||||
var _formatter = that.header.formatters[i];
|
||||
var _class = [that.options.treeCellcls];
|
||||
if (that.header.classes[i]) {
|
||||
_class.push(that.header.classes[i].split('"')[1] || '');
|
||||
}
|
||||
that.header.classes[i] = ' class="' + _class.join(' ') + '"';
|
||||
that.header.formatters[i] = function (value, row, index) {
|
||||
var colTree = [that.options.onTreeFormatter.apply(that, [row])];
|
||||
colTree.push('<span class="' + that.options.treeTextcls + '">');
|
||||
if (_formatter) {
|
||||
colTree.push(_formatter.apply(this, Array.prototype.slice.apply(arguments)));
|
||||
} else {
|
||||
colTree.push(value);
|
||||
}
|
||||
colTree.push('</span>');
|
||||
return colTree.join('');
|
||||
};
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var initNode = function (item, idx, data, parentDom) {
|
||||
var that = this;
|
||||
var nodes = that.options.onGetNodes.apply(that, [item, data]);
|
||||
item._nodes = nodes;
|
||||
parentDom.append(_initRow.apply(that, [item, idx, data, parentDom]));
|
||||
var len = nodes.length - 1;
|
||||
for (var i = 0; i <= len; i++) {
|
||||
var node = nodes[i];
|
||||
node._level = item._level + 1;
|
||||
node._parent = item;
|
||||
if (i === len)
|
||||
node._last = 1;
|
||||
initNode.apply(that, [node, $.inArray(node, data), data, parentDom]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
BootstrapTable.prototype.initRow = function (item, idx, data, parentDom) {
|
||||
var that = this;
|
||||
if (that.treeEnable) {
|
||||
if (that.options.onCheckRoot.apply(that, [item, data])) {
|
||||
if (item._level === undefined) {
|
||||
item._level = 0;
|
||||
}
|
||||
initNode.apply(that, [item, idx, data, parentDom]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
return _initRow.apply(that, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
} (jQuery);
|
||||
@@ -0,0 +1,43 @@
|
||||
.table:not(.table-condensed) > tbody > tr > td.treenode {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: solid #fff 1px;
|
||||
}
|
||||
|
||||
.table:not(.table-condensed) > tbody > tr:last-child > td.treenode {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.treenode {
|
||||
.text {
|
||||
float: left;
|
||||
display: block;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
.vertical,
|
||||
.vertical.last {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 1px;
|
||||
border-left: dashed silver 1px;
|
||||
height: 38px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.vertical.last {
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.space,
|
||||
.node {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 5px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.node {
|
||||
border-top: dashed silver 1px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Tree column",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support display tree data column.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/tree-column",
|
||||
"example": "http://issues.wenzhixin.net.cn/bootstrap-table/#extensions/tree-column.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-reorder-rows",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/tree-column"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "KingYang",
|
||||
"image": "https://avatars3.githubusercontent.com/u/1540211"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 346 B |
111
InvenTree/InvenTree/static/bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.js
vendored
Normal file
111
InvenTree/InvenTree/static/bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.js
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @author: YL
|
||||
* @version: v1.0.0
|
||||
*/
|
||||
!function ($) {
|
||||
'use strict';
|
||||
$.extend($.fn.bootstrapTable.defaults, {
|
||||
treeShowField: null,
|
||||
idField: 'id',
|
||||
parentIdField: 'pid',
|
||||
rootParentId: null,
|
||||
onGetNodes: function (row, data) {
|
||||
var that = this;
|
||||
var nodes = [];
|
||||
$.each(data, function (i, item) {
|
||||
if (row[that.options.idField] === item[that.options.parentIdField]) {
|
||||
nodes.push(item);
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
},
|
||||
onCheckRoot: function (row, data) {
|
||||
var that = this;
|
||||
return that.options.rootParentId === row[that.options.parentIdField] ||
|
||||
!row[that.options.parentIdField];
|
||||
}
|
||||
});
|
||||
|
||||
var BootstrapTable = $.fn.bootstrapTable.Constructor,
|
||||
_init = BootstrapTable.prototype.init,
|
||||
_initRow = BootstrapTable.prototype.initRow,
|
||||
_initHeader = BootstrapTable.prototype.initHeader,
|
||||
_rowStyle = null;
|
||||
|
||||
BootstrapTable.prototype.init = function () {
|
||||
_rowStyle = this.options.rowStyle;
|
||||
_init.apply(this, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
|
||||
// td
|
||||
BootstrapTable.prototype.initHeader = function () {
|
||||
var that = this;
|
||||
_initHeader.apply(that, Array.prototype.slice.apply(arguments));
|
||||
var treeShowField = that.options.treeShowField;
|
||||
if (treeShowField) {
|
||||
$.each(this.header.fields, function (i, field) {
|
||||
if (treeShowField === field) {
|
||||
that.treeEnable = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var initTr = function (item, idx, data, parentDom) {
|
||||
var that = this;
|
||||
var nodes = that.options.onGetNodes.apply(that, [item, data]);
|
||||
item._nodes = nodes;
|
||||
parentDom.append(_initRow.apply(that, [item, idx, data, parentDom]));
|
||||
|
||||
// init sub node
|
||||
var len = nodes.length - 1;
|
||||
for (var i = 0; i <= len; i++) {
|
||||
var node = nodes[i];
|
||||
node._level = item._level + 1;
|
||||
node._parent = item;
|
||||
if (i === len)
|
||||
node._last = 1;
|
||||
// jquery.treegrid.js
|
||||
that.options.rowStyle = function (item, idx) {
|
||||
var res = _rowStyle.apply(that, Array.prototype.slice.apply(arguments));
|
||||
var id = item[that.options.idField] ? item[that.options.idField] : 0;
|
||||
var pid = item[that.options.parentIdField] ? item[that.options.parentIdField] : 0;
|
||||
res.classes = [
|
||||
res.classes || '',
|
||||
'treegrid-' + id,
|
||||
'treegrid-parent-' + pid
|
||||
].join(' ');
|
||||
return res;
|
||||
};
|
||||
initTr.apply(that, [node, $.inArray(node, data), data, parentDom]);
|
||||
}
|
||||
};
|
||||
|
||||
// tr
|
||||
BootstrapTable.prototype.initRow = function (item, idx, data, parentDom) {
|
||||
var that = this;
|
||||
if (that.treeEnable) {
|
||||
// init root node
|
||||
if (that.options.onCheckRoot.apply(that, [item, data])) {
|
||||
if (item._level === undefined) {
|
||||
item._level = 0;
|
||||
}
|
||||
// jquery.treegrid.js
|
||||
that.options.rowStyle = function (item, idx) {
|
||||
var res = _rowStyle.apply(that, Array.prototype.slice.apply(arguments));
|
||||
var x = item[that.options.idField] ? item[that.options.idField] : 0;
|
||||
res.classes = [
|
||||
res.classes || '',
|
||||
'treegrid-' + x
|
||||
].join(' ');
|
||||
return res;
|
||||
};
|
||||
initTr.apply(that, [item, idx, data, parentDom]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return _initRow.apply(that, Array.prototype.slice.apply(arguments));
|
||||
};
|
||||
}(jQuery);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "treegrid",
|
||||
"version": "1.0.0",
|
||||
"description": "Plugin to support the jquery treegrid.",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/treegrid",
|
||||
"example": "https://github.com/wenzhixin/bootstrap-table-examples/blob/master/extensions/treegrid.html",
|
||||
|
||||
"plugins": [{
|
||||
"name": "bootstrap-table-treegrid",
|
||||
"url": "https://github.com/wenzhixin/bootstrap-table/tree/master/src/extensions/treegrid"
|
||||
}],
|
||||
|
||||
"author": {
|
||||
"name": "foreveryang321",
|
||||
"image": "https://avatars0.githubusercontent.com/u/5868190"
|
||||
}
|
||||
}
|
||||
13
InvenTree/InvenTree/static/css/bootstrap-table-filter-control.css
vendored
Normal file
13
InvenTree/InvenTree/static/css/bootstrap-table-filter-control.css
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
@charset "UTF-8";
|
||||
/**
|
||||
* @author: Dennis Hernández
|
||||
* @webSite: http://djhvscf.github.io/Blog
|
||||
* @version: v2.1.1
|
||||
*/
|
||||
.no-filter-control {
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
margin: 0 2px 2px 2px;
|
||||
}
|
||||
3179
InvenTree/InvenTree/static/css/color-themes/dark-reader.css
Normal file
3179
InvenTree/InvenTree/static/css/color-themes/dark-reader.css
Normal file
File diff suppressed because it is too large
Load Diff
42
InvenTree/InvenTree/static/css/color-themes/darker.css
Normal file
42
InvenTree/InvenTree/static/css/color-themes/darker.css
Normal file
@@ -0,0 +1,42 @@
|
||||
/* Color Theme: "Darker" by Radek Hladik */
|
||||
|
||||
.navbar-nav > li {
|
||||
border-color: rgb(179, 179, 179);
|
||||
}
|
||||
|
||||
.navbar-nav > li > a {
|
||||
color:#0b2a62 !important;
|
||||
}
|
||||
|
||||
.navbar-nav > li > a:hover {
|
||||
color:#202020 !important;
|
||||
}
|
||||
|
||||
.navbar-nav > .open > a {
|
||||
color:#202020 !important;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: rgb(189, 189, 189);
|
||||
}
|
||||
|
||||
.table-condensed > tbody > tr > td {
|
||||
border-top: 1px solid #062152 !important ;
|
||||
}
|
||||
|
||||
.table-striped > tbody > tr > td {
|
||||
border-top: 1px solid #92b3f1 ;
|
||||
}
|
||||
|
||||
.table-bordered, .table-bordered > tbody > tr > td {
|
||||
border: 1px solid rgb(182,182,182);
|
||||
}
|
||||
|
||||
.table-bordered > thead > tr > th {
|
||||
border: 1px solid rgb(182, 182, 182);
|
||||
background-color: rgb(235, 235, 235);
|
||||
}
|
||||
|
||||
h3 {
|
||||
color:#06255d;
|
||||
}
|
||||
1
InvenTree/InvenTree/static/css/color-themes/default.css
Normal file
1
InvenTree/InvenTree/static/css/color-themes/default.css
Normal file
@@ -0,0 +1 @@
|
||||
/* Color Theme: "Default" */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user