Compare commits

..

725 Commits

Author SHA1 Message Date
Oliver
8ea2c627c4 Update version.py
Bump version number for release
2020-09-30 08:06:37 +10:00
Oliver
064a142d76 Merge pull request #1006 from SchrodingersGat/install_into
UI stuff
2020-09-30 08:03:34 +10:00
Oliver Walters
4f1d087654 PEP fix 2020-09-30 07:56:44 +10:00
Oliver Walters
d1cce7df94 Add direct admin links if the user is staff 2020-09-30 00:02:10 +10:00
Oliver Walters
0e5f10c020 Add some more search terms to the admin interface 2020-09-29 23:41:50 +10:00
Oliver Walters
1f6cbd7408 Add action to uninstall a particular stock item 2020-09-29 23:11:37 +10:00
Oliver
9e4a599c44 Merge pull request #1001 from SchrodingersGat/index-fix
Change layout for the index page
2020-09-28 22:23:08 +10:00
Oliver Walters
e78e649aa9 Change layout for the index page
- Expanding one table doesn't force the neighbouring one to be expanded also
2020-09-28 22:22:48 +10:00
Oliver
41d6ad2db9 Merge pull request #1000 from SchrodingersGat/installed-in
Installed in
2020-09-28 22:16:07 +10:00
Oliver Walters
38beaff01b peppy 2020-09-28 22:09:09 +10:00
Oliver Walters
d348d90fbe Cleanup errors in unit testing 2020-09-28 22:08:38 +10:00
Oliver Walters
ae55c81dae Updated translation files 2020-09-28 22:04:08 +10:00
Oliver Walters
54bfcff213 CHange "parts" to "items" 2020-09-28 22:03:41 +10:00
Oliver Walters
b2b22762ef style fixes 2020-09-28 22:01:45 +10:00
Oliver Walters
a18886f196 Add some unit tests for the new functions 2020-09-28 22:00:17 +10:00
Oliver Walters
df8d1fb32b Add functions to install and uninstall stock items 2020-09-28 21:52:23 +10:00
Oliver Walters
81ce284264 Select the "default" where we wish to uninstall parts 2020-09-28 21:41:35 +10:00
Oliver Walters
39cfe39172 View / form / url for stock-uninstall form 2020-09-28 21:27:27 +10:00
Oliver Walters
ca6994566d Add better terminal support for invoke server command 2020-09-28 21:26:40 +10:00
Oliver Walters
2ef8464a83 Add buttons to remove installed items from a stock item 2020-09-28 20:27:13 +10:00
Oliver
4de6cc3e4f Merge pull request #999 from SchrodingersGat/build_fix
Fix unit test
2020-09-28 20:20:08 +10:00
Oliver Walters
b58f7d7461 Custom table display for installed stock items 2020-09-28 20:19:56 +10:00
Oliver Walters
d684ed076b Improve table filter naming 2020-09-28 20:10:29 +10:00
Oliver Walters
d8a74ad8b7 Style fixes 2020-09-28 20:08:08 +10:00
Oliver Walters
f253bf1843 Add ability for stock API to be filtered by installed status 2020-09-28 20:07:25 +10:00
Oliver Walters
f409bfd72b More fixes 2020-09-28 19:41:41 +10:00
Oliver Walters
8dd8e69c05 Add "installed parts" tab for stock item 2020-09-28 19:34:43 +10:00
Oliver Walters
97b35d9269 Renamed related name "owned_parts" to "installed_parts" 2020-09-28 19:33:32 +10:00
Oliver Walters
042956ad48 Fix unit test 2020-09-28 19:27:18 +10:00
Oliver
a3f59d8115 Merge pull request #998 from SchrodingersGat/test-report-ordering
Bugfix: Test result ordering was reversed!
2020-09-28 09:02:52 +10:00
Oliver Walters
b76e6a5023 Bugfix: Test result ordering was reversed! 2020-09-28 09:02:17 +10:00
Oliver
3a5014da4b Merge pull request #996 from SchrodingersGat/batch-filtering
Adds ability to filter stock API results by batch code
2020-09-23 09:20:13 +10:00
Oliver Walters
8a4597be6a Adds ability to filter stock API results by batch code 2020-09-23 09:11:46 +10:00
Oliver
fb247c3dd8 Merge pull request #994 from SchrodingersGat/docs-update
Update links to documenation
2020-09-23 08:51:07 +10:00
Oliver
7dd988a7dc Merge pull request #995 from eeintech/stock_to_build_prefetch
Reduced SQL load for 'Require Stock to Complete Build' widget on homepage
2020-09-23 08:45:51 +10:00
eeintech
120a17241e Reduced SQL load for 'Require Stock to Complete Build' widget on homepage 2020-09-22 11:24:09 -05:00
Oliver Walters
07adee3c51 Update links to documenation 2020-09-21 22:44:48 +10:00
Oliver
12e43dcc46 Update README.md
Rearrange headers in README.md
2020-09-21 22:40:02 +10:00
Oliver
bc57845aaa Update README.md
Point to new documentation on readthedocs.io
2020-09-21 22:39:04 +10:00
Oliver
8f1e116c0b Merge pull request #992 from SchrodingersGat/build-table-refresh
Build display improvements
2020-09-21 08:14:25 +10:00
Oliver Walters
5d6e20d897 Build display improvements
- Reload AJAX table instead of entire page
- Clean up auto-allocate page
- Add extra message
2020-09-21 08:05:31 +10:00
Oliver
7dbb6c7c8e Merge pull request #988 from SchrodingersGat/sell-price
Sell price
2020-09-19 23:52:25 +10:00
Oliver
d6bca4d6ca Merge pull request #987 from SchrodingersGat/serial_number_filter
Stock Search
2020-09-19 23:48:11 +10:00
Oliver Walters
f19a727a02 Allow searching by stock item batch code 2020-09-19 23:40:06 +10:00
Oliver Walters
89b3290068 Add search results for stock items 2020-09-19 23:38:33 +10:00
Oliver Walters
083bfe05c0 Add ability to search stock API
- serial number
- part information
2020-09-19 21:49:07 +10:00
Oliver
5f81d650e9 Merge pull request #986 from SchrodingersGat/serial_number_filter
Filter stock tables by serial number
2020-09-19 21:47:40 +10:00
Oliver Walters
2e1388475e Filter stock tables by serial number 2020-09-19 21:39:43 +10:00
Oliver
2389bb9621 Merge pull request #984 from SchrodingersGat/index_page_fixes
Tweaks for new index page
2020-09-19 21:33:25 +10:00
Oliver Walters
b30754f561 Animate hourglass icons 2020-09-19 21:26:34 +10:00
Oliver Walters
b9594db832 Add some hourglass loading icons 2020-09-19 21:23:36 +10:00
Oliver Walters
597ab37ba6 Further cleanup 2020-09-19 21:18:29 +10:00
Oliver Walters
1b6843e72d Cleanup "bom_invalid" filter
- Allow filtering by bom either valid or invalid
- Use "bom_valid" as the filter (positive tense)
2020-09-19 21:03:49 +10:00
Oliver Walters
87d0d872e0 Fix spelling mistakes 2020-09-19 20:45:00 +10:00
Oliver Walters
b8509f7533 Add ability to filter builds by "active" status 2020-09-19 20:42:20 +10:00
Oliver Walters
11a17fb9b1 Implement a "simplified" part table which does not enforce extra filtering 2020-09-19 20:35:30 +10:00
Oliver Walters
eaf42b8abe Instead of creating a custom filter for "latest" parts,
simply make use of the existing "ordering" query as part of DRF
2020-09-19 20:26:17 +10:00
Oliver
a218b6b351 Merge pull request #982 from eeintech/new_homepage_layout
New homepage layout
2020-09-19 20:04:23 +10:00
Oliver Walters
86660a5f17 style fixes 2020-09-19 19:52:48 +10:00
eeintech
6540fb968e Merge branch 'master' of git://github.com/inventree/InvenTree into new_homepage_layout 2020-09-18 11:52:26 -05:00
eeintech
c55fa13cdb Added part list which require more stock to be built 2020-09-18 11:40:50 -05:00
Oliver Walters
ca1281ee10 Adds ajax table for part sale price information 2020-09-18 22:11:51 +10:00
Oliver Walters
ff7570aea4 VIews / forms / etc 2020-09-18 21:49:56 +10:00
Oliver Walters
1a90106bac Add a tab for part sale prices 2020-09-18 21:20:28 +10:00
Oliver Walters
a95dd86540 Limit choices for the 'Part' reference in the new model 2020-09-18 09:16:59 +10:00
Oliver Walters
71c0406cf3 Register new model in the admin interface 2020-09-18 09:16:41 +10:00
eeintech
945c3c214d Added outstanding purchase and sales orders views to homepage 2020-09-17 17:19:19 -05:00
eeintech
3d597cc3c3 Added 'Pending' builds report to homepage, includes builds with 'Allocated' status 2020-09-17 14:36:05 -05:00
Oliver Walters
8f1b018f0a Add table for price breaks for selling a part 2020-09-17 23:22:37 +10:00
Oliver Walters
e51fee081b SupplierPart price break table now uses API rather than django template 2020-09-17 23:19:50 +10:00
Oliver Walters
805e8daa57 Convert SupplierPriceBreak model to use the abstract PriceBreak class 2020-09-17 22:47:31 +10:00
Oliver Walters
95c5c4b575 Fix issues with circular imports 2020-09-17 22:44:17 +10:00
Oliver
25e73db455 Merge pull request #981 from wolffam/build-allocated-no-location-fix
Handle situation where allocated items within a build do not have a location set
2020-09-17 19:33:47 +10:00
alex
d10ba5edc3 handle case where stock item does not have a location set; also show header 2020-09-17 00:55:54 -07:00
alex
6ed604bbb8 fix erroring html 2020-09-17 00:49:17 -07:00
eeintech
28a55f0a58 Merge branch 'master' of git://github.com/inventree/InvenTree into new_homepage_layout 2020-09-16 17:03:59 -05:00
eeintech
a9fffe6a73 Added latest parts and invalid BOMs on homepage 2020-09-16 17:02:24 -05:00
Oliver
4af6b65ac3 Merge pull request #978 from eeintech/fix_navbar_barcode_border
Fixed borders of navbar barcode button
2020-09-17 07:54:28 +10:00
eeintech
472e2d0acd Fixed borders of navbar barcode button 2020-09-16 09:44:12 -05:00
Oliver
303db293b7 Merge pull request #976 from SchrodingersGat/path-fix
Ensure correct path separator is returned
2020-09-13 21:22:15 +10:00
Oliver Walters
d674eacd78 Ensure correct path separator is returned 2020-09-13 21:10:11 +10:00
Oliver
adaaa1e81d Merge pull request #975 from SchrodingersGat/dropdown-css
Dropdown css
2020-09-13 21:05:18 +10:00
Oliver Walters
32cde5d8f1 Fixx css for multiple button groups 2020-09-13 20:56:05 +10:00
Oliver Walters
04c702e1b8 Improve action buttons for part display 2020-09-13 20:42:59 +10:00
Oliver
eaf87294ce Merge pull request #974 from SchrodingersGat/toggle-fix
Fix for weird button toggles (it was a doozy!)
2020-09-13 17:56:08 +10:00
Oliver Walters
aa35c787b2 Fix for weird button toggles (it was a doozy!)
- Turning off autocomplete seems to have done the job
- https://stackoverflow.com/questions/36546775/html-checkboxes-keep-checked-after-refresh#36547079
2020-09-13 17:47:40 +10:00
Oliver
65bc23a516 Merge pull request #973 from SchrodingersGat/multi-bom-item-delete
Multi bom item delete
2020-09-13 00:47:44 +10:00
Oliver Walters
57e395de71 BOM: Allow multiple BOM items to be selected and deleted
(in editing mode)
2020-09-13 00:40:06 +10:00
Oliver Walters
7a7db97914 add inventreeDelete ajax function 2020-09-13 00:39:25 +10:00
Oliver Walters
36ac268b96 BOM Display: Multiple fixes
- Previous multi-level BOM display changes had broken the table in editing mode
- Simplify code
- Re-implement edit / delete / validate buttons
- Re-enable checkboxes in edit mode
2020-09-13 00:13:53 +10:00
Oliver
8c9491f3ca Merge pull request #970 from eeintech/color_themes
Color theme selection added to settings
2020-09-10 09:28:40 +10:00
eeintech
28585644ea Added automatic listing of custom CSS sheets (no more hardcoded), added error message when current selection is not valid and select default theme 2020-09-09 14:55:32 -05:00
eeintech
8198fad6d5 Updated common.colortheme migration 2020-09-08 16:36:58 -05:00
eeintech
35060ce8dc Merge branch 'master' of git://github.com/inventree/InvenTree into color_themes 2020-09-08 16:33:22 -05:00
eeintech
896fa131f0 Moved color themes CSS sheets to own folder, nicer crispy form for selecting theme 2020-09-08 16:24:22 -05:00
Oliver
700d522c3c Merge pull request #962 from eeintech/part_ipn_slug
Added PartDetailFromIPN view (subclass of PartDetail) and url pattern
2020-09-08 09:40:28 +10:00
eeintech
8e2d568a42 Style correction (missing ending new line), removed commented line in ColorThemeSelectForm 2020-09-07 17:01:05 -05:00
eeintech
10dd8fad80 Renamed Theme to ColorTheme, ColorTheme is now a per-user selection 2020-09-07 16:27:23 -05:00
eeintech
2e5ec5d249 Theme change works but applies to all user 2020-09-07 15:15:51 -05:00
eeintech
0548bee8ad Added Color Theme view in settings 2020-09-07 11:29:24 -05:00
eeintech
27241e217f Style correction 2020-09-07 08:46:39 -05:00
eeintech
c5efce36b6 Added PartDetailFromIPN view tests 2020-09-07 08:40:37 -05:00
eeintech
41071f0ed4 Moved IPN url pattern to right before Part index to avoid catching others 2020-09-05 12:03:07 -05:00
eeintech
8198e919b1 Merge branch 'master' of git://github.com/inventree/InvenTree into part_ipn_slug 2020-09-05 11:42:33 -05:00
Oliver
f0713ce01d Merge pull request #966 from SchrodingersGat/stock-item-link
Add link from stock item to base part
2020-09-06 00:01:36 +10:00
Oliver Walters
87f25eacc5 Add link from stock item to base part 2020-09-06 00:01:14 +10:00
Oliver
22e30903b6 Merge pull request #965 from SchrodingersGat/api-stock-fix
Major bugfix for multiple annotations in API querysets
2020-09-05 23:42:25 +10:00
Oliver Walters
46a8107be5 Refactor stock filter 2020-09-05 23:35:48 +10:00
Oliver Walters
2cf40c0e0b Translation updates 2020-09-05 23:32:36 +10:00
Oliver Walters
f7ad38dad5 Add unit tests for new aggregation annotation approach 2020-09-05 23:28:54 +10:00
Oliver Walters
5f2e4c3790 Update serializers for Order model 2020-09-05 23:08:59 +10:00
Oliver Walters
598e15af46 Fix annotations for Company serializers 2020-09-05 23:03:38 +10:00
Oliver Walters
db214dfd73 Update queryset annotation for StockItem
- Query time reduced from 4.1 to 3.5s (for sample data)
2020-09-05 22:50:17 +10:00
Oliver Walters
42967905bc New approach for multiple annotations
- Use the django-sql-utils addon - https://github.com/martsberger/django-sql-utils
- The "distinct=True" flag was not doing what I thought it was doing...
2020-09-05 22:35:19 +10:00
Oliver
a2efac4dbe Merge pull request #964 from SchrodingersGat/new-stock-item-form
Bugfix: CreateStockItem form
2020-09-05 20:51:36 +10:00
Oliver Walters
f737ad2d38 Bugfix: CreateStockItem form
- hide the field rather than popping it
2020-09-05 20:31:18 +10:00
Oliver
2667dc68d7 Merge pull request #963 from SchrodingersGat/variant-form
Allow selection of bom_copy and parameter_copy when making a new variant
2020-09-05 20:26:14 +10:00
Oliver Walters
c4989e59b2 Allow selection of bom_copy and parameter_copy when making a new variant 2020-09-05 20:10:18 +10:00
Oliver
6ef4390e29 Merge pull request #961 from eeintech/parameters_copy
Implemented part parameters copy from template/original part
2020-09-05 20:03:18 +10:00
eeintech
2fa284d37b Added PartDetailFromIPN view (subclass of PartDetail) and url pattern 2020-09-04 17:20:17 -05:00
eeintech
440a5fafea Implemented part parameters copy from template/original part 2020-09-04 14:02:12 -05:00
Oliver
11b751323e Merge pull request #953 from SchrodingersGat/part-import-fix
Part: Alter model fields to fix data import issues
2020-09-03 00:18:26 +10:00
Oliver Walters
34a334236a Part: Alter model fields to fix data import issues
Without setting 'null=True' on fields which may be empty,
the import subsequently fails if these fields are indeed empty
2020-09-03 00:11:20 +10:00
Oliver
81b50312e2 Merge pull request #952 from SchrodingersGat/variant-table
Add ability to filter part list by 'ancestor'
2020-09-03 00:09:53 +10:00
Oliver Walters
b89588ffb7 variant of link links to variants page 2020-09-03 00:00:03 +10:00
Oliver Walters
303572bc03 More translation updates 2020-09-02 23:57:51 +10:00
Oliver Walters
e3d92e1390 Fixes for translation script 2020-09-02 23:54:35 +10:00
Oliver Walters
eaec85398f Treegrid display for part variants 2020-09-02 23:51:28 +10:00
Oliver Walters
62c26c881d Add ability to filter part list by 'ancestor' 2020-09-02 23:18:26 +10:00
Oliver
104b9d2d44 Merge pull request #949 from SchrodingersGat/multi-db-unit-test
Add CI tests for MySQL database
2020-09-02 20:26:51 +10:00
Oliver Walters
36955d6d4f pep fixes 2020-09-02 20:16:46 +10:00
Oliver Walters
92ec849fc1 Add ci settings file for postgresql database 2020-09-02 20:09:11 +10:00
Oliver Walters
bb9e8fa97b Merge remote-tracking branch 'inventree/master' into multi-db-unit-test
# Conflicts:
#	InvenTree/build/test_build.py
2020-09-02 20:02:22 +10:00
Oliver Walters
911727f7b7 Update test fixtures
- Can't assume that pk values will be assigned sensibly!
- Need to hard-code them in
2020-09-02 19:47:07 +10:00
Oliver Walters
bc2f131144 travis fixes 2020-09-01 21:24:19 +10:00
Oliver Walters
b8856476be More CI file changes 2020-09-01 21:21:32 +10:00
Oliver Walters
37a43c8657 Install required PIP packages 2020-09-01 21:13:38 +10:00
Oliver Walters
85f635c975 Darn spelling misterks 2020-09-01 21:08:58 +10:00
Oliver Walters
3af704c453 Fix - tickmark 2020-09-01 21:05:52 +10:00
Oliver Walters
55c1ea750c Add CI tests for MySQL database 2020-09-01 21:01:38 +10:00
Oliver
cae6fb6731 Merge pull request #948 from SchrodingersGat/bcc-feature-non-int-serial
Bcc feature non int serial
2020-09-01 20:31:42 +10:00
Oliver Walters
4613730a19 PEP fixes 2020-09-01 20:24:14 +10:00
Oliver Walters
74f25ee7f9 Part: Extend logic for "latest" serial number
- Return value if it is a string
- Remove 'getNextSerialNumber' function
- Improve functionality of function which returns serial number placeholder string
2020-09-01 20:16:46 +10:00
Oliver Walters
e6c0a04131 Merge branch 'feature-non-int-serial' of git://github.com/bcc/InvenTree into bcc-feature-non-int-serial 2020-09-01 19:41:48 +10:00
Oliver Walters
1eeaa17085 Merge remote-tracking branch 'inventree/master' 2020-09-01 16:41:07 +10:00
Oliver
c3c5a86ea5 Merge pull request #946 from SchrodingersGat/field-focus
Add "focus" option when displaying a modal form
2020-09-01 13:04:02 +10:00
Oliver Walters
ffbdb0317f Add "focus" option when displaying a modal form 2020-09-01 12:43:37 +10:00
Oliver
90d2265dae Merge pull request #945 from SchrodingersGat/template-attachments
Part: add function to get part attachments for *all* parents of a part
2020-08-31 22:38:02 +10:00
Oliver Walters
73892e894e Refactoring 2020-08-31 22:30:38 +10:00
Oliver Walters
cd8214ff4a Part: add function to get part attachments for *all* parents of a part 2020-08-31 22:26:46 +10:00
Oliver
baaabd00d6 Merge pull request #939 from SchrodingersGat/column-remembering
Make checkbox field non-switchable for stock table
2020-08-31 22:08:56 +10:00
Oliver Walters
672936e2d4 Update many tables 2020-08-31 21:55:01 +10:00
Oliver Walters
97ed70502c Fix function which hides table columns based on saved selections 2020-08-31 20:42:11 +10:00
Oliver
35cd3923a5 Merge pull request #944 from SchrodingersGat/stock-item-api-fix
Stock item api fix
2020-08-31 20:13:57 +10:00
Oliver Walters
59e7474f75 Stock: More unit tests
- Add some more tests (would have caught a bug that was there already, darn it)
2020-08-31 20:09:43 +10:00
Oliver Walters
8615cad711 Stock: Fix serializers.py
- Use of read_only PrimaryKeyRelatedField was breaking POST via API
2020-08-31 20:08:07 +10:00
Oliver
fa540c0096 Merge pull request #943 from SchrodingersGat/order-details
Order: Improve help text(s)
2020-08-31 19:32:03 +10:00
Oliver Walters
eabb99053c Update requirements.txt 2020-08-31 19:20:16 +10:00
Oliver Walters
5c4781b5b4 Order: Improve help text(s) 2020-08-31 19:13:24 +10:00
Oliver
406dbc36fa Merge pull request #942 from bcc/bug-invoke-pty
Fix invoke pty error when running 'invoke superuser'
2020-08-30 21:32:17 +10:00
Ben Charlton
07f7b46a8a Fix invoke pty error when running 'invoke superuser' 2020-08-28 20:38:16 +01:00
Ben Charlton
06552832cb Style corrections 2020-08-28 17:30:16 +01:00
Ben Charlton
6b1696f8df Merge branch 'master' into feature-non-int-serial 2020-08-28 17:14:02 +01:00
Oliver Walters
7829e3654c Add callback when a column is shown / hidden 2020-08-28 10:51:55 +10:00
Oliver Walters
9c2e959ab0 Add function to turn a set of visible columns into a settings string for saving to session 2020-08-28 10:50:43 +10:00
Oliver Walters
86e675dbbd Make checkbox field non-switchable for stock table 2020-08-28 10:49:49 +10:00
Oliver Walters
f6d8a3f229 Merge remote-tracking branch 'upstream/master' 2020-08-27 22:50:52 +10:00
Oliver
2c1d585f8b Merge pull request #937 from SchrodingersGat/dynamic-form-fixes
Dynamic form fixes
2020-08-27 08:47:21 +10:00
Oliver Walters
b17c3f6e0f Fix for empty option 2020-08-27 08:41:38 +10:00
Oliver Walters
27f7745231 Follow link to newly created StockItem 2020-08-27 08:37:18 +10:00
Oliver Walters
19329a9627 Merge remote-tracking branch 'inventree/master' 2020-08-27 00:21:42 +10:00
Oliver
ce866c6d30 Merge pull request #934 from SchrodingersGat/reactive-forms
Reactive forms
2020-08-27 00:12:26 +10:00
Oliver Walters
146dae6d43 Toot toot - it's the refactor tractor!
- New function for launching a CreateStockItem form
- Wraps up the previous code improvements into a single function
- Oh, the ease!
2020-08-27 00:08:02 +10:00
Oliver Walters
4be1b2928b PEP fix 2020-08-26 23:50:49 +10:00
Oliver Walters
72899530cf Add helper function to simplify reloading of field options 2020-08-26 23:43:19 +10:00
Oliver Walters
9dcc23ee4e Hide 'supplier part' options if no part is selected in the form 2020-08-26 23:40:15 +10:00
Oliver Walters
3a75682878 Add "pretty" name to the SupplierPart API
- Quick way to ensure the supplier part objects are printed the same way
- Use the pythonic back-end to render the data
2020-08-26 23:23:57 +10:00
Oliver Walters
5aa11063a0 Add function to update the 'options' for a given field in a modal 2020-08-26 23:13:32 +10:00
Oliver Walters
2037474fad Option generation function now returns array, rather than flat text 2020-08-26 23:04:50 +10:00
Oliver Walters
02d17378a6 Add function to programatically generate a list of <option> elements 2020-08-26 23:02:25 +10:00
Oliver Walters
6ab34c3d0e SupplierPart - Improve API
- The default DRF behaviour throws errors if the supplied query params do not conform to the limit_choices_to field
- This is non optimum! Don't want to have to handle these cases
- Do the filtering ourselves!
2020-08-26 22:44:22 +10:00
Oliver Walters
d44ad541eb Add "callback" functionality for modal forms when a given field is changed
- Attach callback function
- Add a function to retrieve a field by name
2020-08-26 22:35:36 +10:00
Oliver
0b4f732160 Merge pull request #933 from SchrodingersGat/report-select-fix
Bugfix: Select test report template
2020-08-26 15:03:39 +10:00
Oliver Walters
54d0c4e8a8 Bugfix: Select test report template
- Actually, two bugs!
2020-08-26 14:29:49 +10:00
Oliver
ee0df29af0 Merge pull request #921 from eeintech/show_potential_bom_items_stock
Show 'available_stock' in Part string representation
2020-08-26 09:36:55 +10:00
Oliver
60c6d6d33e Merge pull request #919 from eeintech/bom_export_parameter_stock
BoM export: added option to export part parameters (#126) and stocks (#793)
2020-08-26 08:41:44 +10:00
eeintech
5d6def75cc BoM export, Part stock: changed 'Location' header to 'Default Location' 2020-08-25 16:02:46 -05:00
eeintech
109307858a Merge branch 'master' of git://github.com/inventree/InvenTree into bom_export_parameter_stock 2020-08-25 15:59:58 -05:00
Ben Charlton
7d6055f06f Merge branch 'master' into feature-non-int-serial 2020-08-25 09:28:42 +01:00
Oliver
788d497e87 Merge pull request #931 from bcc/bug-serialize-no-location
Fix internal server error when serializing stock with no location
2020-08-25 08:38:35 +10:00
Oliver
cd016d3f7d Merge pull request #930 from bcc/feature-custom-authentication
Allow custom authentication/middleware in config.yaml
2020-08-25 08:37:30 +10:00
Ben Charlton
e0a744b01d Remove no-longer-needed cast functions 2020-08-24 20:09:06 +01:00
Ben Charlton
9c2d13b487 test non-numeric serials are handled correctly for ranges 2020-08-24 20:04:10 +01:00
Ben Charlton
0da2682c68 handle non-int serial range suggestions cleanly 2020-08-24 20:00:19 +01:00
Ben Charlton
d5a374f1fd Make serial number suggestion DB independent, handle mixed types more cleanly and test 2020-08-24 19:49:32 +01:00
Ben Charlton
9149aa1536 Fix internal server error when serializing stock with no location 2020-08-24 18:04:22 +01:00
eeintech
946d824995 Switched to ModelChoiceField 2020-08-24 11:41:14 -05:00
eeintech
6f89f7dc7f Merge branch 'master' into show_potential_bom_items_stock 2020-08-24 10:35:59 -05:00
Ben Charlton
b6f9590d55 Add default config example to config template 2020-08-24 15:24:18 +01:00
Ben Charlton
988d263ef1 Allow custom authentication / middleware in config.yaml 2020-08-24 15:05:21 +01:00
Ben Charlton
471ece136e Merge branch 'master' into feature-non-int-serial 2020-08-24 11:12:07 +01:00
Oliver Walters
78826dc20b Merge remote-tracking branch 'inventree/master' 2020-08-23 21:21:18 +10:00
Oliver
2a4b9c6c5c Merge pull request #929 from SchrodingersGat/label-enable
Label enable
2020-08-23 21:18:11 +10:00
Oliver
c3bfcad4df Merge pull request #928 from Kedarius/small-device
Collapse navbar on small screen
2020-08-23 21:12:28 +10:00
Oliver Walters
ca1985e11a PEP fixes 2020-08-23 21:10:45 +10:00
Oliver Walters
9a35293126 Remove PartFilterMixin class 2020-08-23 21:08:41 +10:00
Oliver Walters
ed5ad3c047 Rename 'part_filters' field to 'filters' 2020-08-23 21:05:54 +10:00
Oliver Walters
c849f618d5 Clean up implementation of test report matching 2020-08-23 21:03:29 +10:00
Oliver Walters
b7ae95686e Add field "enabled" to Report template 2020-08-23 20:51:41 +10:00
Radek Hladik
f9149d041e Collapse navbar on small screen 2020-08-23 10:56:29 +02:00
Oliver Walters
116d966d29 Delete "ReportTemplate" class 2020-08-23 09:10:56 +10:00
Oliver Walters
771b2117c4 Remove duplicated function 2020-08-23 09:08:24 +10:00
Oliver Walters
8f1af0f5f9 Add "enabled" field to Label class
- Only list labels which are enabled
- Allows labels to be hidden / disabled without being deleted
2020-08-23 09:07:15 +10:00
Oliver
69a3ad3c5b Merge pull request #927 from bcc/bug-serialized-move
Fix disabled form item from preventing location moves for serialized stock
2020-08-22 09:13:40 +10:00
Ben Charlton
23cc3d9b06 Handle 'next serial' more gracefully 2020-08-21 19:17:58 +01:00
Ben Charlton
c31b30bf83 Fix simple tests 2020-08-21 18:53:51 +01:00
Ben Charlton
f7ed48809c Support non-integer serial numbers 2020-08-21 17:36:49 +01:00
Ben Charlton
eeca3effee Fix disabled form item from preventing stock location moves for items with serial numbers 2020-08-21 16:20:33 +01:00
Oliver Walters
98017cb8bd Merge remote-tracking branch 'inventree/master' 2020-08-21 22:11:02 +10:00
Oliver
de86418ede Merge pull request #925 from SchrodingersGat/make-replace
Change from using "make" to "invoke"
2020-08-21 21:58:19 +10:00
Oliver Walters
017c1ece89 Update docstring 2020-08-21 21:51:22 +10:00
Oliver Walters
d8f9f95cb4 Add command to run the development server 2020-08-21 21:49:03 +10:00
Oliver Walters
ef1e598341 Install PIP requirements as a part of "invoke install" 2020-08-21 21:32:08 +10:00
Oliver Walters
513142d487 Update travis file 2020-08-21 21:27:39 +10:00
Oliver Walters
592e87941d Add more targets
- superuser
- backup
2020-08-21 21:24:02 +10:00
Oliver Walters
f5fd0fc5be Migrate "mysql" and "postgresql" targets 2020-08-21 21:17:38 +10:00
Oliver Walters
af50e29e2c Migrate "translate" command 2020-08-21 21:13:28 +10:00
Oliver Walters
ec1e646de0 Migrate "style" task 2020-08-21 21:12:05 +10:00
Oliver Walters
ab75f85555 Migrate "test" command to invoke 2020-08-21 21:10:14 +10:00
Oliver Walters
05fae4be87 Are more methods to tasks.py:
- static
- update
- install
- key
- coverage

The functionality of setup.py is now included here!
2020-08-21 21:08:04 +10:00
Oliver Walters
f4debeac47 Moving to "invoke" framework
- Add "migrate" test
2020-08-21 20:39:43 +10:00
Oliver
7a0e1e9574 Merge pull request #924 from SchrodingersGat/stocktake-export-fixes
Stocktake export fixes
2020-08-21 20:14:02 +10:00
Oliver Walters
59b3d8b5bc Fix stocktake export
- Exclude MPTT fields
- Fix foreignkey field references
2020-08-21 20:08:47 +10:00
Oliver Walters
25cced3b90 Exclude MPTT fields from Part model resource 2020-08-21 20:08:16 +10:00
eeintech
9fa13aeae3 Show 'available_stock' in Part string representation 2020-08-20 15:38:41 -05:00
eeintech
89e63df1fb Corrected style 2020-08-20 14:53:03 -05:00
eeintech
6fc275ca30 BoM export: added option to export part paremeters (#126) and stocks (#793) 2020-08-20 13:53:27 -05:00
Oliver Walters
142a1462bb Merge remote-tracking branch 'inventree/master' 2020-08-20 16:02:18 +10:00
Oliver
852a680c09 Merge pull request #916 from SchrodingersGat/null-customer
StockItem: Allow customer field to be blank
2020-08-20 14:59:06 +10:00
Oliver Walters
90d34cdfcf StockItem: Allow customer field to be blank 2020-08-20 14:54:55 +10:00
Oliver Walters
2f88cc9fe0 Merge remote-tracking branch 'inventree/master' 2020-08-20 14:48:16 +10:00
Oliver
4f1b469cee Merge pull request #915 from SchrodingersGat/tex-response
Tex response
2020-08-20 14:14:24 +10:00
Oliver Walters
8e0dfa9c6f PEP fix 2020-08-20 14:05:55 +10:00
Oliver Walters
64ae181bf6 Add some more context data 2020-08-20 14:01:41 +10:00
Oliver Walters
5a8804f4bc If LaTeX template errors, return the raw LaTeX! 2020-08-20 13:57:29 +10:00
Oliver Walters
c1956d13b5 Merge remote-tracking branch 'inventree/master' 2020-08-20 09:04:17 +10:00
Oliver
71f3662ebc Merge pull request #914 from SchrodingersGat/recursive-bom
Bugfix: Recursive BOM display
2020-08-20 09:01:26 +10:00
Oliver Walters
0aeb7ddbdf Allow column show/hide on BOM table 2020-08-20 08:57:11 +10:00
Oliver Walters
41c4a5376b Bugfix: Recursive BOM display
- Actually request recursively! (duh)
- Fix the idField and parentIdField for the BOM display (was incredibly wrong)
- Sub-rows are initially displayed in the "collapsed" state
2020-08-20 08:52:22 +10:00
Oliver Walters
6930456e2b Merge remote-tracking branch 'inventree/master' 2020-08-19 14:19:43 +10:00
Oliver
8556bdec6a Merge pull request #913 from SchrodingersGat/supplier-data-in-bom
Supplier data in bom
2020-08-19 14:13:53 +10:00
Oliver Walters
0faba39dfc PEP: Remove unused variable
(cherry picked from commit e49a1c1ad2)
2020-08-19 14:08:52 +10:00
Oliver Walters
7e829e88af Add option for including supplier data for BOM export
(cherry picked from commit d0b5550c86)
2020-08-19 14:08:47 +10:00
Oliver Walters
3d0012a3bf Bugfix: accomodate missing manfufacturer links
If a supplier_part does not have an associated manufacturer, the code would break

(cherry picked from commit 965dba4739)
2020-08-19 14:08:41 +10:00
eeintech
b5bc8935a2 Added manufacturer data to BoM export
(cherry picked from commit c7ddccc4ec)
2020-08-19 14:08:34 +10:00
Oliver Walters
f8bb357e6c Merge remote-tracking branch 'inventree/master' 2020-08-18 21:30:08 +10:00
Oliver Walters
50460ed527 Merge branch 'master' of https://github.com/SchrodingersGat/InvenTree 2020-08-18 21:29:48 +10:00
Oliver
13b64c6a04 Merge pull request #910 from SchrodingersGat/multi-level-bom
Multi level bom
2020-08-18 21:28:47 +10:00
Oliver Walters
1767ef7a3a Fix issues with tree sorting
- Need to set parent ID based on the BOM items
- This is to prevent data duplication that was weird and dumb
2020-08-18 21:24:11 +10:00
Oliver Walters
82903d2bd6 BOM display is now heirarchical 2020-08-18 21:01:22 +10:00
Oliver Walters
4cd54203a2 Add jquery-treegrid plugin 2020-08-18 20:53:50 +10:00
Oliver Walters
37baec2db6 bootstrap-table: Add "extensions" 2020-08-18 20:53:05 +10:00
Oliver Walters
32133fecc7 Merge remote-tracking branch 'upstream/master' 2020-08-18 14:41:03 +10:00
Oliver
316dfe5e38 Merge pull request #909 from SchrodingersGat/bom-import-export
BOM upload: improve generation of "import" template
2020-08-18 14:37:48 +10:00
Oliver Walters
92ac93aac5 More intelligent checking for circular BOM
- Check all the way down a BOM "tree"
- Validate BOM tree before allowing BOM submission
2020-08-18 14:17:59 +10:00
Oliver Walters
ca4d3df287 Update translation files 2020-08-18 14:01:40 +10:00
Oliver Walters
68fb599c73 Major improvements to BOM upload
- Column headings now match the values provided in BOM import template
- Add a new column for part selection, while displaying all imported data
- Better code documentation
- Improve data validation
- Allow decimal quantity (not just integer!)
- Better matching logic
2020-08-18 14:01:01 +10:00
Oliver Walters
7349b396ca Merge remote-tracking branch 'upstream/master' into bom-import-export 2020-08-18 11:50:40 +10:00
Oliver
561516e8d7 Merge pull request #908 from eeintech/bom_upload_part_match_auto_and_ipn
BoM Upload: Automatic Part match using name or IPN
2020-08-18 11:50:10 +10:00
Oliver Walters
476cc5f661 BOM upload: improve generation of "import" template
- Better field naming
- Remove some fields which are not required here
- Better description of what is going to happen
2020-08-18 11:47:27 +10:00
Oliver Walters
badc306f33 Merge remote-tracking branch 'upstream/master' 2020-08-18 10:39:24 +10:00
eeintech
783a94acce Corrected MakeBomTemplate method's queryset 2020-08-17 16:42:28 -05:00
eeintech
84c0ec7937 BoM: added part matching based on IPN 2020-08-17 16:35:38 -05:00
eeintech
303157c586 Added part match auto-selection (if partial_ratio >= 100) to BoM part selection form 2020-08-17 14:10:24 -05:00
eeintech
685a58b807 Merge remote-tracking branch 'origin/master' into bom_dev 2020-08-17 12:05:54 -05:00
Oliver
89c7f72caf Merge pull request #907 from SchrodingersGat/bom-level-int
Make BOM levels integers
2020-08-17 10:43:31 +10:00
Oliver Walters
25d90f643a Make BOM levels integers 2020-08-17 10:39:35 +10:00
Oliver Walters
e67327953d Merge remote-tracking branch 'inventree/master' 2020-08-16 15:10:23 +10:00
Oliver
4dcd6ee136 Update version.py 2020-08-16 14:03:49 +10:00
Oliver
ab62f8fc59 Update version.py
Increment version number
2020-08-16 14:03:20 +10:00
Oliver
b5b882d3b6 Merge pull request #906 from SchrodingersGat/blabel
Label Printing Functionality
2020-08-16 14:02:38 +10:00
Oliver Walters
d52278e423 Icon tweak 2020-08-16 13:45:58 +10:00
Oliver Walters
da079b23dc Fix duplicate label printing menu action 2020-08-16 13:43:59 +10:00
Oliver Walters
ccda637e3c Fixes for barcode decoding 2020-08-16 13:42:27 +10:00
Oliver Walters
a4267f76e8 Fixes for barcode data rendering 2020-08-16 13:39:05 +10:00
Oliver Walters
2bbc65cc59 Add "brief" version of QR codes
- Use this to render to labels (as it contains much less information)
2020-08-16 13:29:38 +10:00
Oliver Walters
2f5e3efada Update translations 2020-08-16 12:11:22 +10:00
Oliver Walters
1807ba4e7b Add form for selecting label template for a StockItem 2020-08-16 12:10:58 +10:00
Oliver Walters
30e24f19d9 PEP fixes 2020-08-16 11:13:17 +10:00
Oliver Walters
cdde0f8c43 Add URL endpoint for downloading PDF labels 2020-08-16 11:12:21 +10:00
Oliver Walters
2b1d6c268c Render to an in-memory PDF 2020-08-16 10:29:03 +10:00
Oliver Walters
18b3fd3256 Add context data to StockItemLabel model 2020-08-16 10:24:15 +10:00
Oliver Walters
aefd70ce49 Add admin interface for StockItemLabel 2020-08-16 09:29:45 +10:00
Oliver
eb2422e6be Merge pull request #905 from SchrodingersGat/bom-depth
Allow user to set "depth" of exported multi-level BOM
2020-08-16 09:29:09 +10:00
Oliver Walters
a45902bd4f Add "label" app 2020-08-16 09:28:12 +10:00
Oliver Walters
dd77cc00b7 Add requirement for "blabel" plugin 2020-08-16 09:07:27 +10:00
Oliver Walters
e5406cae24 More PEP fixes 2020-08-16 09:05:16 +10:00
Oliver Walters
97103207b6 PEP fixes 2020-08-16 08:34:54 +10:00
Oliver Walters
b27ecf54d7 Allow user to set "depth" of exported multi-level BOM 2020-08-16 08:29:36 +10:00
Oliver
318c16f321 Merge pull request #904 from SchrodingersGat/query-speeds
Significant query speed improvements to stock list API
2020-08-15 21:56:55 +10:00
Oliver Walters
f1373e3bea Improve speed of BomItem API queries
- Including the pricing information takes absolutely ages, takes too long!!
2020-08-15 21:52:32 +10:00
Oliver Walters
e1a8772af6 Update serializers for SupplierPart 2020-08-15 21:24:01 +10:00
Oliver Walters
d34383e842 Specify serializer type for part category 2020-08-15 21:20:31 +10:00
Oliver Walters
2f77007dbe Significant query speed improvements to stock list API
- Thanks, django-debug-toolbar!
- Gah, django DRF is the worst. Enforcing a PrimaryKeyRelatedSerializer seems to really improve speed
2020-08-15 21:15:11 +10:00
Oliver
411e7507a3 Merge pull request #903 from SchrodingersGat/debug-toolbar
Debug toolbar
2020-08-15 20:08:04 +10:00
Oliver Walters
3c4cfc9a1b Flake fix 2020-08-15 19:56:02 +10:00
Oliver Walters
2bb669d7de Remove defunct "log_queries" option 2020-08-15 19:52:04 +10:00
Oliver Walters
b8b3a933ab Add django-debug-toolbar plugin
- Must be running in DEBUG mode
- Must set debug_toolbar to True in config file
2020-08-15 19:48:42 +10:00
Oliver Walters
2280558303 Add a method of return raw LaTeX for debugging 2020-08-15 19:27:31 +10:00
Oliver Walters
3b01614a38 Merge remote-tracking branch 'inventree/master' 2020-08-09 21:37:06 +10:00
Oliver
1f5d81ef93 Merge pull request #900 from SchrodingersGat/stock-display-improvements
Stock display improvements
2020-08-09 21:26:26 +10:00
Oliver Walters
cebba6909c Revert thing 2020-08-09 21:21:12 +10:00
Oliver Walters
3336eb9f45 Default stock list behaviour is to only show in_stock items 2020-08-09 21:12:49 +10:00
Oliver Walters
0da9e0f834 Add "sent_to_customer" filter 2020-08-09 21:10:21 +10:00
Oliver Walters
502702b3bc Add some more visual tags to the stock list 2020-08-09 21:04:41 +10:00
Oliver
bb6b3b965d Merge pull request #896 from SchrodingersGat/company-ordering
Add default ordering for Company model
2020-08-08 17:21:21 +10:00
Oliver
fcfd1f82d6 Merge pull request #898 from SchrodingersGat/filter-depleted
Filter depleted
2020-08-08 17:19:28 +10:00
Oliver Walters
19c036f50a Add migration files 2020-08-08 17:16:04 +10:00
Oliver Walters
ad11681369 Custom filter tag for company stock listing 2020-08-08 17:15:08 +10:00
Oliver Walters
bc3fda71a4 Display "depleted" label next to depleted stock 2020-08-08 17:11:50 +10:00
Oliver Walters
96a3f2920d Stock table filters now arranged in alphabetical order 2020-08-08 17:09:22 +10:00
Oliver Walters
099f56e779 Add "depleted" table filter 2020-08-08 17:08:18 +10:00
Oliver Walters
402301e165 Add ability to filter stock items by "depleted" status 2020-08-08 17:06:22 +10:00
Oliver
190610c6c9 Merge pull request #897 from SchrodingersGat/serialized-stock-adjust-fix
Cannot edit quantity for serialized stock when adjusting
2020-08-08 17:05:29 +10:00
Oliver Walters
732405f738 Cannot edit quantity for serialized stock when adjusting 2020-08-08 17:01:29 +10:00
Oliver Walters
a7ee323035 PEP fix 2020-08-08 16:59:48 +10:00
Oliver Walters
ad977d4d26 Add default ordering for Company model
- Now will sort by "name" in choice fields, etc
2020-08-08 16:54:09 +10:00
Oliver Walters
3831a04ce0 Merge remote-tracking branch 'inventree/master' 2020-08-08 09:52:48 +10:00
Oliver
eac53c836c Update README.md 2020-08-08 09:52:14 +10:00
Oliver
2cd6c0b9fc Merge pull request #895 from SchrodingersGat/return-to-stock
Return to stock
2020-08-08 09:48:44 +10:00
Oliver Walters
8d9cfd3678 Migration file for StockStatus codes 2020-08-08 09:44:21 +10:00
Oliver Walters
a2c3c1086c Catch unhandled javascript errors 2020-08-08 09:31:57 +10:00
Oliver Walters
ff71fe6e93 Fix "in_stock" logic for StockItem
- Not in stock if it is at a customer!
2020-08-08 09:20:42 +10:00
Oliver Walters
861a2982db Removed "SHIPPED" stock status code 2020-08-08 09:16:53 +10:00
Oliver Walters
54c1f2d9a2 Upgrade to latest flake8 2020-08-08 09:15:45 +10:00
Oliver Walters
459cc03aae Remove "ASSIGNED_TO_BUILD" status code 2020-08-08 09:08:15 +10:00
Oliver Walters
80173a9d43 Remove ASSIGNED_TO_OTHER_ITEM status code 2020-08-08 09:07:25 +10:00
Oliver Walters
bdea29df04 Add functionality to return stock item from customer 2020-08-08 09:05:33 +10:00
Oliver Walters
7b332d93ee Rearrange button layouts 2020-08-08 08:48:27 +10:00
Oliver
518ad3eb60 Merge pull request #892 from SchrodingersGat/import-fix
Allow default_keywords to be  null
2020-08-04 11:35:25 +10:00
Oliver Walters
0f199556df Add migration file 2020-08-04 11:21:27 +10:00
Oliver Walters
3a266cf322 Allow default_keywords to be null 2020-08-04 11:10:24 +10:00
Oliver Walters
d65d76653c Merge remote-tracking branch 'inventree/master' 2020-08-03 20:49:33 +10:00
Oliver
08bab0a1b9 Merge pull request #891 from SchrodingersGat/security-fix
Update requirements.txt
2020-08-03 19:53:34 +10:00
Oliver Walters
481eeb8338 Update requirements.txt 2020-08-03 19:36:41 +10:00
Oliver
e0e82dabf3 Merge pull request #889 from SchrodingersGat/allow-empty-stock
Allow creation of a stock item with zero quantity
2020-07-30 21:09:57 +10:00
Oliver Walters
10691b3d03 Allow creation of a stock item with zero quantity 2020-07-30 21:05:37 +10:00
Oliver
8a1f358505 Merge pull request #888 from SchrodingersGat/allow-staff-delete
Allow staff delete
2020-07-30 20:29:57 +10:00
Oliver Walters
b7f30ee136 Update translations 2020-07-30 20:26:14 +10:00
Oliver Walters
d90edab59f Staff users are allowed to delete stock items that otherwise are not able to be deleted 2020-07-30 20:23:46 +10:00
Oliver Walters
d1d5b45073 Merge remote-tracking branch 'inventree/master' 2020-07-22 14:52:57 +10:00
Oliver
daab81fa2c Merge pull request #887 from eeintech/allow_api_thumbnail_update
Allowing Part thumbnail (image) update via API
2020-07-22 09:19:43 +10:00
eeintech
f386039d5a Added validation method for uploaded file using imghdr library 2020-07-21 16:01:17 -05:00
eeintech
af70dd5dfe Allowing Part thumbnail (image) update via API 2020-07-20 16:16:26 -05:00
eeintech
51c194fb8f Merge branch 'master' of github.com:eeintech/InvenTree into bom_dev 2020-07-19 15:05:07 -05:00
Oliver Walters
6994af411d Merge remote-tracking branch 'inventree/master' 2020-07-18 13:31:07 +10:00
Oliver
5209a7a1b0 Merge pull request #884 from SchrodingersGat/fix-filters
Adds table filters for stock assigned to a particular customer
2020-07-14 16:05:44 +10:00
Oliver Walters
0152ae79ef Adds table filters for stock assigned to a particular customer 2020-07-14 15:54:03 +10:00
eeintech
3b98d85143 Merge branch 'master' of github:eeintech/InvenTree into bom_dev 2020-07-09 08:48:41 -05:00
eeintech
46586ba960 Corrected typo in part.models, simplified MakeBomTemplate function 2020-07-09 08:47:52 -05:00
Oliver
cfd1194a6e Merge pull request #883 from eeintech/api_delete_inactive_part
API: Allow part deletion if inactive
2020-07-09 19:42:51 +10:00
eeintech
9b7977a217 API: Allow part deletion if inactive 2020-07-08 10:56:23 -05:00
Oliver Walters
5f2ca784c9 Merge branch 'negative-stock-fix' 2020-06-28 19:26:56 +10:00
Oliver
3d7676e292 Merge pull request #880 from SchrodingersGat/negative-stock-fix
Improve logic of "quantity_to_order" function
2020-06-28 19:23:37 +10:00
Oliver
25b7f0b7ee Merge pull request #879 from SchrodingersGat/star-filter-fix
Fix filter logic for 'starred' parts
2020-06-28 19:14:55 +10:00
Oliver Walters
3d245c7ce3 Improve logic of "quantity_to_order" function 2020-06-28 19:14:51 +10:00
Oliver Walters
134a688e4a Fix filter logic for 'starred' parts 2020-06-28 19:08:13 +10:00
Oliver
8ac23c080f Merge pull request #875 from SchrodingersGat/tweaks
"Active" part toggle is always enabled
2020-06-22 11:33:39 +10:00
Oliver Walters
fa3d2d005a "Active" part toggle is always enabled 2020-06-22 11:27:59 +10:00
Oliver
da70af7ba0 Merge pull request #874 from SchrodingersGat/show_non_stock
Add ability to display "out of stock" items against a part
2020-06-22 10:55:12 +10:00
Oliver Walters
930f903f5d Add ability to display "out of stock" items against a part 2020-06-22 10:48:41 +10:00
Oliver
16bea33695 Merge pull request #867 from SchrodingersGat/name-conventions
Update verbose names for some fields
2020-06-13 20:52:15 +10:00
Oliver Walters
dedc25d681 Update verbose names for some fields 2020-06-13 20:46:19 +10:00
Oliver Walters
82433db1ea Merge remote-tracking branch 'inventree/master' 2020-06-12 18:16:23 +10:00
Oliver
2047b2d7d7 Merge pull request #866 from SchrodingersGat/moar-buttons
Reorganize buttons for "Part" view
2020-06-12 18:16:06 +10:00
Oliver Walters
2e91f64a96 Add (disabled) print-label button for Part display 2020-06-12 18:13:14 +10:00
Oliver Walters
1ad4b8b131 Reorganize buttons for "Part" view 2020-06-12 18:11:57 +10:00
Oliver
a63219466f Merge pull request #865 from SchrodingersGat/barcode-suppor
Barcode support
2020-06-12 17:57:36 +10:00
Oliver Walters
9757fb06db Fix a weird button issue
- reloadTable() was causing the button 'click' handler to fire...
- NFI why this is happening
2020-06-12 16:47:57 +10:00
Oliver Walters
ab4e6548dc Transfer stock items into a stock location using barcode scanning 2020-06-12 16:29:08 +10:00
Oliver Walters
351a55c9ea Add the concept of "action groups" to the buttons for the Stock Location view 2020-06-12 11:14:33 +10:00
Oliver Walters
e1c503836d Improve barcode icon in navbar 2020-06-12 10:56:52 +10:00
Oliver Walters
8cafaa0e62 Make barcode javascript translatable 2020-06-12 10:43:27 +10:00
Oliver Walters
6064c6ceb5 Add dialog for linking a barcode with a stock item 2020-06-12 10:26:23 +10:00
Oliver Walters
b559816dca Update translations 2020-06-12 10:25:07 +10:00
Oliver Walters
ba94b340dd Add function to un-link barcode from a stock item 2020-06-12 10:12:31 +10:00
Oliver Walters
beccbe8583 Flake fixes 2020-06-11 19:46:36 +10:00
Oliver Walters
07be08d0f3 "Simplify" qr code display 2020-06-11 19:40:44 +10:00
Oliver Walters
4bb9c4c559 Prevent barcode data being entered multiple times 2020-06-11 19:27:32 +10:00
Oliver Walters
2d9b75c6ae Include URL data in barcode scan endpoint 2020-06-11 19:21:38 +10:00
Oliver Walters
3aecb110a5 Add qrcode icon as prefix to barcode input 2020-06-11 19:05:55 +10:00
Oliver Walters
503d5a41b1 Add global 'barcode-scan' button 2020-06-11 18:09:43 +10:00
Oliver Walters
e943681baa Add translations for error messages 2020-06-11 18:09:06 +10:00
Oliver Walters
0559946791 Add unit testing framework for barcodes 2020-06-11 14:44:56 +10:00
Oliver Walters
22a8e82108 Add endpoint for assigning a particular barcode to a StockItem 2020-06-11 12:21:33 +10:00
Oliver Walters
a72ed11cb0 Skeleton for DigiKey barcode 2020-06-11 11:16:55 +10:00
Oliver Walters
1a15b46d65 Remove some debug statements 2020-06-11 11:14:02 +10:00
Oliver Walters
0068cd9825 Refactor barcode endoint
- Moved code into 'barcode' directory
2020-06-11 11:09:07 +10:00
Oliver Walters
290c0eb225 Add barcode javascript file 2020-06-11 09:56:59 +10:00
Oliver Walters
bc31cc550e Consolidate buttons for stock item 2020-06-10 15:27:30 +10:00
Oliver Walters
f40a9d1dad Merge remote-tracking branch 'inventree/master' 2020-06-09 16:28:59 +10:00
Oliver
218971e6f1 Merge pull request #864 from SchrodingersGat/translation-fixes
Update translation hooks
2020-06-09 16:28:34 +10:00
Oliver Walters
38dcdcad54 Update translation hooks 2020-06-09 16:18:38 +10:00
Oliver
aeba189c1a Merge pull request #863 from SchrodingersGat/decimalfix
Decimalize display of variant stock
2020-06-09 13:44:47 +10:00
Oliver Walters
09400fd66b Decimalize display of variant stock
(cherry picked from commit 6e45360c4b7a6bc91f584f498fb6e1988593db95)
2020-06-09 13:31:16 +10:00
Oliver
f3e3838782 Merge pull request #862 from SchrodingersGat/stock-buttons-fix
Fix display of buttons for stock item
2020-06-09 13:19:15 +10:00
Oliver Walters
c7586b83e9 Fix display of buttons for stock item
(cherry picked from commit b23017b6bf)
2020-06-09 13:15:12 +10:00
Oliver Walters
b23017b6bf Fix display of buttons for stock item 2020-06-09 13:14:29 +10:00
Oliver Walters
a6a58b299b Merge remote-tracking branch 'inventree/master' 2020-06-06 08:59:28 +10:00
Oliver
b2c302cb2c Merge pull request #858 from SchrodingersGat/stock-table-ipn
Add "IPN" column to stock list table
2020-06-06 08:58:59 +10:00
Oliver Walters
126e42a022 Add "IPN" column to stock list table 2020-06-06 08:47:06 +10:00
Oliver
22b30a0626 Merge pull request #857 from SchrodingersGat/security-fix
Update django version
2020-06-06 08:38:15 +10:00
Oliver Walters
84362ea790 Update django version 2020-06-06 08:27:13 +10:00
Oliver Walters
7be158722f Merge remote-tracking branch 'inventree/master' 2020-06-05 20:04:41 +10:00
Oliver
f563ac0dcb Merge pull request #855 from SchrodingersGat/part-table
Add "IPN" column to part table
2020-06-05 19:38:16 +10:00
Oliver Walters
16d261d956 Make description fields optional 2020-06-05 19:33:32 +10:00
Oliver Walters
15817ce61a Add "IPN" column to part table 2020-06-05 19:26:41 +10:00
Oliver
dedd87f20a Merge pull request #854 from SchrodingersGat/customer-stock-display
Customer stock display
2020-06-05 12:20:25 +10:00
Oliver Walters
fcd588cb01 Filter SalesOrder by status 2020-06-05 12:08:47 +10:00
Oliver Walters
6ebce2b2fd Add ability to filter purchase orders by "outstanding" status 2020-06-05 12:06:28 +10:00
Oliver Walters
544e217347 Add page to show stock which is assigned to a customer 2020-06-05 11:52:07 +10:00
Oliver Walters
9f599546de Use stock status for determining "in stock" 2020-06-05 09:08:46 +10:00
Oliver Walters
408c6a4040 Filter stock list by "customer" 2020-06-05 09:05:57 +10:00
Oliver Walters
2c7e0bd321 Merge remote-tracking branch 'inventree/master' 2020-06-04 20:07:59 +10:00
Oliver
96b79397c2 Merge pull request #852 from SchrodingersGat/send-to-customer
Send to customer
2020-06-04 19:55:57 +10:00
Oliver Walters
ca1526405b PEP fixes 2020-06-04 19:51:43 +10:00
Oliver Walters
80019a3ed8 Add forms/views for manually assigning a stock item to a customer 2020-06-04 19:45:41 +10:00
Oliver Walters
d907136264 Display customer in stock item 2020-06-04 19:13:37 +10:00
Oliver Walters
fbd21827fb Add 'customer' field to StockItem 2020-06-04 19:08:33 +10:00
Oliver Walters
4ddabb82ef Add a button to assign stock item to customer 2020-06-04 11:50:55 +10:00
Oliver
c1da4d5207 Merge pull request #850 from SchrodingersGat/bom-division-fix
Fix for case where BOM item quantity is zero
2020-06-04 11:42:39 +10:00
Oliver Walters
a3c0d35b20 Fix for case where BOM item quantity is zero 2020-06-04 11:37:55 +10:00
Oliver
7c7055c0a0 Merge pull request #845 from SchrodingersGat/tweaks
Tweaks
2020-05-30 22:03:15 +10:00
Oliver Walters
dd9584f487 Follow link when a new stock item is created 2020-05-30 20:58:52 +10:00
Oliver Walters
8459de7be2 Display a message if no stock location is set 2020-05-30 20:58:05 +10:00
Oliver Walters
b73044cd44 Merge remote-tracking branch 'inventree/master' 2020-05-29 13:47:19 +10:00
Oliver
55bfdc000b Merge pull request #844 from SchrodingersGat/part-validation-fixes
template parts are now allowed to be variants
2020-05-29 12:51:08 +10:00
Oliver Walters
5f3816212f template parts are now allowed to be variants
- Do not throw an error here!
2020-05-29 12:40:40 +10:00
Oliver
a6dab76ff6 Merge pull request #843 from SchrodingersGat/api-improvements
Api improvements
2020-05-28 21:31:28 +10:00
Oliver Walters
59d9e430b4 Remove unique-together requirement
- This is handled manually via the validate_unique function
2020-05-28 20:38:24 +10:00
Oliver Walters
774e26eb19 Mark some fields as not-required via the API 2020-05-28 20:33:33 +10:00
Oliver
f8e36c5468 Merge pull request #840 from SchrodingersGat/bom-queryset
Allow template parts to be specified for the BOM item
2020-05-27 10:47:46 +10:00
Oliver Walters
9b7b65374d Use rounding decimal field in BomItem form 2020-05-27 10:33:47 +10:00
Oliver Walters
7bcf9bf5be Allow template parts to be specified for the BOM item 2020-05-27 10:06:18 +10:00
Oliver Walters
d0b3e0bd7c Merge remote-tracking branch 'inventree/master' 2020-05-27 09:31:15 +10:00
Oliver
04858f8835 Merge pull request #839 from SchrodingersGat/serializer-fix
Fix serializer for stock item
2020-05-26 23:20:15 +10:00
Oliver Walters
78990a99e5 Fix serializer for stock item 2020-05-26 23:01:30 +10:00
Oliver Walters
fe69a8e7bb Merge remote-tracking branch 'inventree/master' 2020-05-26 21:53:20 +10:00
Oliver
002b896384 Merge pull request #838 from SchrodingersGat/attachies
Attachies
2020-05-26 20:33:06 +10:00
Oliver Walters
e128410d54 Update 'required' status for new serializers
(cherry picked from commit 2306e29743eb25266c78bec5357d1194e0778c58)
2020-05-26 20:29:09 +10:00
Oliver Walters
81e38eeb14 Add some more custom FileField serializers
(cherry picked from commit 7b9ff27baefacc977fef7852e99d8a9bb46a062a)
2020-05-26 20:29:04 +10:00
Oliver Walters
861e30e8d6 Add a special serializer class for FileField which has a custom to_representation function
- This was solving a very subtle bug which will probably only ever apply to a single installation instance
- Future me will most likely not remember what this was for or how it works
- In any case, there we go
- Ref: http://www.cdrf.co/3.9/rest_framework.fields/Field.html

(cherry picked from commit 7305094854)
2020-05-26 20:29:00 +10:00
Oliver Walters
03cc6892ea Merge remote-tracking branch 'inventree/master' 2020-05-26 12:52:17 +10:00
Oliver
3678c940eb Merge pull request #836 from SchrodingersGat/serial-number-fixes
Serial number fixes
2020-05-26 12:16:02 +10:00
Oliver Walters
c0f1966a2c Decimal filter on StockItem adjustment form 2020-05-26 12:08:00 +10:00
Oliver Walters
c92bb78ae8 Stock table display fixes 2020-05-26 12:06:38 +10:00
Oliver Walters
e63622341f Add description field to table filters 2020-05-26 11:57:35 +10:00
Oliver Walters
669a76c921 Add serial number range filtering options for the stock table 2020-05-26 11:54:23 +10:00
Oliver Walters
4cbf2099fa Add stock item filtering by serial number range 2020-05-26 11:49:48 +10:00
Oliver Walters
046a00026c remove debug print 2020-05-26 11:42:30 +10:00
Oliver Walters
47a4ab2ed8 Template changes 2020-05-26 11:38:17 +10:00
Oliver Walters
27ca84fd2a Part view shows next available serial number 2020-05-26 11:34:11 +10:00
Oliver Walters
ae4bfd07da StockItemCreate form now has better logic for auto-detecting the part 2020-05-26 11:31:08 +10:00
Oliver
8384071ac3 Merge pull request #834 from SchrodingersGat/api-bug-fix
Allow API upload of file attachments against StockItemTestResult objects
2020-05-25 22:24:33 +10:00
Oliver Walters
1bf696a052 Allow API upload of file attachments against StockItemTestResult objects 2020-05-25 22:19:28 +10:00
Oliver
835451c02c Merge pull request #833 from SchrodingersGat/latex-fix
Catch some errors related to missing system packages
2020-05-25 15:13:33 +10:00
Oliver Walters
75cdc7eb59 Catch some errors related to missing system packages 2020-05-25 14:57:15 +10:00
Oliver
9ad54c6579 Merge pull request #832 from SchrodingersGat/convert-variant
Convert variant
2020-05-25 14:27:55 +10:00
Oliver Walters
d2c1e9e2b0 Type fixes 2020-05-25 14:24:29 +10:00
Oliver Walters
0d052334e2 Allow stock items to be created for template parts 2020-05-25 14:20:35 +10:00
Oliver Walters
fdf57891fc Form / view / etc for performing StockItem conversion 2020-05-25 14:16:38 +10:00
Oliver Walters
009adaf528 Code to get and test for variants of a part 2020-05-25 13:13:28 +10:00
Oliver
cfc15582c1 Merge pull request #830 from SchrodingersGat/copy-test-results
Copy test results
2020-05-24 21:14:05 +10:00
Oliver Walters
22220493bd Add unit tests 2020-05-24 21:10:00 +10:00
Oliver Walters
95cc3d2a7a Copy test results when a stock item is split or serialized 2020-05-24 21:09:43 +10:00
Oliver
4292a32ab9 Merge pull request #829 from SchrodingersGat/bom-validation
Integer value required for trackable bom item
2020-05-24 20:26:18 +10:00
Oliver Walters
68b9a690f2 Integer value required for trackable bom item 2020-05-24 20:22:15 +10:00
Oliver
8537dc2a85 Merge pull request #828 from SchrodingersGat/api-stuff
Api stuff
2020-05-24 20:12:28 +10:00
Oliver Walters
c44205273c Simplify javascript 2020-05-24 20:05:34 +10:00
Oliver Walters
e4d10279fa Include 'key' field in StockItemTestResult serializer 2020-05-24 20:04:34 +10:00
Oliver Walters
01481ef5c9 Add function to get the number of required tests for a part 2020-05-23 14:28:25 +10:00
Oliver Walters
e63342418f Improve / simplify logic for file attachments against test result object 2020-05-23 11:30:42 +10:00
Oliver Walters
50987f47b0 Add a buttony thing 2020-05-22 23:37:26 +10:00
Oliver Walters
4bd0872b2c Expose some more stock item data over API 2020-05-22 23:33:27 +10:00
Oliver
203062a67a Merge pull request #824 from SchrodingersGat/reporting-app
Reporting app
2020-05-22 23:16:24 +10:00
Oliver Walters
34b40d205e Expose 'part' to the TestReport interface 2020-05-22 23:08:15 +10:00
Oliver Walters
5245442b11 Fixes 2020-05-22 22:27:52 +10:00
Oliver Walters
e6f56cb056 where one or more test report templates exist for a part,
provide a button for all stock-items of that part,
allowing the user to generate and download a test repotr
2020-05-22 22:25:05 +10:00
Oliver Walters
71681bfda1 Add a button if a stock item test report is available 2020-05-22 21:38:05 +10:00
Oliver Walters
1ad7e699a9 PEP 2020-05-22 21:31:21 +10:00
Oliver Walters
0ec880290b Functionality for retrieving test templates associated with a given part 2020-05-22 21:29:58 +10:00
Oliver Walters
7215a563b1 Add PartFilterMixin 2020-05-22 21:22:43 +10:00
Oliver Walters
616f17d08a Reset the report app migrations 2020-05-22 21:01:08 +10:00
Oliver Walters
d6cad372db Add migration 2020-05-22 13:05:30 +10:00
Oliver Walters
865a6db828 Fix subdirectory lookup 2020-05-22 13:05:12 +10:00
Oliver Walters
174c4cc591 Add subclass models for report types 2020-05-22 13:01:21 +10:00
Oliver Walters
251a23d127 Cleanup 2020-05-22 00:09:51 +10:00
Oliver Walters
cab87a6860 Update admin 2020-05-22 00:01:36 +10:00
Oliver Walters
b93ba6339a Option for rendering HTML template 2020-05-21 23:41:47 +10:00
Oliver Walters
c3dcabcaad Render an uploaded template to LaTeX 2020-05-21 23:03:01 +10:00
Oliver Walters
05be4da25c remove test code 2020-05-21 14:06:29 +10:00
Oliver Walters
70c5b27d22 Add ReportAsset model
- Files which can be embedded into a report
2020-05-21 14:05:25 +10:00
Oliver Walters
b78fe88c26 PEP fixes 2020-05-21 13:53:17 +10:00
Oliver Walters
3d8c059a43 Add "report" app
- Define ReportTemplate model which contains a report file template
2020-05-21 13:51:13 +10:00
Oliver
5018f899f7 Update README.md 2020-05-20 10:45:43 +10:00
Oliver Walters
59dfac2c9b Merge branch 'warning-mesages' 2020-05-17 22:58:21 +10:00
Oliver Walters
1e94a597c9 Merge remote-tracking branch 'inventree/master' 2020-05-17 00:42:03 +10:00
Oliver Walters
dcf00d816d Merge remote-tracking branch 'inventree/master' 2020-05-12 22:14:20 +10:00
Oliver Walters
e4872d9f67 Merge remote-tracking branch 'inventree/master' 2020-05-12 09:16:04 +10:00
Oliver Walters
a86513e15d Merge remote-tracking branch 'inventree/master' 2020-05-12 00:29:33 +10:00
Oliver Walters
c5999f7186 Merge remote-tracking branch 'inventree/master' 2020-05-11 23:10:29 +10:00
Oliver Walters
bbf6f6f93d Merge remote-tracking branch 'inventree/master' 2020-05-04 12:04:02 +10:00
Oliver Walters
15011b7b20 Merge remote-tracking branch 'inventree/master' 2020-05-04 09:46:43 +10:00
Oliver Walters
1c3878829d Merge remote-tracking branch 'inventree/master' 2020-05-04 09:39:19 +10:00
Oliver Walters
fb0ebbb1e4 Merge remote-tracking branch 'inventree/master' 2020-05-02 21:05:00 +10:00
Oliver Walters
937211288e Merge remote-tracking branch 'inventree/master' 2020-05-02 17:30:08 +10:00
Oliver Walters
6912899f41 Merge remote-tracking branch 'inventree/master' 2020-05-02 15:37:24 +10:00
Oliver Walters
094ef38e27 Merge remote-tracking branch 'inventree/master' 2020-05-02 15:29:47 +10:00
Oliver Walters
36ad69091b Merge remote-tracking branch 'inventree/master' 2020-04-28 12:34:47 +10:00
Oliver Walters
351e825ba8 Merge remote-tracking branch 'inventree/master' 2020-04-28 10:51:55 +10:00
Oliver Walters
b338834146 Merge remote-tracking branch 'inventree/master' 2020-04-27 22:18:45 +10:00
Oliver Walters
87d8b4674a Merge remote-tracking branch 'inventree/master' 2020-04-20 09:58:49 +10:00
Oliver Walters
c49cd9ffde Merge remote-tracking branch 'inventree/master' 2020-04-20 01:28:31 +10:00
Oliver Walters
51e2f5c46b Merge remote-tracking branch 'inventree/master' 2020-04-18 15:46:33 +10:00
Oliver Walters
4822d3fd63 Merge remote-tracking branch 'inventree/master' 2020-04-17 21:49:00 +10:00
Oliver Walters
cbfb3b55e6 Merge remote-tracking branch 'upstream/master' 2020-04-17 19:14:15 +10:00
Oliver Walters
762d735618 Merge remote-tracking branch 'inventree/master' 2020-04-16 21:55:59 +10:00
Oliver Walters
2e1c4e9792 Merge remote-tracking branch 'inventree/master' 2020-04-13 20:43:00 +10:00
Oliver Walters
a8ceddc8e4 Merge remote-tracking branch 'inventree/master' 2020-04-12 01:05:42 +10:00
Oliver Walters
ba2b1ce581 Merge remote-tracking branch 'inventree/master' 2020-04-10 01:12:11 +10:00
Oliver Walters
4a02a90954 Merge remote-tracking branch 'inventree/master' 2020-04-07 00:08:44 +10:00
Oliver Walters
93eeeec2f3 Merge remote-tracking branch 'inventree/master' 2020-04-06 21:15:06 +10:00
Oliver Walters
b8f7555a73 Merge remote-tracking branch 'inventree/master' 2020-04-06 16:25:48 +10:00
Oliver Walters
efaa532162 Merge remote-tracking branch 'inventree/master' 2020-04-05 22:07:57 +10:00
Oliver Walters
428b52693a Merge remote-tracking branch 'inventree/master' 2020-04-05 01:00:00 +11:00
Oliver Walters
55e831f4b4 Merge remote-tracking branch 'inventree/master' 2020-04-03 14:24:35 +11:00
Oliver Walters
aada5f91cb Merge remote-tracking branch 'inventree/master' 2020-03-31 22:15:58 +11:00
Oliver Walters
b0edd0eb05 Merge remote-tracking branch 'inventree/master' 2020-03-30 19:06:30 +11:00
Oliver Walters
5b77ff4a4c Merge remote-tracking branch 'inventree/master' 2020-03-26 18:08:34 +11:00
Oliver Walters
ed6abcdf32 Merge remote-tracking branch 'inventree/master' 2020-03-22 20:20:22 +11:00
Oliver Walters
dc0a0f9dc2 Merge branch 'master' of https://github.com/inventree/inventree 2020-03-22 20:14:34 +11:00
Oliver Walters
02ec1d4fa2 Merge remote-tracking branch 'inventree/master' 2020-03-19 10:29:26 +11:00
Oliver Walters
d8f5255572 Merge remote-tracking branch 'inventree/master' 2020-03-18 22:24:52 +11:00
Oliver Walters
7424cc1352 Merge remote-tracking branch 'inventree/master' 2020-03-18 21:33:09 +11:00
Oliver Walters
41259f520f Merge remote-tracking branch 'inventree/master' 2020-03-18 21:12:49 +11:00
Oliver Walters
388b26dad2 Merge remote-tracking branch 'inventree/master' 2020-02-25 12:42:49 +11:00
Oliver Walters
ef83480f65 Merge remote-tracking branch 'inventree/master' 2020-02-19 00:03:32 +11:00
Oliver Walters
0498fd633a Merge remote-tracking branch 'inventree/master' 2020-02-12 12:56:17 +11:00
Oliver Walters
3479528d5b Merge branch 'cascading-bom' 2020-02-11 22:48:33 +11:00
Oliver Walters
bb4c25ba68 Merge remote-tracking branch 'inventree/master' 2020-02-11 20:36:13 +11:00
Oliver Walters
bb0c991965 Merge branch 'inventree-settings' 2020-02-03 21:34:08 +11:00
Oliver Walters
8412885e09 Merge remote-tracking branch 'inventree/master' 2020-02-02 22:26:18 +11:00
Oliver Walters
22e3dc634b Merge remote-tracking branch 'inventree/master' 2020-02-02 21:11:52 +11:00
Oliver Walters
e03d8f55ea Merge remote-tracking branch 'inventree/master' 2020-02-02 18:10:05 +11:00
Oliver Walters
40037d826d Merge remote-tracking branch 'inventree/master' 2020-02-02 12:48:04 +11:00
Oliver Walters
8d924eb300 Merge remote-tracking branch 'inventree/master' 2020-01-19 21:39:22 +11:00
Oliver Walters
07f3cd5644 Merge remote-tracking branch 'inventree/master' 2020-01-07 21:31:28 +11:00
Oliver Walters
f217650cec Merge remote-tracking branch 'inventree/master' 2020-01-06 21:11:00 +11:00
Oliver Walters
07de23f120 Merge remote-tracking branch 'inventree/master' 2020-01-06 10:11:18 +11:00
Oliver Walters
881931c6b1 Merge remote-tracking branch 'inventree/master' 2020-01-02 21:08:56 +11:00
Oliver Walters
6bbe818e9c Merge remote-tracking branch 'inventree/master' 2019-12-09 22:27:06 +11:00
Oliver Walters
f6d5f2e426 Merge remote-tracking branch 'inventree/master' 2019-12-05 23:11:54 +11:00
Oliver Walters
0e57a4caec Merge remote-tracking branch 'inventree/master' 2019-12-05 16:36:23 +11:00
Oliver Walters
b29e1ded64 Merge branch 'decimal-quantity' 2019-11-19 21:47:22 +11:00
Oliver Walters
6e54c49142 Merge remote-tracking branch 'inventree/master' 2019-11-16 20:45:34 +11:00
Oliver Walters
f7a1fb0a9d Merge remote-tracking branch 'upstream/master' 2019-09-30 13:54:49 +10:00
Oliver Walters
cfe33deb8e Merge remote-tracking branch 'upstream/master' 2019-09-29 09:04:47 +10:00
Oliver Walters
e891f117f3 Merge remote-tracking branch 'inventree/master' 2019-09-27 10:25:11 +10:00
Oliver Walters
6d628a77fd Merge remote-tracking branch 'inventree/master' 2019-09-24 08:10:00 +10:00
Oliver Walters
14217ff648 Merge remote-tracking branch 'inventree/master' 2019-09-24 08:06:11 +10:00
Oliver Walters
d6749dfb24 Merge remote-tracking branch 'inventree/master' 2019-09-22 22:34:34 +10:00
Oliver Walters
41d91a8f9b Merge remote-tracking branch 'inventree/master' 2019-09-19 15:43:15 +10:00
Oliver Walters
119cccc318 Merge remote-tracking branch 'inventree/master' 2019-09-17 20:32:30 +10:00
Oliver Walters
0a7ea27e9f Merge remote-tracking branch 'inventree/master' 2019-09-17 14:22:04 +10:00
Oliver Walters
420b16104c Merge remote-tracking branch 'inventree/master' 2019-09-16 00:09:20 +10:00
Oliver Walters
f64758eb03 Merge remote-tracking branch 'inventree/master' 2019-09-15 23:29:36 +10:00
Oliver Walters
d0ad3f0e37 Merge remote-tracking branch 'inventree/master' 2019-09-15 22:28:55 +10:00
Oliver Walters
5fde9777fb Merge remote-tracking branch 'inventree/master' 2019-09-14 00:19:17 +10:00
Oliver Walters
4b33b15dd2 Merge remote-tracking branch 'inventree/master' 2019-09-13 23:38:07 +10:00
Oliver Walters
2be99be4de Merge remote-tracking branch 'inventree/master' 2019-09-13 16:41:34 +10:00
Oliver Walters
625ab33bc8 Merge remote-tracking branch 'inventree/master' 2019-09-13 15:38:41 +10:00
Oliver Walters
9681372a84 Merge remote-tracking branch 'inventree/master' 2019-09-13 11:20:39 +10:00
Oliver Walters
0683911076 Merge remote-tracking branch 'inventree/master' 2019-09-12 22:55:58 +10:00
Oliver Walters
d703e48ecb Merge remote-tracking branch 'inventree/master' 2019-09-10 14:50:47 +10:00
Oliver Walters
015ed66967 Merge remote-tracking branch 'inventree/master' 2019-09-09 20:54:55 +10:00
Oliver Walters
0a4f7a55b8 Merge remote-tracking branch 'inventree/master' 2019-09-09 16:20:01 +10:00
Oliver Walters
a6e850e39f Merge remote-tracking branch 'inventree/master' 2019-09-09 15:03:57 +10:00
Oliver Walters
abecb4856f Merge remote-tracking branch 'inventree/master' 2019-09-09 00:09:58 +10:00
Oliver Walters
dbf88f8485 Merge remote-tracking branch 'upstream/master' 2019-09-08 01:48:20 +10:00
Oliver Walters
83af318ad0 Merge remote-tracking branch 'inventree/master' 2019-09-07 21:23:50 +10:00
Oliver Walters
7be1edd896 Merge remote-tracking branch 'inventree/master' 2019-09-07 20:37:30 +10:00
Oliver Walters
d6ca21273d Merge remote-tracking branch 'inventree/master' 2019-09-07 15:03:54 +10:00
Oliver Walters
dd934f0e34 Merge remote-tracking branch 'inventree/master' 2019-09-06 14:42:40 +10:00
Oliver Walters
612851fd48 Merge remote-tracking branch 'inventree/master' 2019-09-06 11:19:48 +10:00
Oliver Walters
755962c6a2 Merge remote-tracking branch 'inventree/master' 2019-09-05 20:13:17 +10:00
Oliver Walters
153dde4129 Merge remote-tracking branch 'inventree/master' 2019-09-04 11:02:27 +10:00
Oliver Walters
8f3a022b3c Merge remote-tracking branch 'inventree/master' 2019-09-04 08:57:24 +10:00
Oliver Walters
017bf187f9 Merge remote-tracking branch 'inventree/master' 2019-09-03 10:42:14 +10:00
Oliver Walters
7b8d7c9fe0 Merge remote-tracking branch 'upstream/master' 2019-09-01 23:33:39 +10:00
Oliver Walters
d319c79abb Merge remote-tracking branch 'upstream/master' 2019-08-30 02:59:01 +10:00
Oliver Walters
1e62528d2a Merge remote-tracking branch 'upstream/master' 2019-08-29 21:37:03 +10:00
Oliver Walters
7ae7c19a24 Merge remote-tracking branch 'inventree/master' 2019-08-29 08:21:21 +10:00
Oliver Walters
0dce5fab7b Merge remote-tracking branch 'inventree/master' 2019-08-28 19:48:46 +10:00
Oliver Walters
d1dc0fae19 Merge remote-tracking branch 'inventree/master' 2019-08-15 22:02:45 +10:00
Oliver Walters
dd33209b1c Merge remote-tracking branch 'inventree/master' 2019-08-15 19:54:21 +10:00
Oliver Walters
c3fe101571 Merge remote-tracking branch 'inventree/master' 2019-08-15 13:41:50 +10:00
Oliver Walters
2c1b20b5d5 Merge branch 'mysql-install' 2019-08-14 13:19:13 +10:00
Oliver Walters
c1ab004c0c Merge remote-tracking branch 'inventree/master' 2019-08-09 20:37:46 +10:00
Oliver Walters
348f2e1df8 Merge remote-tracking branch 'inventree/master' 2019-08-07 10:29:32 +10:00
Oliver Walters
3098c8c155 Merge branch 'bom-output' 2019-08-07 09:27:31 +10:00
Oliver Walters
55ed483b13 Merge remote-tracking branch 'inventree/master' 2019-08-07 08:55:01 +10:00
Oliver Walters
84ea95181d Merge remote-tracking branch 'inventree/master' 2019-08-02 22:37:26 +10:00
Oliver Walters
163f076565 Merge remote-tracking branch 'inventree/master' 2019-07-23 12:07:13 +10:00
Oliver Walters
7d2d6a79e4 Merge remote-tracking branch 'inventree/master' 2019-07-12 00:20:54 +10:00
Oliver Walters
40695b3288 Merge remote-tracking branch 'inventree/master' 2019-07-10 20:13:35 +10:00
Oliver Walters
bd9d5b92dc Merge remote-tracking branch 'inventree/master' 2019-06-25 21:04:02 +10:00
Oliver Walters
39a62d1cb9 Merge remote-tracking branch 'origin/master' 2019-06-25 16:39:50 +10:00
Oliver Walters
fd5b902e1f Merge remote-tracking branch 'inventree/master' 2019-06-25 16:39:41 +10:00
Oliver Walters
c7877c67ff Add a simple endpoint with server information 2019-06-24 23:51:46 +10:00
Oliver Walters
5d0369958f Merge remote-tracking branch 'inventree/master' 2019-06-24 08:27:58 +10:00
Oliver Walters
6da0106aa2 Merge remote-tracking branch 'inventree/master' 2019-06-20 21:49:30 +10:00
Oliver Walters
e0bfda2b14 Merge remote-tracking branch 'inventree/master' 2019-06-20 21:49:19 +10:00
Oliver Walters
e5c2ee5587 Merge remote-tracking branch 'inventree/master' 2019-06-19 08:12:14 +10:00
Oliver Walters
d4391f99bb Merge remote-tracking branch 'inventree/master' 2019-06-18 23:26:48 +10:00
Oliver Walters
346c713cd2 Merge remote-tracking branch 'inventree/master' 2019-06-18 22:26:47 +10:00
Oliver Walters
ec44a480a8 Merge remote-tracking branch 'inventree/master' 2019-06-18 11:51:02 +10:00
Oliver Walters
f417ddb8e0 Merge remote-tracking branch 'inventree/master' 2019-06-18 01:50:50 +10:00
Oliver Walters
1b43f151df Merge remote-tracking branch 'inventree/master' 2019-06-17 23:28:35 +10:00
Oliver Walters
6b5bed7786 Merge remote-tracking branch 'inventree/master' 2019-06-13 23:07:54 +10:00
Oliver Walters
c906cbe2c2 Merge branch 'stock-endpoint' 2019-06-13 23:06:08 +10:00
Oliver Walters
1dea5f1624 Merge remote-tracking branch 'inventree/master' 2019-06-13 22:54:10 +10:00
Oliver Walters
0fea7f2a22 Merge remote-tracking branch 'inventree/master' 2019-06-11 08:57:14 +10:00
Oliver Walters
45d19e3a4e Merge remote-tracking branch 'inventree/master' 2019-06-11 08:40:11 +10:00
Oliver Walters
8a46dda07f Merge remote-tracking branch 'inventree/master' 2019-06-10 23:22:04 +10:00
Oliver Walters
166b41196b Merge remote-tracking branch 'inventree/master' 2019-06-04 21:22:36 +10:00
Oliver Walters
a13e50445e Merge branch 'tweeeek' 2019-06-03 14:19:21 +10:00
Oliver Walters
c00b19bc5b Merge remote-tracking branch 'inventree/master' 2019-06-02 20:42:37 +10:00
Oliver Walters
9577e4e505 Merge remote-tracking branch 'inventree/master'
# Conflicts:
#	InvenTree/static/script/inventree/stock.js
#	InvenTree/stock/forms.py
#	InvenTree/stock/urls.py
#	InvenTree/stock/views.py
2019-06-02 12:51:56 +10:00
Oliver Walters
72dc75512b Pass list of selected stock items to the view 2019-05-29 22:23:45 +10:00
Oliver Walters
f4a052ee2b Redirect non-ajax forms to / 2019-05-29 22:11:03 +10:00
Oliver Walters
080f9da9c8 Beginning to move the stocktake forms server side 2019-05-29 22:05:13 +10:00
Oliver Walters
1c1cc670f8 Merge remote-tracking branch 'inventree/master' 2019-05-29 01:06:27 +10:00
Oliver Walters
141d9b779d Merge remote-tracking branch 'inventree/master' 2019-05-26 16:09:20 +10:00
Oliver Walters
dbfa812d37 Merge remote-tracking branch 'inventree/master' 2019-05-26 00:48:45 +10:00
Oliver Walters
026ffde807 Merge remote-tracking branch 'inventree/master' 2019-05-25 22:11:21 +10:00
Oliver Walters
e07d7e3874 Merge remote-tracking branch 'inventree/master' 2019-05-23 23:24:11 +10:00
Oliver Walters
413e996e92 Merge remote-tracking branch 'inventree/master' 2019-05-23 23:01:17 +10:00
Oliver Walters
bebf2c9bee Merge remote-tracking branch 'inventree/master' 2019-05-23 00:48:05 +10:00
Oliver Walters
afb927258c Merge remote-tracking branch 'inventree/master' 2019-05-21 16:37:16 +10:00
Oliver Walters
a15f46c972 Merge remote-tracking branch 'inventree/master' 2019-05-21 14:13:30 +10:00
Oliver Walters
92b0ed6599 Merge remote-tracking branch 'inventree/master' 2019-05-21 08:58:08 +10:00
Oliver Walters
29ab493cbe Merge remote-tracking branch 'inventree/master' 2019-05-21 00:58:19 +10:00
Oliver Walters
90915f8dc0 Merge remote-tracking branch 'inventree/master' 2019-05-19 00:14:35 +10:00
Oliver Walters
1f4471ccc3 Merge remote-tracking branch 'inventree/master' 2019-05-18 15:40:50 +10:00
Oliver Walters
5d4ec2154b Merge remote-tracking branch 'inventree/master' 2019-05-16 23:15:02 +10:00
Oliver Walters
9d9f30e41f Merge branch 'secondary-modals' 2019-05-14 23:53:14 +10:00
Oliver Walters
e3a8bb23c1 Merge remote-tracking branch 'inventree/master' 2019-05-14 18:54:23 +10:00
Oliver Walters
787ab0a2e4 Merge remote-tracking branch 'inventree/master' 2019-05-13 23:07:12 +10:00
Oliver Walters
c140ecf14b Merge remote-tracking branch 'inventree/master' 2019-05-13 19:00:51 +10:00
Oliver Walters
980116b33b Merge remote-tracking branch 'inventree/master' 2019-05-12 20:58:06 +10:00
Oliver Walters
142a7659bd Merge remote-tracking branch 'inventree/master' 2019-05-12 16:39:02 +10:00
Oliver Walters
6fbe588aee Merge branch 'case-insensitive-names' 2019-05-11 20:28:54 +10:00
Oliver Walters
17eb8237da Merge remote-tracking branch 'inventree/master' 2019-05-11 09:00:41 +10:00
Oliver Walters
f11cee7197 Merge remote-tracking branch 'inventree/master' 2019-05-10 23:29:16 +10:00
Oliver Walters
1b28326c5e Merge branch 'part-revision' 2019-05-10 20:19:21 +10:00
Oliver Walters
6ffb6db248 Merge remote-tracking branch 'inventree/master' 2019-05-10 08:52:45 +10:00
Oliver Walters
9b0825739a Merge remote-tracking branch 'inventree/master' 2019-05-09 23:07:42 +10:00
Oliver Walters
b42f7fce44 Merge remote-tracking branch 'inventree/master' 2019-05-09 18:44:29 +10:00
Oliver Walters
30f491795d Merge remote-tracking branch 'inventree/master' 2019-05-09 18:33:36 +10:00
Oliver Walters
55f7557b5e Merge remote-tracking branch 'inventree/master' 2019-05-09 18:30:49 +10:00
Oliver Walters
5a0de7088b Merge remote-tracking branch 'inventree/master' 2019-05-09 12:03:36 +10:00
Oliver Walters
9319b9f433 Merge remote-tracking branch 'inventree/master' 2019-05-09 00:53:20 +10:00
Oliver Walters
b8942acfed Merge remote-tracking branch 'inventree/master' 2019-05-09 00:44:19 +10:00
Oliver Walters
a0d8fbede3 Merge remote-tracking branch 'inventree/master' 2019-05-09 00:42:50 +10:00
Oliver Walters
8e8930d51f Merge branch 'help-text' 2019-05-09 00:20:31 +10:00
Oliver Walters
09b7578c35 Merge remote-tracking branch 'inventree/master' 2019-05-08 23:33:55 +10:00
Oliver Walters
fdd4d09714 Merge remote-tracking branch 'inventree/master' 2019-05-08 23:05:49 +10:00
Oliver Walters
0deb6e8bea Merge remote-tracking branch 'inventree/master' 2019-05-08 22:09:40 +10:00
Oliver Walters
586bad4876 Merge remote-tracking branch 'inventree/master' 2019-05-07 21:17:42 +10:00
Oliver Walters
47fd317a11 Merge remote-tracking branch 'inventree/master' 2019-05-07 14:53:31 +10:00
Oliver Walters
0c229ede24 Merge remote-tracking branch 'inventree/master' 2019-05-07 10:30:07 +10:00
Oliver Walters
64a6ee0f3b Merge remote-tracking branch 'inventree/master' 2019-05-07 08:14:12 +10:00
Oliver Walters
364f78d20c Merge remote-tracking branch 'inventree/master' 2019-05-07 00:19:18 +10:00
Oliver Walters
ff0d163a95 Limit SupplierPart choices in EditStockItem view 2019-05-06 18:05:29 +10:00
Oliver Walters
a7a37437a1 Merge remote-tracking branch 'inventree/master' 2019-05-06 18:00:43 +10:00
Oliver Walters
e333f5f726 Merge remote-tracking branch 'inventree/master' 2019-05-06 07:36:37 +10:00
Oliver Walters
c822450792 Merge remote-tracking branch 'inventree/master' 2019-05-05 20:22:41 +10:00
Oliver Walters
e6d77273c4 Merge remote-tracking branch 'inventree/master' 2019-05-05 11:53:50 +10:00
Oliver Walters
c203c3542f Merge remote-tracking branch 'inventree/master' 2019-05-05 11:49:58 +10:00
Oliver Walters
d218f07940 Merge remote-tracking branch 'inventree/master' 2019-05-05 01:07:35 +10:00
Oliver Walters
0da2999c75 Merge remote-tracking branch 'inventree/master' 2019-05-05 00:39:36 +10:00
Oliver Walters
67cfd22084 Merge branch 'part-image-hover' 2019-05-05 00:30:23 +10:00
Oliver Walters
d97484b30b Merge remote-tracking branch 'inventree/master' 2019-05-04 22:03:18 +10:00
Oliver Walters
d5ad888b0a Merge remote-tracking branch 'inventree/master' 2019-05-04 21:59:48 +10:00
Oliver Walters
a7cec1627f Merge remote-tracking branch 'inventree/master' 2019-05-04 11:49:49 +10:00
Oliver Walters
e42a1b7b88 Merge remote-tracking branch 'inventree/master' 2019-05-04 10:47:55 +10:00
Oliver Walters
dc03283e14 Merge branch 'image-upload-fix' 2019-05-04 00:43:47 +10:00
Oliver Walters
4aef7643a0 Fixed part image upload
- Image was being saved twice
- Don't call super().post in AjaxUpdateView
- Instead, handle properly
2019-05-04 00:37:08 +10:00
Oliver Walters
ac17cf0121 Merge remote-tracking branch 'inventree/master' 2019-05-04 00:03:46 +10:00
Oliver Walters
e19061c902 Merge remote-tracking branch 'inventree/master' 2019-05-03 23:53:37 +10:00
Oliver Walters
db71993905 Merge remote-tracking branch 'inventree/master' 2019-05-03 08:08:31 +10:00
Oliver Walters
3f8f7ed32c Merge remote-tracking branch 'inventree/master' 2019-05-02 21:33:51 +10:00
Oliver Walters
b7ac86fab4 Merge remote-tracking branch 'inventree/master' 2019-05-02 21:30:34 +10:00
Oliver Walters
79e0f972e7 Merge remote-tracking branch 'inventree/master' 2019-05-02 17:47:17 +10:00
Oliver Walters
255d754045 Merge remote-tracking branch 'inventree/master' 2019-05-02 01:05:08 +10:00
Oliver Walters
385c13148d Merge branch 'migration-test' 2019-05-02 01:03:08 +10:00
Oliver Walters
4de3445c58 Merge remote-tracking branch 'inventree/master' 2019-05-02 00:56:42 +10:00
Oliver Walters
a2314074d3 Merge remote-tracking branch 'inventree/master' 2019-05-02 00:30:17 +10:00
Oliver Walters
460ff571e4 Merge remote-tracking branch 'inventree/master' 2019-05-02 00:26:34 +10:00
Oliver Walters
3a10d235c1 Merge remote-tracking branch 'inventree/master' 2019-04-29 00:05:18 +10:00
Oliver Walters
207d2bb4d3 Merge remote-tracking branch 'inventree/master' 2019-04-28 21:26:33 +10:00
Oliver Walters
2076faaa6c Added docstring 2019-04-28 11:49:20 +10:00
Oliver Walters
ec166c906f Merge remote-tracking branch 'inventree/master' 2019-04-28 11:29:23 +10:00
Oliver Walters
c0e69d7a99 Merge remote-tracking branch 'inventree/master' 2019-04-28 01:18:32 +10:00
Oliver Walters
a8348aa674 Merge remote-tracking branch 'inventree/master' 2019-04-27 18:01:57 +10:00
Oliver Walters
b17590942d Merge remote-tracking branch 'inventree/master' 2019-04-27 17:56:49 +10:00
Oliver Walters
062b3fd0fe Merge remote-tracking branch 'inventree/master' 2019-04-27 15:32:46 +10:00
Oliver Walters
f2f40e4758 Merge branch 'stockitem-note' 2019-04-27 13:56:41 +10:00
Oliver Walters
922feec7f4 Merge remote-tracking branch 'inventree/master' 2019-04-27 01:08:14 +10:00
Oliver Walters
64636a6d60 Merge remote-tracking branch 'inventree/master' 2019-04-27 01:05:12 +10:00
Oliver Walters
ac46a4a413 Merge remote-tracking branch 'inventree/master' 2019-04-26 21:44:44 +10:00
Oliver Walters
7593d855cb Merge remote-tracking branch 'inventree/master' 2019-04-26 21:40:26 +10:00
Oliver Walters
27c7f6589c Merge remote-tracking branch 'inventree/master' 2019-04-26 21:25:00 +10:00
Oliver Walters
edad4f9cf1 Merge branch 'master' of https://github.com/SchrodingersGat/InvenTree
# Conflicts:
#	Makefile
2019-04-26 21:24:03 +10:00
Oliver Walters
14e9b71ebb Remove comment line in Makefile which doesn't play nice on Windows 2019-04-26 18:51:37 +10:00
331 changed files with 39091 additions and 4469 deletions

View File

@@ -1,5 +1,9 @@
dist: xenial
services:
- mysql
- postgresql
language: python
python:
- 3.6
@@ -12,16 +16,25 @@ addons:
before_install:
- sudo apt-get update
- sudo apt-get install gettext
- make install
- make migrate
- sudo apt-get install mysql-server libmysqlclient-dev
- sudo apt-get install libpq-dev
- pip3 install invoke
- pip3 install mysqlclient
- pip3 install psycopg2
- invoke install
- invoke migrate
- cd InvenTree && python3 manage.py createsuperuser --username InvenTreeAdmin --email admin@inventree.com --noinput && cd ..
- psql -c 'create database inventree_test_db;' -U postgres
- mysql -e 'CREATE DATABASE inventree_test_db;'
script:
- cd InvenTree && python3 manage.py makemigrations && cd ..
- python3 ci/check_migration_files.py
- make coverage
- make translate
- make style
- invoke coverage
- cd InvenTree && python3 manage.py test --settings=InvenTree.ci_mysql && cd ..
- cd InvenTree && python3 manage.py test --settings=InvenTree.ci_postgresql && cd ..
- invoke translate
- invoke style
after_success:
- coveralls

View File

@@ -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.

View File

@@ -20,9 +20,6 @@ from .version import inventreeVersion, inventreeInstanceName
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")
action_plugins = inventree_plugins.load_action_plugins()
@@ -100,66 +97,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)

View File

@@ -0,0 +1,18 @@
"""
Configuration file for running tests against a MySQL database.
"""
from InvenTree.settings import *
# Override the 'test' database
if 'test' in sys.argv:
eprint('InvenTree: Running tests - Using MySQL test database')
DATABASES['default'] = {
# Ensure mysql backend is being used
'ENGINE': 'django.db.backends.mysql',
'NAME': 'inventree_test_db',
'USER': 'travis',
'PASSWORD': '',
'HOST': '127.0.0.1'
}

View File

@@ -0,0 +1,17 @@
"""
Configuration file for running tests against a MySQL database.
"""
from InvenTree.settings import *
# Override the 'test' database
if 'test' in sys.argv:
eprint('InvenTree: Running tests - Using PostGreSQL test database')
DATABASES['default'] = {
# Ensure postgresql backend is being used
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'inventree_test_db',
'USER': 'postgres',
'PASSWORD': '',
}

View File

@@ -11,7 +11,7 @@ from django.core import validators
from django import forms
from decimal import Decimal
from InvenTree.helpers import normalize
import InvenTree.helpers
class InvenTreeURLFormField(FormURLField):
@@ -55,7 +55,7 @@ class RoundingDecimalFormField(forms.DecimalField):
"""
if type(value) == Decimal:
return normalize(value)
return InvenTree.helpers.normalize(value)
else:
return value

View File

@@ -9,8 +9,9 @@ from django.utils.translation import ugettext 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
class HelperForm(forms.ModelForm):
@@ -161,3 +162,36 @@ 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',
),
)

View File

@@ -15,7 +15,8 @@ from django.http import StreamingHttpResponse
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from .version import inventreeVersion, inventreeInstanceName
import InvenTree.version
from .settings import MEDIA_URL, STATIC_URL
@@ -242,7 +243,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 +256,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', False)
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)
@@ -363,14 +372,10 @@ def ExtractSerialNumbers(serials, expected_quantity):
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)
@@ -383,3 +388,56 @@ def ExtractSerialNumbers(serials, 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):
"""
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
return results

View File

@@ -91,6 +91,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):

