diff --git a/CHANGELOG.md b/CHANGELOG.md index e5ac42b..7bbaf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,32 @@ # Changelog +## Unreleased + +### Enhancements & Features +- Add `defaultRowCount` property to set default row count from rowCount array +- Add `sort` event; triggered when column is sorted +- Add `searchSettings.highlightResults` option to wrap matching substrings that match the query in result set + - Add `highlightResults` css class to options + - Add `highlightResults` template which wraps the matched substring + +## 1.4.3 + +### Enhancements & Features +- Updated search to pass row, column, and grid to converter +- Add derived cell support with a derived formatter + - Adds additional `sortKey` parameter to determine which reference should be considered for sorting +- Add `sortRendered` parameter that uses the rendered cell value as the comparator + - Useful is situations where the formatter expects values other than what you want to sort on +- Add `derived` formatter and converter to defaults + ## 1.4.2 ### Bug Fixes - Fixed bug with showing/hiding columns +### Enhancements & Features +- Added options for search to include hidden columns + ## 1.4.1 ### Bug Fixes diff --git a/bower.json b/bower.json index 600d5f1..45fbf86 100644 --- a/bower.json +++ b/bower.json @@ -13,7 +13,7 @@ "accessibility", "bootstrap" ], - "version": "1.4.1", + "version": "1.4.2", "authors": [ { "name": "Rafael Staib", diff --git a/demo/ajax.htm b/demo/ajax.htm new file mode 100644 index 0000000..befe92a --- /dev/null +++ b/demo/ajax.htm @@ -0,0 +1,118 @@ + + + + + + jQuery Bootgrid Demo + + + + + + + + +
+
+
+
+ + +
+
+
+ + + + + + + + +
IDSenderReceived
+
+
+
+ + + + + + + + + diff --git a/demo/append.html b/demo/append.html new file mode 100644 index 0000000..be57c0c --- /dev/null +++ b/demo/append.html @@ -0,0 +1,157 @@ + + + + + + jQuery Bootgrid Demo + + + + + + + + +
+
+
+
+ Sub Nav +
+
+
+ + + + + + + + + + + + + + + +
ID
1111
2222
3333
11111
+ +
+
+
+ + + + + + + + + + diff --git a/demo/data.json b/demo/data.json deleted file mode 100644 index b177807..0000000 --- a/demo/data.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "current": 1, - "rowCount": 5, - "rows": [ - { - "id": "a0e3a286-4343-4240-8d6d-e79fa2e94b4c", - "sender": "test@test.de", - "received": "2014-04-17 15:08:03Z" - }, - { - "id": "dd9f2d42-9442-404c-8d2a-dd3bd2156c03", - "sender": "test@test.de", - "received": "2014-04-16 15:19:31Z" - }, - { - "id": "e9b8ede5-c1bf-4d90-b724-e7379b25f7b3", - "sender": "test@test.de", - "received": "2014-04-16 15:17:05Z" - }, - { - "id": "153d3acb-efe7-4b5f-a3a9-e8ac18bdec30", - "sender": "test@test.de", - "received": "2014-04-16 15:17:05Z" - }, - { - "id": "49bad60a-bbf7-42bf-b040-d901805ccbf1", - "sender": "test@test.de", - "received": "2014-04-15 11:23:06Z" - } - ], - "total": 5 -} \ No newline at end of file diff --git a/demo/data1.json b/demo/data1.json new file mode 100644 index 0000000..e33e8aa --- /dev/null +++ b/demo/data1.json @@ -0,0 +1,26 @@ +{ + "current": 1, + "rowCount": 5, + "rows": [{ + "id": "a0e3a286-4343-4240-8d6d-e79fa2e94b4c", + "sender": "test@test.de", + "received": "2014-04-17 15:08:03Z" + }, { + "id": "dd9f2d42-9442-404c-8d2a-dd3bd2156c03", + "sender": "test@test.de", + "received": "2014-04-16 15:19:31Z" + }, { + "id": "e9b8ede5-c1bf-4d90-b724-e7379b25f7b3", + "sender": "test@test.de", + "received": "2014-04-16 15:17:05Z" + }, { + "id": "153d3acb-efe7-4b5f-a3a9-e8ac18bdec30", + "sender": "test@test.de", + "received": "2014-04-16 15:17:05Z" + }, { + "id": "49bad60a-bbf7-42bf-b040-d901805ccbf1", + "sender": "test@test.de", + "received": "2014-04-15 11:23:06Z" + }], + "total": 10 +} diff --git a/demo/data2.json b/demo/data2.json new file mode 100644 index 0000000..35cfbc2 --- /dev/null +++ b/demo/data2.json @@ -0,0 +1,26 @@ +{ + "current": 2, + "rowCount": 5, + "rows": [{ + "id": "V5e3a286-4343-4240-8d6d-e79fa2e94b4c", + "sender": "test@test.de", + "received": "2014-04-17 15:08:03Z" + }, { + "id": "pp9f2d42-9442-404c-8d2a-dd3bd2156c03", + "sender": "test@test.de", + "received": "2014-04-16 15:19:31Z" + }, { + "id": "p7b8ede5-c1bf-4d90-b724-e7379b25f7b3", + "sender": "test@test.de", + "received": "2014-04-16 15:17:05Z" + }, { + "id": "553d3acb-efe7-4b5f-a3a9-e8ac18bdec30", + "sender": "test@test.de", + "received": "2014-04-16 15:17:05Z" + }, { + "id": "34bad60a-bbf7-42bf-b040-d901805ccbf1", + "sender": "test@test.de", + "received": "2014-04-15 11:23:06Z" + }], + "total": 10 +} diff --git a/demo/dataFunc.htm b/demo/dataFunc.htm new file mode 100644 index 0000000..4731695 --- /dev/null +++ b/demo/dataFunc.htm @@ -0,0 +1,137 @@ + + + + + + jQuery Bootgrid Demo + + + + + + + + +
+
+
+
+ Sub Nav +
+
+
+ + + + + + + + +
IDSenderReceived
+
+
+
+ + + + + + + + + diff --git a/demo/derived.htm b/demo/derived.htm new file mode 100644 index 0000000..a1fc66c --- /dev/null +++ b/demo/derived.htm @@ -0,0 +1,284 @@ + + + + + + jQuery Bootgrid Demo + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDNameSenderReceivedCompanyCostDerivedHidden
1Aimee Delaney03.06.16Maecenas Malesuada Foundation222222C9F35176-EE9C-7DA5-DB47-5B62C57AA176
2{"text": "BORIS BULE", "url": "http://www.zzz.ca"}vitae.diam@Proinultrices.edu10.12.16Ac Corporation111111111BC83AA2-0979-C426-A3B4-4CC4F8B0CFF9
3{"text": "ALEX Reese", "url": "http://www.ccc.com"}mauris.sit.amet@nonmassa.ca27.06.17Nulla At Incorporated333333333C3CF97AE-0E0E-EA4E-FBF5-A63444134B8E
4{"text": "Juliet Mcfarland", "url": "http://www.eee.com"}egestas.Duis.ac@eu.net02.07.16Nisi Sem Semper Limited55555533AC322B-8AB4-B9F0-FAF0-BD69A7937627
5{"url": "http://www.ddd.com", "text": "Aaron Weaver"}Curabitur@fermentumconvallisligula.org07.06.17Magna Nec Quam Industries4444444235B6F73-2150-A423-DC0A-A75C74669824
+ +
+
+
+ + + + + + + + + + diff --git a/demo/index.htm b/demo/index.htm index be96906..a35686e 100644 --- a/demo/index.htm +++ b/demo/index.htm @@ -1,556 +1,564 @@ - - - - jQuery Bootgrid Demo - - - - - - - + + + -
-
-
-
- Sub Nav -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IDNameSenderReceivedLinkStatusHidden
1Aimee Delaneyerat.vitae@estvitaesodales.com03.06.16Maecenas Malesuada Foundation1C9F35176-EE9C-7DA5-DB47-5B62C57AA176
2BORIS BULEvitae.diam@Proinultrices.edu10.12.16Ac Corporation31BC83AA2-0979-C426-A3B4-4CC4F8B0CFF9
3ALEX Reesemauris.sit.amet@nonmassa.ca27.06.17Nulla At Incorporated5C3CF97AE-0E0E-EA4E-FBF5-A63444134B8E
4Juliet Mcfarlandegestas.Duis.ac@eu.net02.07.16Nisi Sem Semper Limited333AC322B-8AB4-B9F0-FAF0-BD69A7937627
5Lester WeaverCurabitur@fermentumconvallisligula.org07.06.17Magna Nec Quam Industries2235B6F73-2150-A423-DC0A-A75C74669824
6Aaron HendersonIn@volutpat.co.uk29.01.16Nec Tellus Incorporated1284D00C0-6A24-E9B3-E462-3A3892BA7EF1
7Sloane CraftFusce.feugiat.Lorem@tellusAeneanegestas.co.uk02.02.17Non Sollicitudin Institute1BC72D4C2-C600-44CF-D8CE-A3A0C8C98224
8Mufutau CookeSed@tempusnonlacinia.ca02.05.16Nascetur Ridiculus Mus Company11DF470B0-15B8-44C2-0991-A15F13EBE087
9Brooke MaloneAliquam.adipiscing@posuere.edu27.06.16Ut Eros Inc.47346FBE9-4AC2-ECD4-96D8-75BF2B20E021
10Rooney ClineProin.ultrices.Duis@eu.com29.08.16Mauris Integer Inc.287412B97-5862-AB2C-EB92-F7692EB0C17A
11Alexander VelasquezPraesent.interdum@infaucibusorci.org14.01.17Luctus Et Limited2E4DAE99C-6078-A89E-210A-C21487A90361
12Stone SosaUt@turpis.com18.05.16Orci Luctus Et Foundation444723C37-B0D3-FBE6-E1A2-6EA3E13388E3
13Graham Harveynulla.Integer@Phasellus.edu02.03.17Nibh Donec Est Consulting1AA3B3771-D300-8411-1167-AC7AEA0995B2
14Lance Oliverelit.sed.consequat@mollis.com19.03.16Dignissim Consulting4D8241C7E-4B13-7A24-DD90-CFAC74C44E40
15Cassady Brightlibero.dui.nec@dis.org24.12.16Nulla Consulting16A4BB223-3FC0-242F-CDBD-341E014BA7CD
16Brett Sloanligula.Donec.luctus@scelerisquescelerisque.co.uk25.05.17Velit Pellentesque LLP378A0AC6A-55CE-E7F9-9E8D-F9638A067FC1
17Cheyenne Herringmolestie.pharetra@egetmetus.ca17.09.16Egestas Foundation437D88706-931A-25EF-ABEB-D65F3D72BB8A
182porttitor.interdum@pharetra.edu19.06.16Elit Nulla Facilisi Inc.5C919D2B9-63E6-BF6D-6639-F058F0141D27
1911nunc.sed.pede@lectusrutrum.edu15.11.15Leo Morbi Incorporated2AA9836DC-8FA0-DCE3-12F0-78ABD416279C
2001nulla.magna.malesuada@musProin.edu30.01.17Ac Urna Corp.2F51F8323-42AB-5E84-091D-F02B5E1E04E3
21Martin SharpeCras@Suspendissenon.com06.10.15Etiam Imperdiet Dictum LLP47EDF8E88-70D2-4A80-AEF6-60661DF2ECA9
22Ray OconnorFusce.aliquam.enim@tristiquepharetraQuisque.edu03.11.16Consectetuer Cursus Company3334C5153-2E2C-EB0A-C42C-CF221CCD6674
23Yetta Beckerdictum.placerat@in.org24.12.16Sed Company489877E4F-8A15-5CAE-75DA-278F27D0B49F
24Ishmael Wileyarcu@ornare.edu14.08.16Velit Institute438FB6436-85EB-9DE4-4A77-17B20941C627
25Omar Mckinneya.dui.Cras@Integeraliquamadipiscing.edu03.02.17Quis Massa Ltd20619ED7E-4295-962D-A068-99AC32D0D088
26Lucy SmallMaecenas.iaculis.aliquet@diamDuismi.co.uk13.03.17Dolor Dapibus Gravida Associates45322357B-864C-9D07-6220-551095B9E85A
27Davis Hensonmus.Aenean@enim.net26.08.16Elementum Corp.5F8403A4B-1604-1798-7948-1B6C2E48D386
28Ralph Travistristique.pellentesque.tellus@vulputatedui.ca16.08.15Posuere Cubilia Curae; LLP48081C1C0-5EF3-C7FF-7307-2E59683D86E0
29Wayne HamptonNam.interdum@Proin.ca19.01.17Dictum Placerat Augue Foundation51A7276D5-A929-72E5-A397-F3AB7AE0EEBC
30Priscilla Robersonmagna.Nam.ligula@lectus.com26.08.16Tortor At Industries22AFA1C2B-CDD0-84E7-8535-DC8551AE9945
31Garth GreeneCum.sociis.natoque@lacusUt.org04.08.16Tincidunt Inc.54580886C-89E0-8FEE-CEFD-ED87FAC5FDA2
32Erasmus Hamptonnunc@non.net08.10.16Dui Nec Urna PC4C1B98F7D-0554-7719-697D-754B1711F76C
33Doris MaynardDonec.egestas.Aliquam@dolorsitamet.com11.07.17Est Arcu PC1C039DE61-4AFA-1B40-1074-5DD823625E00
34Margaret Gonzalezpellentesque.tellus.sem@mi.ca19.11.16A Associates294C687D6-0085-D04C-1D16-7945411E9E3A
35Kylan WattsCras@atnisiCum.com17.07.16Rutrum Consulting18CF021E9-AEE6-79DE-A368-BBE456153B5A
36Ursula Lewistempus@sitametconsectetuer.net16.09.16Parturient Montes Consulting4EC79724F-9632-7C26-ED36-60737A57E57A
37Vance AdkinsCurabitur.egestas.nunc@Sed.ca17.12.16Tristique Pharetra Company10720A4E2-19D4-5023-4E56-659C63B3D655
38Steven Frankviverra@lectus.co.uk15.10.15Aliquam Gravida Mauris Foundation2C73C063E-68AF-BA1B-F318-B5A1F8D98E27
39Rhoda Mccartyelit.dictum@sollicitudincommodo.com24.08.15Pellentesque Inc.461308679-75BF-06BA-AA56-A29DF911537D
40Dara Hudsonac.feugiat.non@neque.ca05.09.15Sit Consulting18B1634F8-05AA-CBBD-B7A7-EC9F3CE97AC3
- +
+
+
+
+ Sub Nav
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDNameSenderReceivedLinkStatusHidden
1Aimee Delaneyerat.vitae@estvitaesodales.com03.06.16Maecenas Malesuada Foundation1C9F35176-EE9C-7DA5-DB47-5B62C57AA176
2BORIS BULEvitae.diam@Proinultrices.edu10.12.16Ac Corporation31BC83AA2-0979-C426-A3B4-4CC4F8B0CFF9
3ALEX Reesemauris.sit.amet@nonmassa.ca27.06.17Nulla At Incorporated5C3CF97AE-0E0E-EA4E-FBF5-A63444134B8E
4Juliet Mcfarlandegestas.Duis.ac@eu.net02.07.16Nisi Sem Semper Limited333AC322B-8AB4-B9F0-FAF0-BD69A7937627
5Lester WeaverCurabitur@fermentumconvallisligula.org07.06.17Magna Nec Quam Industries2235B6F73-2150-A423-DC0A-A75C74669824
6Aaron HendersonIn@volutpat.co.uk29.01.16Nec Tellus Incorporated1284D00C0-6A24-E9B3-E462-3A3892BA7EF1
7Sloane CraftFusce.feugiat.Lorem@tellusAeneanegestas.co.uk02.02.17Non Sollicitudin Institute1BC72D4C2-C600-44CF-D8CE-A3A0C8C98224
8Mufutau CookeSed@tempusnonlacinia.ca02.05.16Nascetur Ridiculus Mus Company11DF470B0-15B8-44C2-0991-A15F13EBE087
9Brooke MaloneAliquam.adipiscing@posuere.edu27.06.16Ut Eros Inc.47346FBE9-4AC2-ECD4-96D8-75BF2B20E021
10Rooney ClineProin.ultrices.Duis@eu.com29.08.16Mauris Integer Inc.287412B97-5862-AB2C-EB92-F7692EB0C17A
11Alexander VelasquezPraesent.interdum@infaucibusorci.org14.01.17Luctus Et Limited2E4DAE99C-6078-A89E-210A-C21487A90361
12Stone SosaUt@turpis.com18.05.16Orci Luctus Et Foundation444723C37-B0D3-FBE6-E1A2-6EA3E13388E3
13Graham Harveynulla.Integer@Phasellus.edu02.03.17Nibh Donec Est Consulting1AA3B3771-D300-8411-1167-AC7AEA0995B2
14Lance Oliverelit.sed.consequat@mollis.com19.03.16Dignissim Consulting4D8241C7E-4B13-7A24-DD90-CFAC74C44E40
15Cassady Brightlibero.dui.nec@dis.org24.12.16Nulla Consulting16A4BB223-3FC0-242F-CDBD-341E014BA7CD
16Brett Sloanligula.Donec.luctus@scelerisquescelerisque.co.uk25.05.17Velit Pellentesque LLP378A0AC6A-55CE-E7F9-9E8D-F9638A067FC1
17Cheyenne Herringmolestie.pharetra@egetmetus.ca17.09.16Egestas Foundation437D88706-931A-25EF-ABEB-D65F3D72BB8A
182porttitor.interdum@pharetra.edu19.06.16Elit Nulla Facilisi Inc.5C919D2B9-63E6-BF6D-6639-F058F0141D27
1911nunc.sed.pede@lectusrutrum.edu15.11.15Leo Morbi Incorporated2AA9836DC-8FA0-DCE3-12F0-78ABD416279C
2001[nulla.magna.malesuada@musProin.edu30.01.17Ac Urna Corp.2F51F8323-42AB-5E84-091D-F02B5E1E04E3
21Martin SharpeCras@Suspendissenon.com06.10.15Etiam Imperdiet Dictum LLP47EDF8E88-70D2-4A80-AEF6-60661DF2ECA9
22Ray OconnorFusce.aliquam.enim@tristiquepharetraQuisque.edu03.11.16Consectetuer Cursus Company3334C5153-2E2C-EB0A-C42C-CF221CCD6674
23Yetta Beckerdictum.placerat@in.org24.12.16Sed Company489877E4F-8A15-5CAE-75DA-278F27D0B49F
24Ishmael Wileyarcu@ornare.edu14.08.16Velit Institute438FB6436-85EB-9DE4-4A77-17B20941C627
25Omar Mckinneya.dui.Cras@Integeraliquamadipiscing.edu03.02.17Quis Massa Ltd20619ED7E-4295-962D-A068-99AC32D0D088
26Lucy SmallMaecenas.iaculis.aliquet@diamDuismi.co.uk13.03.17Dolor Dapibus Gravida Associates45322357B-864C-9D07-6220-551095B9E85A
27Davis Hensonmus.Aenean@enim.net26.08.16Elementum Corp.5F8403A4B-1604-1798-7948-1B6C2E48D386
28Ralph Travistristique.pellentesque.tellus@vulputatedui.ca16.08.15Posuere Cubilia Curae; LLP48081C1C0-5EF3-C7FF-7307-2E59683D86E0
29Wayne HamptonNam.interdum@Proin.ca19.01.17Dictum Placerat Augue Foundation51A7276D5-A929-72E5-A397-F3AB7AE0EEBC
30Priscilla Robersonmagna.Nam.ligula@lectus.com26.08.16Tortor At Industries22AFA1C2B-CDD0-84E7-8535-DC8551AE9945
31Garth GreeneCum.sociis.natoque@lacusUt.org04.08.16Tincidunt Inc.54580886C-89E0-8FEE-CEFD-ED87FAC5FDA2
32Erasmus Hamptonnunc@non.net08.10.16Dui Nec Urna PC4C1B98F7D-0554-7719-697D-754B1711F76C
33Doris MaynardDonec.egestas.Aliquam@dolorsitamet.com11.07.17Est Arcu PC1C039DE61-4AFA-1B40-1074-5DD823625E00
34Margaret Gonzalezpellentesque.tellus.sem@mi.ca19.11.16A Associates294C687D6-0085-D04C-1D16-7945411E9E3A
35Kylan WattsCras@atnisiCum.com17.07.16Rutrum Consulting18CF021E9-AEE6-79DE-A368-BBE456153B5A
36Ursula Lewistempus@sitametconsectetuer.net16.09.16Parturient Montes Consulting4EC79724F-9632-7C26-ED36-60737A57E57A
37Vance AdkinsCurabitur.egestas.nunc@Sed.ca17.12.16Tristique Pharetra Company10720A4E2-19D4-5023-4E56-659C63B3D655
38Steven Frankviverra@lectus.co.uk15.10.15Aliquam Gravida Mauris Foundation2C73C063E-68AF-BA1B-F318-B5A1F8D98E27
39Rhoda Mccartyelit.dictum@sollicitudincommodo.com24.08.15Pellentesque Inc.461308679-75BF-06BA-AA56-A29DF911537D
40Dara Hudsonac.feugiat.non@neque.ca05.09.15Sit Consulting18B1634F8-05AA-CBBD-B7A7-EC9F3CE97AC3
+ +
+
-
- © Copyright 2014-2015, Rafael Staib -
- - - - - - + + + + - + + $("#getSelectedRows").on("click", function() { + alert($("#grid").bootgrid("getSelectedRows")); + }); + }); + + + diff --git a/demo/server.js b/demo/server.js new file mode 100644 index 0000000..4fd220e --- /dev/null +++ b/demo/server.js @@ -0,0 +1,74 @@ +'use strict'; + +const Hapi = require('hapi'); + +const server = new Hapi.Server(); +server.connection({ port: 8080, host: 'localhost' }); + +server.register(require('inert'), (err) => { + + server.route({ + method: 'GET', + path: '/demo/{file}', + handler: function (request, reply) { + reply.file('./demo/' + encodeURIComponent(request.params.file)); + } + }); + + server.route({ + method: 'GET', + path: '/data', + handler: function (request, reply) { + reply.file('./demo/data' + request.query.current + '.json'); + } + }); + + server.route({ + method: 'GET', + path: '/demo/css/{name*}', + handler: { + directory: { + path: 'demo/css', + redirectToSlash: true + } + } + }); + server.route({ + method: 'GET', + path: '/dist/{name*}', + handler: { + directory: { + path: 'dist', + redirectToSlash: true + } + } + }); + server.route({ + method: 'GET', + path: '/demo/{name*}', + handler: { + directory: { + path: 'demo/', + redirectToSlash: true + } + } + }); + server.route({ + method: 'GET', + path: '/lib/{name*}', + handler: { + directory: { + path: 'lib/', + redirectToSlash: true + } + } + }); +}); + +server.start((err) => { + + if (err) { + throw err; + } + console.log(`Server running at: ${server.info.uri}`); +}); \ No newline at end of file diff --git a/dist/jquery.bootgrid.css b/dist/jquery.bootgrid.css index 3beaa72..3a95473 100644 --- a/dist/jquery.bootgrid.css +++ b/dist/jquery.bootgrid.css @@ -1,6 +1,6 @@ /*! - * jQuery Bootgrid v1.4.1 - 08/10/2016 - * Copyright (c) 2014-2016 Rafael Staib (http://www.jquery-bootgrid.com) + * jQuery Bootgrid v1.4.2 - 05/24/2018 + * Copyright (c) 2014-2018 Rafael Staib (http://www.jquery-bootgrid.com) * Licensed under MIT http://www.opensource.org/licenses/MIT */ .bootgrid-header, @@ -145,3 +145,6 @@ text-overflow: inherit !important; white-space: inherit !important; } +.bootgrid-search-highlight { + font-weight: bold; +} diff --git a/dist/jquery.bootgrid.fa.js b/dist/jquery.bootgrid.fa.js index 418ba75..45e152d 100644 --- a/dist/jquery.bootgrid.fa.js +++ b/dist/jquery.bootgrid.fa.js @@ -1,6 +1,6 @@ /*! - * jQuery Bootgrid v1.4.1 - 08/10/2016 - * Copyright (c) 2014-2016 Rafael Staib (http://www.jquery-bootgrid.com) + * jQuery Bootgrid v1.4.2 - 05/24/2018 + * Copyright (c) 2014-2018 Rafael Staib (http://www.jquery-bootgrid.com) * Licensed under MIT http://www.opensource.org/licenses/MIT */ ;(function ($, window, undefined) diff --git a/dist/jquery.bootgrid.js b/dist/jquery.bootgrid.js index 0961795..0b815c5 100644 --- a/dist/jquery.bootgrid.js +++ b/dist/jquery.bootgrid.js @@ -1,6 +1,6 @@ /*! - * jQuery Bootgrid v1.4.1 - 08/10/2016 - * Copyright (c) 2014-2016 Rafael Staib (http://www.jquery-bootgrid.com) + * jQuery Bootgrid v1.4.2 - 05/24/2018 + * Copyright (c) 2014-2018 Rafael Staib (http://www.jquery-bootgrid.com) * Licensed under MIT http://www.opensource.org/licenses/MIT */ ;(function ($, window, undefined) @@ -87,6 +87,49 @@ function highlightAppendedRows(rows) { } } +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); +} + +// Replaces all occurrences of the word in the given html +// Original source: http://stackoverflow.com/questions/8503121/replace-words-in-a-string-but-ignore-html +function highlightResults(html) { + var that = this, + word = escapeRegExp(that.searchPhrase), + tpl = this.options.templates, + css = this.options.css, + container = document.createElement("div"), + regexFlag = that.options.caseSensitive ? 'g' : 'gi', + regex = new RegExp('(' + word + ')', regexFlag); + + container.innerHTML = html; + + // Traverses the given element and apply the text replacement function with the given regex + function traverseElement(el, regex, textReplacerFunc) { + var child = el.lastChild; + while (child) { + if (child.nodeType === 1) { + traverseElement(child, regex, textReplacerFunc); + } else if (child.nodeType === 3) { + textReplacerFunc(child, regex); + } + child = child.previousSibling; + } + } + + traverseElement(container, regex, function(textNode, regex) { + // need this as the rendered cell will encode any html tags which will not render + textNode.data = textNode.data.replace(regex, "{{$1}}"); + }); + + var reg = new RegExp("(?:{{)(.*?)(?:}})", "g"); + return container.innerHTML.replace(reg, function(str, el){ + return tpl.highlightResults.resolve(getParams.call(that, { + content: el + })); + }); +} + function isVisible(column) { return column.visible; } @@ -113,6 +156,9 @@ function loadColumns() { order: (!sorted && (data.order === "asc" || data.order === "desc")) ? data.order : null, searchable: !(data.searchable === false), // default: true sortable: !(data.sortable === false), // default: true + sortKey: data.sortKey || '', + sortRendered: data.sortRendered || false, // default: false + rows: data.derivedRows || [], visible: !(data.visible === false), // default: true visibleInSelection: !(data.visibleInSelection === false), // default: true width: ($.isNumeric(data.width)) ? data.width + "px" : @@ -147,6 +193,8 @@ response = { } */ + + function loadData() { var that = this; @@ -154,13 +202,12 @@ function loadData() { showLoading.call(this); function containsPhrase(row) { - var column, - searchPattern = new RegExp(that.searchPhrase, (that.options.caseSensitive) ? "g" : "gi"); - + var column; + var searchPattern = new RegExp(escapeRegExp(that.searchPhrase), (that.options.caseSensitive) ? "g" : "gi"); for (var i = 0; i < that.columns.length; i++) { column = that.columns[i]; - if (column.searchable && column.visible && - column.converter.to(row[column.id]).search(searchPattern) > -1) { + if (column.searchable && (column.visible || that.options.searchSettings.includeHidden) && + column.converter.to(row[column.id], row, column, that).toString().search(searchPattern) > -1) { return true; } } @@ -223,6 +270,20 @@ function loadData() { settings = $.extend(this.options.ajaxSettings, settings); this.xqr = $.ajax(settings); + } else if (this.options.dataFunc) { + var querySettings = { + data: getRequest.call(this), + success: function(response) { + that.current = response.current; + update(response.rows, response.total); + }, + error: function(err) { + renderNoResultsRow.call(that); // overrides loading mask + that.element._bgBusyAria(false).trigger("loaded" + namespace); + } + }; + + this.options.dataFunc(querySettings); } else { var rows = (this.searchPhrase.length > 0) ? this.rows.where(containsPhrase) : this.rows, total = rows.length; @@ -239,7 +300,7 @@ function loadData() { } function loadRows() { - if (!this.options.ajax) { + if (!this.options.ajax && !this.options.dataFunc) { var that = this, rows = this.element.find("tbody > tr"); @@ -248,6 +309,7 @@ function loadRows() { cells = $this.children("td"), row = {}; + row['data'] = $this.data(); $.each(that.columns, function(i, column) { row[column.id] = column.converter.from(cells.eq(i).text()); }); @@ -305,7 +367,7 @@ function renderActions() { actions = $(tpl.actions.resolve(getParams.call(this))); // Refresh Button - if (this.options.ajax) { + if ((this.options.ajax || this.options.dataFunc) && this.options.ajaxSettings.includeRefresh) { var refreshIcon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconRefresh })), @@ -371,7 +433,7 @@ function renderColumnSelection(actions) { loadData.call(that); } }) - .on("change" + namespace, checkboxSelector, function(e){ + .on("change" + namespace, checkboxSelector, function(e) { var $this = $(this); that.element.trigger('toggleColumn', [column.id, column.text, column.visible]); }); @@ -523,12 +585,12 @@ function renderRowCountSelection(actions) { var $this = $(this), newRowCount = $this.data("action"); if (newRowCount !== that.rowCount) { - if(that.options.resolvePageFromRowCount){ + if (that.options.resolvePageFromRowCount) { var page = that.current > 1 ? that.current : 1; - var skip = that.current > 1 ? that.rowCount * (that.current-1) + 1 : 0; - var newPage = skip > 1 ? Math.ceil(skip/newRowCount) : 1; + var skip = that.current > 1 ? that.rowCount * (that.current - 1) + 1 : 0; + var newPage = skip > 1 ? Math.ceil(skip / newRowCount) : 1; that.current = newRowCount > 0 ? newPage : 1; - }else{ + } else { that.current = 1; } that.rowCount = newRowCount; @@ -589,8 +651,14 @@ function renderRows(rows) { if (column.visible) { var value = ($.isFunction(column.formatter)) ? column.formatter.call(that, column, row) : - column.converter.to(row[column.id]), + column.converter.to(row[column.id], row, column, that), cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : ""; + + // Highlight search phrase if available + if (that.searchPhrase !== '' && that.options.searchSettings.highlightResults) { + value = highlightResults.call(that, value); + } + cells += tpl.cell.resolve(getParams.call(that, { content: (value == null || value === "") ? " " : value, css: ((column.align === "right") ? css.right : (column.align === "center") ? @@ -848,28 +916,61 @@ function showLoading() { } function sortRows() { + var that = this; var sortArray = []; + that.element.trigger('sort', that.getSortDictionary()); + function sort(x, y, current) { current = current || 0; var next = current + 1, - item = sortArray[current]; + item = sortArray[current], + cell = item.id; function sortOrder(value) { return (item.order === "asc") ? value : value * -1; } - var a = that.options.caseSensitive ? x[item.id] : x[item.id].toLowerCase(); - var b = that.options.caseSensitive ? y[item.id] : y[item.id].toLowerCase(); + var column = that.getColumnSettings({ + id: cell + })[0]; + + if (column.sortKey) { + cell = column.sortKey; + column = that.getColumnSettings({ + id: cell + })[0]; + } + + var a = x[cell]; + var b = y[cell]; + + if (column.sortRendered) { + a = ($.isFunction(column.formatter)) ? + column.formatter.call(that, column, x) : + column.converter.to(x[column.id]); + try { + a = $(a).text() || a; + } catch (e) {} + b = ($.isFunction(column.formatter)) ? + column.formatter.call(that, column, y) : + column.converter.to(y[column.id]); + try { + b = $(b).text() || b; + } catch (e) {} + } + + if (!that.options.caseSensitive) { + a = $.type(a) === 'string' ? a.toLowerCase() : a; + b = $.type(b) === 'string' ? b.toLowerCase() : b; + } return (a > b) ? sortOrder(1) : (a < b) ? sortOrder(-1) : (sortArray.length > next) ? sort(x, y, next) : 0; } - if (!this.options.ajax) { - var that = this; - + if (!this.options.ajax || !this.options.dataFunc) { for (var key in this.sortDictionary) { if (this.options.multiSort || sortArray.length === 0) { sortArray.push({ @@ -910,7 +1011,11 @@ var Grid = function(element, options) this.identifier = null; // The first column ID that is marked as identifier this.selection = false; this.converter = null; // The converter for the column that is marked as identifier - this.rowCount = ($.isArray(rowCount)) ? rowCount[0] : rowCount; + this.rowCount = rowCount; + if($.isArray(rowCount)){ + var defaultRowCount = this.options.defaultRowCount = this.element.data().defaultRowCount || options.defaultRowCount || this.options.defaultRowCount; + this.rowCount = (rowCount.indexOf(defaultRowCount) > -1) ? defaultRowCount : rowCount[0]; + } this.rows = []; this.searchPhrase = ""; this.selectedRows = []; @@ -1035,7 +1140,27 @@ Grid.defaults = { * @default 1 * @for searchSettings **/ - characters: 1 + characters: 1, + + /** + * Option if search should include hidden columns + * + * @property includeHidden + * @type Boolean + * @default false + * @for searchSettings + **/ + includeHidden: false, + + /** + * Option if search term in results should be highlighted + * + * @property highlightResults + * @type Boolean + * @default false + * @for searchSettings + **/ + highlightResults: true }, /** @@ -1069,7 +1194,17 @@ Grid.defaults = { * @default "POST" * @for ajaxSettings **/ - method: "POST" + method: "POST", + + /** + * Option if refresh button should be shown + * + * @property includeRefresh + * @type Boolean + * @default true + * @for ajaxSettings + **/ + includeRefresh: true }, /** @@ -1147,7 +1282,28 @@ Grid.defaults = { // default converter from: function (value) { return value; }, to: function (value) { return value; } - } + }, + derived: { + // applies reference column converter to each row + to: function(val, row, column, grid) { + try { + var rows = column.rows.split(','); + var self = grid; + var compiledRows = []; + $.each(rows, function(index, element) { + var column = self.getColumnSettings({id: element})[0]; + var content = column.converter.to(row[column.id]); + compiledRows.push(content); + }); + return compiledRows.join(''); + } catch (e) { + return val; + } + }, + from: function(val) { + return val; + } + } }, /** @@ -1170,6 +1326,7 @@ Grid.defaults = { dropDownMenuText: "dropdown-text", // must be a unique class name or constellation of class names within the actionDropDown footer: "bootgrid-footer container-fluid", header: "bootgrid-header container-fluid", + highlightResults: "bootgrid-search-highlight", icon: "icon glyphicon", iconColumns: "glyphicon-th-list", iconDown: "glyphicon-chevron-down", @@ -1180,7 +1337,6 @@ Grid.defaults = { left: "text-left", pagination: "pagination", // must be a unique class name or constellation of class names within the header and footer paginationButton: "button", // must be a unique class name or constellation of class names within the pagination - /** * CSS class to select the parent div which activates responsive mode. * @@ -1221,7 +1377,31 @@ Grid.defaults = { * @for defaults * @since 1.0.0 **/ - formatters: {}, + formatters: { + 'derived': function(column, row) { + try { + var rows = column.rows.split(','); + var self = this; + var compiledRows = []; + $.each(rows, function(index, element) { + var content = row[element]; + var template = element.template || '