View File

@@ -102,12 +102,14 @@ class InvenTreeTree(MPTTModel):
name = models.CharField(
blank=False,
max_length=100,
validators=[validate_tree_name]
validators=[validate_tree_name],
help_text=_("Name"),
)
description = models.CharField(
blank=False,
max_length=250
blank=True,
max_length=250,
help_text=_("Description (optional)")
)
# When a category is deleted, graft the children onto its parent

View 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

View File

@@ -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))

View File

@@ -72,6 +72,30 @@ if 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(CONFIG.get('static_root', os.path.join(BASE_DIR, 'static')))
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'InvenTree', 'static'),
]
# Color Themes Directory
STATIC_COLOR_THEMES_DIR = os.path.join(STATIC_ROOT, 'css', 'color-themes')
# 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)
# Does the user wish to use the sentry.io integration?
sentry_opts = CONFIG.get('sentry', {})
@@ -106,12 +130,14 @@ 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',
# Third part add-ons
'django_filters', # Extended filter functionality
@@ -126,6 +152,7 @@ INSTALLED_APPS = [
'mptt', # Modified Preorder Tree Traversal
'markdownx', # Markdown editing
'markdownify', # Markdown template rendering
'django_tex', # LaTeX output
]
LOGGING = {
@@ -139,7 +166,7 @@ LOGGING = {
},
}
MIDDLEWARE = [
MIDDLEWARE = CONFIG.get('middleware', [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
@@ -149,18 +176,29 @@ 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')
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):
print("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'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -173,6 +211,14 @@ TEMPLATES = [
],
},
},
# Backend for LaTeX report rendering
{
'NAME': 'tex',
'BACKEND': 'django_tex.engine.TeXEngine',
'DIRS': [
os.path.join(MEDIA_ROOT, 'report'),
]
},
]
REST_FRAMEWORK = {
@@ -315,31 +361,22 @@ DATE_INPUT_FORMATS = [
"%Y-%m-%d",
]
# LaTeX rendering settings (django-tex)
LATEX_SETTINGS = CONFIG.get('latex', {})
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
# Is LaTeX rendering enabled? (Off by default)
LATEX_ENABLED = LATEX_SETTINGS.get('enabled', False)
# Web URL endpoint for served static files
STATIC_URL = '/static/'
# Set the latex interpreter in the config.yaml settings file
LATEX_INTERPRETER = LATEX_SETTINGS.get('interpreter', 'pdflatex')
# The filesystem location for served static files
STATIC_ROOT = os.path.abspath(CONFIG.get('static_root', os.path.join(BASE_DIR, 'static')))
LATEX_INTERPRETER_OPTIONS = LATEX_SETTINGS.get('options', '')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'InvenTree', 'static'),
LATEX_GRAPHICSPATH = [
# Allow LaTeX files to access the report assets directory
os.path.join(MEDIA_ROOT, "report", "assets"),
]
# 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'
@@ -351,3 +388,8 @@ DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage'
DBBACKUP_STORAGE_OPTIONS = {
'location': CONFIG.get('backup_dir', tempfile.gettempdir()),
}
# Internal IP addresses allowed to see the debug toolbar
INTERNAL_IPS = [
'127.0.0.1',
]

View File

@@ -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)

View File

@@ -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"
}
}

View 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)

View File

@@ -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)

View File

@@ -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"
}
}

View 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)

View File

@@ -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"
}
}

View 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);

View File

@@ -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"
}
}

View 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);

View File

@@ -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"
}
}

View 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)

View File

@@ -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"
}
}

View 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 === '&nbsp;' ? 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)

View File

@@ -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"
}
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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"
}
}

View File

@@ -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;
}

View 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)

View 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;
}

View 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);

View File

@@ -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"
}
}

View 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,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/FRgAAAHFJREFUeNpi/P//PwMlgImBQsA44C6gvhfa29v3MzAwOODRc6CystIRbxi0t7fjDJjKykpGYrwwi1hxnLHQ3t7+jIGBQRJJ6HllZaUUKYEYRYBPOB0gBShKwKGA////48VtbW3/8clTnBIH3gCKkzJgAGvBX0dDm0sCAAAAAElFTkSuQmCC);
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,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);
}
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,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);
}

View 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);

View File

@@ -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"
}
}

View File

@@ -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);

View File

@@ -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"
}
}

View 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);

View File

@@ -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"
}
}

View 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);

View File

@@ -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"
}
}

View File

@@ -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);

View File

@@ -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"
}
}

View File

@@ -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);