__data__

'; + var column = self.getColumnSettings({id: element})[0]; + if (element.formatter || column.formatter) { + var formatter = column.formatter || element.formatter; + content = ($.isFunction(formatter)) ? + formatter.call(self, column, row) : + column.converter.to(row[column.id]); + } + var data = template.replace('__data__', content); + compiledRows.push(data); + }); + return compiledRows.join(''); + } catch (e) { + return row[column.id]; + } + } + }, /** * Contains all labels. @@ -1303,6 +1483,7 @@ Grid.defaults = { footer: "

", header: "

", headerCell: "{{ctx.column.text}}{{ctx.icon}}", + highlightResults: "{{ctx.content}}", icon: "", infos: "
{{lbl.infos}}
", loading: "{{lbl.loading}}", @@ -1334,9 +1515,14 @@ Grid.prototype.append = function(rows) var appendedRows = []; for (var i = 0; i < rows.length; i++) { - if (appendRow.call(this, rows[i])) + var row = rows[i]; + for (var j = 0; j < this.columns.length; j++) { + var column = this.columns[j]; + row[column.id] = column.converter.from(row[column.id]); + } + if (appendRow.call(this, row)) { - appendedRows.push(rows[i]); + appendedRows.push(row); } } sortRows.call(this); @@ -1602,6 +1788,37 @@ Grid.prototype.deselect = function(rowIds) return this; }; +/** + * Deselects all rows. + * + * @method deselectAll + * @chainable + **/ +Grid.prototype.deselectAll = function() +{ + if (this.selection) + { + if (this.selectedRows.length > 0) + { + var deselected = []; + var selectBoxSelector = getCssSelector(this.options.css.selectBox); + + this.element.find("thead " + selectBoxSelector).prop("checked", false); + for (var i = 0; i < this.selectedRows.length; i++) + { + this.element.find("tbody > tr[data-row-id=\"" + this.selectedRows[i] + "\"]") + .removeClass(this.options.css.selected)._bgAria("selected", "false") + .find(selectBoxSelector).prop("checked", false); + } + + this.selectedRows = []; + this.element.trigger("deselected" + namespace, [deselected]); + } + } + + return this; +}; + /** * Sorts the rows by a given sort descriptor dictionary. * The sort filter will be reseted, if no argument is provided. diff --git a/package.json b/package.json index e871f4c..64f18fb 100644 --- a/package.json +++ b/package.json @@ -1,68 +1,74 @@ { - "name": "jquery-bootgrid", - "namespace": "jquery.bootgrid", - "title": "jQuery Bootgrid", - "version": "1.4.1", - "description": "Nice, sleek and intuitive. A grid control especially designed for bootstrap.", - "homepage": "http://www.jquery-bootgrid.com", - "author": { - "name": "Rafael Staib", - "email": "me@rafaelstaib.com", - "url": "http://www.rafaelstaib.com" - }, - "contributors": [{ - "name": "Matt Groth", - "email": "mattgroth85@gmail.com", - "url": "https://github.com/Iodine-" - }], - "bugs": "https://github.com/MoveInc/jquery-bootgrid/issues", - "scripts": { - "test": "grunt" - }, - "repository": { - "type": "git", - "url": "https://github.com/MoveInc/jquery-bootgrid.git" - }, - "keywords": [ - "jquery-plugin", - "jQuery", - "Bootstrap", - "Plugin", - "Component", - "Control", - "UI", - "Grid", - "Table", - "Data", - "Sorting", - "Filtering", - "HTML5", - "Accessibility" - ], - "licenses": [{ - "type": "MIT", - "url": "http://www.opensource.org/licenses/MIT" - }], - "dependencies": { - "jquery": ">=1.9.0", - "bootstrap": ">=3.1.1" - }, - "devDependencies": { - "grunt": "^1.0.1", - "grunt-cli": "^1.2.0", - "grunt-contrib-clean": "^1.0.0", - "grunt-contrib-compress": "^1.3.0", - "grunt-contrib-concat": "^1.0.1", - "grunt-contrib-csslint": "^1.0.0", - "grunt-contrib-cssmin": "^1.0.1", - "grunt-contrib-jshint": "^1.0.0", - "grunt-contrib-less": "^1.3.0", - "grunt-contrib-qunit": "^1.2.0", - "grunt-contrib-uglify": "^2.0.0", - "grunt-contrib-yuidoc": "^1.0.0", - "grunt-exec": "^1.0.0", - "grunt-nuget": "~0.1.4", - "grunt-regex-replace": "^0.3.0" - }, - "readmeFilename": "README.md" + "name": "jquery-bootgrid", + "namespace": "jquery.bootgrid", + "title": "jQuery Bootgrid", + "version": "1.4.2", + "description": "Nice, sleek and intuitive. A grid control especially designed for bootstrap.", + "homepage": "http://www.jquery-bootgrid.com", + "author": { + "name": "Rafael Staib", + "email": "me@rafaelstaib.com", + "url": "http://www.rafaelstaib.com" + }, + "contributors": [ + { + "name": "Matt Groth", + "email": "mattgroth85@gmail.com", + "url": "https://github.com/Iodine-" + } + ], + "bugs": "https://github.com/MoveInc/jquery-bootgrid/issues", + "scripts": { + "test": "grunt" + }, + "repository": { + "type": "git", + "url": "https://github.com/MoveInc/jquery-bootgrid.git" + }, + "keywords": [ + "jquery-plugin", + "jQuery", + "Bootstrap", + "Plugin", + "Component", + "Control", + "UI", + "Grid", + "Table", + "Data", + "Sorting", + "Filtering", + "HTML5", + "Accessibility" + ], + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/MIT" + } + ], + "dependencies": { + "jquery": ">=1.9.0", + "bootstrap": ">=3.1.1" + }, + "devDependencies": { + "grunt": "^1.0.1", + "grunt-cli": "^1.2.0", + "grunt-contrib-clean": "^1.0.0", + "grunt-contrib-compress": "^1.3.0", + "grunt-contrib-concat": "^1.0.1", + "grunt-contrib-csslint": "^1.0.0", + "grunt-contrib-cssmin": "^1.0.1", + "grunt-contrib-jshint": "^1.0.0", + "grunt-contrib-less": "^1.3.0", + "grunt-contrib-qunit": "^1.2.0", + "grunt-contrib-uglify": "^2.0.0", + "grunt-contrib-yuidoc": "^1.0.0", + "grunt-exec": "^1.0.0", + "grunt-nuget": "~0.1.4", + "grunt-regex-replace": "^0.3.0", + "hapi": "^16.5.0", + "inert": "^4.2.1" + }, + "readmeFilename": "README.md" } diff --git a/src/internal.js b/src/internal.js index 870b473..b363ddd 100644 --- a/src/internal.js +++ b/src/internal.js @@ -77,6 +77,45 @@ function highlightAppendedRows(rows) { } } +// Replaces all occurrences of the word in the given html +// Original source: http://stackoverflow.com/questions/8503121/replace-words-in-a-string-but-ignore-html +function highlightResults(html) { + var that = this, + word = that.searchPhrase, + tpl = this.options.templates, + css = this.options.css, + container = document.createElement("div"), + regexFlag = that.options.caseSensitive ? 'g' : 'gi', + regex = new RegExp('(' + word + ')', regexFlag); + + container.innerHTML = html; + + // Traverses the given element and apply the text replacement function with the given regex + function traverseElement(el, regex, textReplacerFunc) { + var child = el.lastChild; + while (child) { + if (child.nodeType === 1) { + traverseElement(child, regex, textReplacerFunc); + } else if (child.nodeType === 3) { + textReplacerFunc(child, regex); + } + child = child.previousSibling; + } + } + + traverseElement(container, regex, function(textNode, regex) { + // need this as the rendered cell will encode any html tags which will not render + textNode.data = textNode.data.replace(regex, "{{$1}}"); + }); + + var reg = new RegExp("(?:{{)(.*?)(?:}})", "g"); + return container.innerHTML.replace(reg, function(str, el){ + return tpl.highlightResults.resolve(getParams.call(that, { + content: el + })); + }); +} + function isVisible(column) { return column.visible; } @@ -103,6 +142,9 @@ function loadColumns() { order: (!sorted && (data.order === "asc" || data.order === "desc")) ? data.order : null, searchable: !(data.searchable === false), // default: true sortable: !(data.sortable === false), // default: true + sortKey: data.sortKey || '', + sortRendered: data.sortRendered || false, // default: false + rows: data.derivedRows || [], visible: !(data.visible === false), // default: true visibleInSelection: !(data.visibleInSelection === false), // default: true width: ($.isNumeric(data.width)) ? data.width + "px" : @@ -149,8 +191,8 @@ function loadData() { for (var i = 0; i < that.columns.length; i++) { column = that.columns[i]; - if (column.searchable && column.visible && - column.converter.to(row[column.id]).search(searchPattern) > -1) { + if (column.searchable && (column.visible || that.options.searchSettings.includeHidden) && + column.converter.to(row[column.id], row, column, that).toString().search(searchPattern) > -1) { return true; } } @@ -213,6 +255,20 @@ function loadData() { settings = $.extend(this.options.ajaxSettings, settings); this.xqr = $.ajax(settings); + } else if (this.options.dataFunc) { + var querySettings = { + data: getRequest.call(this), + success: function(response) { + that.current = response.current; + update(response.rows, response.total); + }, + error: function(err) { + renderNoResultsRow.call(that); // overrides loading mask + that.element._bgBusyAria(false).trigger("loaded" + namespace); + } + }; + + this.options.dataFunc(querySettings); } else { var rows = (this.searchPhrase.length > 0) ? this.rows.where(containsPhrase) : this.rows, total = rows.length; @@ -229,7 +285,7 @@ function loadData() { } function loadRows() { - if (!this.options.ajax) { + if (!this.options.ajax && !this.options.dataFunc) { var that = this, rows = this.element.find("tbody > tr"); @@ -238,6 +294,7 @@ function loadRows() { cells = $this.children("td"), row = {}; + row['data'] = $this.data(); $.each(that.columns, function(i, column) { row[column.id] = column.converter.from(cells.eq(i).text()); }); @@ -295,7 +352,7 @@ function renderActions() { actions = $(tpl.actions.resolve(getParams.call(this))); // Refresh Button - if (this.options.ajax) { + if ((this.options.ajax || this.options.dataFunc) && this.options.ajaxSettings.includeRefresh) { var refreshIcon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconRefresh })), @@ -361,7 +418,7 @@ function renderColumnSelection(actions) { loadData.call(that); } }) - .on("change" + namespace, checkboxSelector, function(e){ + .on("change" + namespace, checkboxSelector, function(e) { var $this = $(this); that.element.trigger('toggleColumn', [column.id, column.text, column.visible]); }); @@ -513,12 +570,12 @@ function renderRowCountSelection(actions) { var $this = $(this), newRowCount = $this.data("action"); if (newRowCount !== that.rowCount) { - if(that.options.resolvePageFromRowCount){ + if (that.options.resolvePageFromRowCount) { var page = that.current > 1 ? that.current : 1; - var skip = that.current > 1 ? that.rowCount * (that.current-1) + 1 : 0; - var newPage = skip > 1 ? Math.ceil(skip/newRowCount) : 1; + var skip = that.current > 1 ? that.rowCount * (that.current - 1) + 1 : 0; + var newPage = skip > 1 ? Math.ceil(skip / newRowCount) : 1; that.current = newRowCount > 0 ? newPage : 1; - }else{ + } else { that.current = 1; } that.rowCount = newRowCount; @@ -579,8 +636,14 @@ function renderRows(rows) { if (column.visible) { var value = ($.isFunction(column.formatter)) ? column.formatter.call(that, column, row) : - column.converter.to(row[column.id]), + column.converter.to(row[column.id], row, column, that), cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : ""; + + // Highlight search phrase if available + if (that.searchPhrase !== '' && that.options.searchSettings.highlightResults) { + value = highlightResults.call(that, value); + } + cells += tpl.cell.resolve(getParams.call(that, { content: (value == null || value === "") ? " " : value, css: ((column.align === "right") ? css.right : (column.align === "center") ? @@ -838,28 +901,61 @@ function showLoading() { } function sortRows() { + var that = this; var sortArray = []; + that.element.trigger('sort', that.getSortDictionary()); + function sort(x, y, current) { current = current || 0; var next = current + 1, - item = sortArray[current]; + item = sortArray[current], + cell = item.id; function sortOrder(value) { return (item.order === "asc") ? value : value * -1; } - var a = that.options.caseSensitive ? x[item.id] : x[item.id].toLowerCase(); - var b = that.options.caseSensitive ? y[item.id] : y[item.id].toLowerCase(); + var column = that.getColumnSettings({ + id: cell + })[0]; + + if (column.sortKey) { + cell = column.sortKey; + column = that.getColumnSettings({ + id: cell + })[0]; + } + + var a = x[cell]; + var b = y[cell]; + + if (column.sortRendered) { + a = ($.isFunction(column.formatter)) ? + column.formatter.call(that, column, x) : + column.converter.to(x[column.id]); + try { + a = $(a).text() || a; + } catch (e) {} + b = ($.isFunction(column.formatter)) ? + column.formatter.call(that, column, y) : + column.converter.to(y[column.id]); + try { + b = $(b).text() || b; + } catch (e) {} + } + + if (!that.options.caseSensitive) { + a = $.type(a) === 'string' ? a.toLowerCase() : a; + b = $.type(b) === 'string' ? b.toLowerCase() : b; + } return (a > b) ? sortOrder(1) : (a < b) ? sortOrder(-1) : (sortArray.length > next) ? sort(x, y, next) : 0; } - if (!this.options.ajax) { - var that = this; - + if (!this.options.ajax || !this.options.dataFunc) { for (var key in this.sortDictionary) { if (this.options.multiSort || sortArray.length === 0) { sortArray.push({ diff --git a/src/jquery.bootgrid.less b/src/jquery.bootgrid.less index 36bfc2e..9a421e6 100644 --- a/src/jquery.bootgrid.less +++ b/src/jquery.bootgrid.less @@ -54,7 +54,7 @@ text-align: right; .btn-group > .btn-group - { + { .dropdown-menu { text-align: left; @@ -157,4 +157,9 @@ td { .noTurncate(); } } -} \ No newline at end of file +} + +.bootgrid-search-highlight +{ + font-weight: bold; +} diff --git a/src/public.js b/src/public.js index a691dec..44e8763 100644 --- a/src/public.js +++ b/src/public.js @@ -23,7 +23,11 @@ var Grid = function(element, options) this.identifier = null; // The first column ID that is marked as identifier this.selection = false; this.converter = null; // The converter for the column that is marked as identifier - this.rowCount = ($.isArray(rowCount)) ? rowCount[0] : rowCount; + this.rowCount = rowCount; + if($.isArray(rowCount)){ + var defaultRowCount = this.options.defaultRowCount = this.element.data().defaultRowCount || options.defaultRowCount || this.options.defaultRowCount; + this.rowCount = (rowCount.indexOf(defaultRowCount) > -1) ? defaultRowCount : rowCount[0]; + } this.rows = []; this.searchPhrase = ""; this.selectedRows = []; @@ -148,7 +152,27 @@ Grid.defaults = { * @default 1 * @for searchSettings **/ - characters: 1 + characters: 1, + + /** + * Option if search should include hidden columns + * + * @property includeHidden + * @type Boolean + * @default false + * @for searchSettings + **/ + includeHidden: false, + + /** + * Option if search term in results should be highlighted + * + * @property highlightResults + * @type Boolean + * @default false + * @for searchSettings + **/ + highlightResults: true }, /** @@ -182,7 +206,17 @@ Grid.defaults = { * @default "POST" * @for ajaxSettings **/ - method: "POST" + method: "POST", + + /** + * Option if refresh button should be shown + * + * @property includeRefresh + * @type Boolean + * @default true + * @for ajaxSettings + **/ + includeRefresh: true }, /** @@ -260,7 +294,28 @@ Grid.defaults = { // default converter from: function (value) { return value; }, to: function (value) { return value; } - } + }, + derived: { + // applies reference column converter to each row + to: function(val, row, column, grid) { + try { + var rows = column.rows.split(','); + var self = grid; + var compiledRows = []; + $.each(rows, function(index, element) { + var column = self.getColumnSettings({id: element})[0]; + var content = column.converter.to(row[column.id]); + compiledRows.push(content); + }); + return compiledRows.join(''); + } catch (e) { + return val; + } + }, + from: function(val) { + return val; + } + } }, /** @@ -283,6 +338,7 @@ Grid.defaults = { dropDownMenuText: "dropdown-text", // must be a unique class name or constellation of class names within the actionDropDown footer: "bootgrid-footer container-fluid", header: "bootgrid-header container-fluid", + highlightResults: "bootgrid-search-highlight", icon: "icon glyphicon", iconColumns: "glyphicon-th-list", iconDown: "glyphicon-chevron-down", @@ -293,7 +349,6 @@ Grid.defaults = { left: "text-left", pagination: "pagination", // must be a unique class name or constellation of class names within the header and footer paginationButton: "button", // must be a unique class name or constellation of class names within the pagination - /** * CSS class to select the parent div which activates responsive mode. * @@ -334,7 +389,31 @@ Grid.defaults = { * @for defaults * @since 1.0.0 **/ - formatters: {}, + formatters: { + 'derived': function(column, row) { + try { + var rows = column.rows.split(','); + var self = this; + var compiledRows = []; + $.each(rows, function(index, element) { + var content = row[element]; + var template = element.template || '

__data__

'; + var column = self.getColumnSettings({id: element})[0]; + if (element.formatter || column.formatter) { + var formatter = column.formatter || element.formatter; + content = ($.isFunction(formatter)) ? + formatter.call(self, column, row) : + column.converter.to(row[column.id]); + } + var data = template.replace('__data__', content); + compiledRows.push(data); + }); + return compiledRows.join(''); + } catch (e) { + return row[column.id]; + } + } + }, /** * Contains all labels. @@ -416,6 +495,7 @@ Grid.defaults = { footer: "

", header: "

", headerCell: "{{ctx.column.text}}{{ctx.icon}}", + highlightResults: "{{ctx.content}}", icon: "", infos: "
{{lbl.infos}}
", loading: "{{lbl.loading}}", @@ -447,9 +527,14 @@ Grid.prototype.append = function(rows) var appendedRows = []; for (var i = 0; i < rows.length; i++) { - if (appendRow.call(this, rows[i])) + var row = rows[i]; + for (var j = 0; j < this.columns.length; j++) { + var column = this.columns[j]; + row[column.id] = column.converter.from(row[column.id]); + } + if (appendRow.call(this, row)) { - appendedRows.push(rows[i]); + appendedRows.push(row); } } sortRows.call(this); @@ -715,6 +800,37 @@ Grid.prototype.deselect = function(rowIds) return this; }; +/** + * Deselects all rows. + * + * @method deselectAll + * @chainable + **/ +Grid.prototype.deselectAll = function() +{ + if (this.selection) + { + if (this.selectedRows.length > 0) + { + var deselected = []; + var selectBoxSelector = getCssSelector(this.options.css.selectBox); + + this.element.find("thead " + selectBoxSelector).prop("checked", false); + for (var i = 0; i < this.selectedRows.length; i++) + { + this.element.find("tbody > tr[data-row-id=\"" + this.selectedRows[i] + "\"]") + .removeClass(this.options.css.selected)._bgAria("selected", "false") + .find(selectBoxSelector).prop("checked", false); + } + + this.selectedRows = []; + this.element.trigger("deselected" + namespace, [deselected]); + } + } + + return this; +}; + /** * Sorts the rows by a given sort descriptor dictionary. * The sort filter will be reseted, if no argument is provided.