View File

@@ -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"
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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"
}
}

View 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">&times;</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);

View File

@@ -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"
}
}

View File

@@ -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);
}

View File

@@ -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"
}
}

View File

@@ -0,0 +1,8 @@
.jumpto input {
height: 31px;
width: 50px;
margin-left: 5px;
margin-right: 5px;
text-align: center;
display: inline-block;
}

View File

@@ -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);

View File

@@ -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.

View File

@@ -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)

View 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);

View File

@@ -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": ""
}
}

View 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);

View File

@@ -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);

View File

@@ -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"
}
}

View File

@@ -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;
}

View 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);

View File

@@ -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"
}
}

View 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);

View File

@@ -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"
}
}

View File

@@ -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);

View File

@@ -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"
}
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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"
}
}

View 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">&times;</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">&times;</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)

View File

@@ -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"
}
}

View File

@@ -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}

View 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);

View File

@@ -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;
}
}

View File

@@ -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

View 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);

View File

@@ -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"
}
}

File diff suppressed because it is too large Load Diff

View 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;
}

View File

@@ -0,0 +1 @@
/* Color Theme: "Default" */

View File

@@ -82,12 +82,26 @@
float: left;
}
#navbar-barcode-li {
border-left: none;
border-right: none;
padding-right: 5px;
}
.navbar-nav > li {
border-left: 1px solid;
border-right: 1px solid;
border-color: #eee;
}
.navbar-form {
padding-right: 3px;
}
#barcode-scan {
margin-top: 8px;
}
.icon-header {
margin-right: 10px;
}
@@ -259,6 +273,16 @@
margin-left: 1px;
}
.dropdown-buttons {
display: inline-block
}
.dropdown-menu .open{
z-index: 1000;
position: relative;
overflow: visible;
}
/* Styles for table buttons and filtering */
.button-toolbar .btn {
margin-left: 1px;
@@ -390,6 +414,10 @@
font-size: 125%;
}
.btn-group {
padding-bottom: 5px;
}
.action-buttons .btn {
font-size: 175%;
align-content: center;
@@ -451,10 +479,12 @@
.media {
padding-top: 15px;
overflow: visible;
}
.media-body {
padding-top: 10px;
overflow: visible;
}
.navigation {

View File

@@ -108,6 +108,25 @@ function inventreePut(url, data={}, options={}) {
if (options.error) {
options.error(xhr, ajaxOptions, thrownError);
}
},
complete: function(xhr, status) {
if (options.complete) {
options.complete(xhr, status);
}
}
});
}
function inventreeDelete(url, options={}) {
/*
* Delete a record
*/
options = options || {};
options.method = 'DELETE';
inventreePut(url, {}, options);
}

View File

@@ -14,7 +14,7 @@
function defaultFilters() {
return {
stock: "cascade=1",
stock: "cascade=1&in_stock=1",
build: "",
parts: "cascade=1",
company: "",
@@ -272,8 +272,9 @@ function setupFilterList(tableKey, table, target) {
for (var key in filters) {
var value = getFilterOptionValue(tableKey, key, filters[key]);
var title = getFilterTitle(tableKey, key);
var description = getFilterDescription(tableKey, key);
element.append(`<div class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`);
element.append(`<div title='${description}' class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`);
}
// Add a callback for adding a new filter
@@ -362,6 +363,15 @@ function getFilterTitle(tableKey, filterKey) {
}
/**
* Return the pretty description for the given table and filter selection
*/
function getFilterDescription(tableKey, filterKey) {
var settings = getFilterSettings(tableKey, filterKey);
return settings.title;
}
/*
* Return a description for the given table and filter selection.
*/

View File

@@ -1,7 +1,136 @@
function makeOption(id, title) {
function makeOption(text, value, title) {
/* Format an option for a select element
*/
return "<option value='" + id + "'>" + title + "</option>";
var html = `<option value='${value || text}'`;
if (title) {
html += ` title='${title}'`;
}
html += `>${text}</option>`;
return html;
}
function makeOptionsList(elements, textFunc, valueFunc, titleFunc) {
/*
* Programatically generate a list of <option> elements,
* from the (assumed array) of elements.
* For each element, we pass the element to the supplied functions,
* which (in turn) generate display / value / title values.
*
* Args:
* - elements: List of elements
* - textFunc: Function which takes an element and generates the text to be displayed
* - valueFunc: optional function which takes an element and generates the value
* - titleFunc: optional function which takes an element and generates a title
*/
var options = [];
elements.forEach(function(element) {
var text = textFunc(element);
var value = null;
var title = null;
if (valueFunc) {
value = valueFunc(element);
} else {
value = text;
}
if (titleFunc) {
title = titleFunc(element);
}
options.push(makeOption(text, value, title));
});
return options;
}
function setFieldOptions(fieldName, optionList, options={}) {
/* Set the options for a <select> field.
*
* Args:
* - fieldName: The name of the target field
* - Options: List of formatted <option> strings
* - append: If true, options will be appended, otherwise will replace existing options.
*/
var append = options.append || false;
var modal = options.modal || '#modal-form';
var field = getFieldByName(modal, fieldName);
var addEmptyOption = options.addEmptyOption || true;
// If not appending, clear out the field...
if (!append) {
field.find('option').remove();
}
if (addEmptyOption) {
// Add an 'empty' option at the top of the list
field.append(`<option value="">---------</option>`);
}
optionList.forEach(function(option) {
field.append(option);
});
}
function reloadFieldOptions(fieldName, options) {
/* Reload the options for a given field,
* using an AJAX request.
*
* Args:
* - fieldName: The name of the field
* - options:
* -- url: Query url
* -- params: Query params
* -- value: A function which takes a returned option and returns the 'value' (if not specified, the `pk` field is used)
* -- text: A function which takes a returned option and returns the 'text'
* -- title: A function which takes a returned option and returns the 'title' (optional!)
*/
inventreeGet(options.url, options.params, {
success: function(response) {
var opts = makeOptionsList(response,
function(item) {
return options.text(item);
},
function(item) {
if (options.value) {
return options.value(item);
} else {
// Fallback is to use the 'pk' field
return item.pk;
}
},
function(item) {
if (options.title) {
return options.title(item);
} else {
return null;
}
}
);
// Update the target field with the new options
setFieldOptions(fieldName, opts);
},
error: function(response) {
console.log("Error GETting field options");
}
});
}
@@ -166,14 +295,28 @@ function modalSetContent(modal, content='') {
}
function modalSetSubmitText(modal, text) {
if (text) {
$(modal).find('#modal-form-submit').html(text);
}
}
function modalSetCloseText(modal, text) {
if (text) {
$(modal).find('#modal-form-close').html(text);
}
}
function modalSetButtonText(modal, submit_text, close_text) {
/* Set the button text for a modal form
*
* submit_text - text for the form submit button
* close_text - text for the form dismiss button
*/
$(modal).find("#modal-form-submit").html(submit_text);
$(modal).find("#modal-form-close").html(close_text);
modalSetSubmitText(modal, submit_text);
modalSetCloseText(modal, close_text);
}
@@ -326,6 +469,9 @@ function openModal(options) {
$(modal).on('shown.bs.modal', function() {
$(modal + ' .modal-form-content').scrollTop(0);
if (options.focus) {
getFieldByName(modal, options.focus).focus();
}
});
// Prevent 'enter' key from submitting the form using the normal method
@@ -383,6 +529,13 @@ function injectModalForm(modal, form_html) {
}
function getFieldByName(modal, name) {
/* Find the field (with the given name) within the modal */
return $(modal).find(`#id_${name}`);
}
function insertNewItemButton(modal, options) {
/* Insert a button into a modal form, after a field label.
* Looks for a <label> tag inside the form with the attribute "for='id_<field>'"
@@ -442,7 +595,8 @@ function attachSecondaryModal(modal, options) {
*/
var select = '#id_' + options.field;
var option = new Option(response.text, response.pk, true, true)
var option = new Option(response.text, response.pk, true, true);
$(modal).find(select).append(option).trigger('change');
}
@@ -461,6 +615,39 @@ function attachSecondaries(modal, secondaries) {
}
function attachFieldCallback(modal, callback) {
/* Attach a 'callback' function to a given field in the modal form.
* When the value of that field is changed, the callback function is performed.
*
* options:
* - field: The name of the field to attach to
* - action: A function to perform
*/
// Find the field input in the form
var field = getFieldByName(modal, callback.field);
field.change(function() {
if (callback.action) {
// Run the callback function with the new value of the field!
callback.action(field.val(), field);
} else {
console.log(`Value changed for field ${callback.field} - ${field.val()}`);
}
});
}
function attachCallbacks(modal, callbacks) {
/* Attach a provided list of callback functions */
for (var i = 0; i < callbacks.length; i++) {
attachFieldCallback(modal, callbacks[i]);
}
}
function handleModalForm(url, options) {
/* Update a modal form after data are received from the server.
* Manages POST requests until the form is successfully submitted.
@@ -560,6 +747,8 @@ function launchModalForm(url, options = {}) {
* no_post - If true, only display form data, hide submit button, and disallow POST
* after_render - Callback function to run after form is rendered
* secondary - List of secondary modals to attach
* callback - List of callback functions to attach to inputs
* focus - Select which field to focus on by default
*/
var modal = options.modal || '#modal-form';
@@ -578,6 +767,7 @@ function launchModalForm(url, options = {}) {
modal: modal,
submit_text: submit_text,
close_text: close_text,
focus: options.focus
});
},
success: function(response) {
@@ -600,6 +790,10 @@ function launchModalForm(url, options = {}) {
attachSecondaries(modal, options.secondary);
}
if (options.callback) {
attachCallbacks(modal, options.callback);
}
if (options.no_post) {
modalShowSubmitButton(modal, false);
} else {

View File

@@ -80,11 +80,28 @@ function reloadTable(table, filters) {
}
function visibleColumnString(columns) {
/* Generate a list of "visible" columns to save to file. */
var fields = [];
columns.forEach(function(column) {
if (column.switchable && column.visible) {
fields.push(column.field);
}
});
return fields.join(',');
}
/* Wrapper function for bootstrapTable.
* Sets some useful defaults, and manage persistent settings.
*/
$.fn.inventreeTable = function(options) {
var table = this;
var tableName = options.name || 'table';
var varName = tableName + '-pagesize';
@@ -95,20 +112,60 @@ $.fn.inventreeTable = function(options) {
options.rememberOrder = true;
options.sortable = true;
options.search = true;
options.showColumns = true;
// Callback to save pagination data
options.onPageChange = function(number, size) {
inventreeSave(varName, size);
};
// Callback when a column is changed
options.onColumnSwitch = function(field, checked) {
console.log(`${field} -> ${checked}`);
var columns = table.bootstrapTable('getVisibleColumns');
var text = visibleColumnString(columns);
// Save visible columns
inventreeSave(`table_columns_${tableName}`, text);
};
// Standard options for all tables
this.bootstrapTable(options);
table.bootstrapTable(options);
// Load visible column list from memory
// Load visible column list
var visibleColumns = inventreeLoad(`table_columns_${tableName}`, null);
// If a set of visible columns has been saved, load!
if (visibleColumns) {
var columns = visibleColumns.split(",");
// Which columns are currently visible?
var visible = table.bootstrapTable('getVisibleColumns');
if (visible) {
visible.forEach(function(column) {
// Visible field should *not* be visible! (hide it!)
if (column.switchable && !columns.includes(column.field)) {
table.bootstrapTable('hideColumn', column.field);
}
});
} else {
console.log('Could not get list of visible columns!');
}
}
// Optionally, link buttons to the table selection
if (options.buttons) {
linkButtonsToSelection(table, options.buttons);
}
}
function customGroupSorter(sortName, sortOrder, sortData) {
console.log('got here');
var order = sortOrder === 'desc' ? -1 : 1;
sortData.sort(function(a, b) {

View File

@@ -0,0 +1,3 @@
/nbproject
/node_modules
/bower_modules

View File

@@ -0,0 +1,43 @@
== 0.3.0 - 03 February 2015
* Perfomance optimization
* Bootstrap 3 update
* Add minifiyed js
* Add npm installation
* Add borew installation
== 0.2.1 - 21 January 2014
* Add events support
== 0.1.9 - 18 December 2013
* Fix table in table bug (thanks liuspcn)
== 0.1.8 - 16 December 2013
* Fix support alphanumeric id (you can use "-" symbol in id)
== 0.1.7 - 15 November 2013
* Fix isLast method. https://github.com/maxazan/jquery-treegrid/issues/4
* Add isFirst method
== 0.1.6 - 14 November 2013
* Add isNode method
* Add getAllNodes method
* Fix isLast method. Throw error if method is not node of tree.
== 0.1.5
* Add support alphanumeric id
== 0.1.4
* Add treeRow parameter
== 0.1.3 - 28 August 2013
* Added bootstrap examples
* Changed expander functionality
* Fixed $.data set and get
* Fixed $.extends settings
* Adding new test
== 0.1.2 - 26 August 2013
* Added save state
== 0.1.0 - 15 August 2013
* Public release

View File

@@ -0,0 +1,23 @@
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= pkg.version %> */\n'
},
build: {
src: 'js/jquery.treegrid.js',
dest: 'js/jquery.treegrid.min.js'
}
}
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-uglify');
// Default task(s).
grunt.registerTask('default', ['uglify']);
};

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013
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.

View File

@@ -0,0 +1,21 @@
TreeGrid plugin for jQuery
==========
See more information at http://maxazan.github.io/jquery-treegrid
## Installation
### Using npm
```
npm install jquery-treegrid
```
### Using bower
```
bower install jquery-treegrid
```
### From source
Download [source](https://github.com/maxazan/jquery-treegrid/archive/master.zip) from github.com

View File

@@ -0,0 +1,33 @@
{
"name": "jquery-treegrid",
"description": "TreeGrid plugin for jQuery.",
"version": "0.3.0",
"homepage": "http://maxazan.github.io/jquery-treegrid",
"license": "MIT",
"ignore": [
"examples",
"tests",
"**/.*",
"composer.json",
"index.html",
"params.json",
"style.css",
"test.html",
"treegrid.jquery.json"
],
"keywords": [
"jquery",
"javascript",
"plugin",
"bootstrap",
"treegrid",
"treeview"
],
"dependencies": {
"jquery": ">= 1.9.1",
"jquery.cookie": ">= 1.4.1"
},
"authors": [
"Maksym Pomazan"
]
}

View File

@@ -0,0 +1,12 @@
{
"name": "maxazan/jquery-treegrid",
"description": "jquery treegrid plugin",
"keywords": ["jquery", "grid", "treegrid", "tree"],
"homepage": "http://maxazan.github.io/jquery-treegrid",
"type": "library",
"license": "MIT",
"support": {
"issues": "https://github.com/maxazan/jquery-treegrid/issues",
"source": "https://github.com/maxazan/jquery-treegrid"
}
}

View File

@@ -0,0 +1,6 @@
.treegrid-indent {width:16px; height: 16px; display: inline-block; position: relative;}
.treegrid-expander {width:16px; height: 16px; display: inline-block; position: relative; cursor: pointer;}
.treegrid-expander-expanded{background-image: url(../img/collapse.png); }
.treegrid-expander-collapsed{background-image: url(../img/expand.png);}

Some files were not shown because too many files have changed in this diff Show More