Demo
Rank | Country | US $ | Year |
---|---|---|---|
1 | Luxembourg | 113,533 | 2011 |
2 | Qatar | 98,329 | 2011 |
3 | Norway | 97,255 | 2011 |
4 | Switzerland | 81,161 | 2011 |
5 | United Arab Emirates | 67,008 | 2011 |
6 | Australia | 65,477 | 2011 |
7 | Denmark | 59,928 | 2011 |
8 | Sweden | 56,956 | 2011 |
9 | Canada | 50,436 | 2011 |
10 | Netherlands | 50,355 | 2011 |
11 | Austria | 49,809 | 2011 |
12 | Finland | 49,350 | 2011 |
13 | Singapore | 49,271 | 2011 |
14 | United States | 48,387 | 2011 |
15 | Kuwait | 47,982 | 2011 |
16 | Ireland | 47,513 | 2011 |
17 | Belgium | 46,878 | 2011 |
18 | Japan | 45,920 | 2011 |
19 | France | 44,008 | 2011 |
20 | Germany | 43,742 | 2011 |
21 | Iceland | 43,088 | 2011 |
22 | United Kingdom | 38,592 | 2011 |
23 | New Zealand | 36,648 | 2011 |
24 | Brunei | 36,584 | 2011 |
25 | Italy | 36,267 | 2011 |
- | European Union | 35,116 | 2011 |
- | Hong Kong | 34,049 | 2011 |
26 | Spain | 32,360 | 2011 |
27 | Israel | 31,986 | 2011 |
28 | Cyprus | 30,571 | 2011 |
29 | Greece | 27,073 | 2011 |
30 | Slovenia | 24,533 | 2011 |
31 | Oman | 23,315 | 2011 |
32 | Bahamas, The | 23,175 | 2011 |
33 | Bahrain | 23,132 | 2011 |
34 | Korea, South | 22,778 | 2011 |
35 | Portugal | 22,413 | 2011 |
36 | Malta | 21,028 | 2011 |
37 | Saudi Arabia | 20,504 | 2011 |
38 | Czech Republic | 20,444 | 2011 |
39 | Taiwan | 20,101 | 2011 |
40 | Slovakia | 17,644 | 2011 |
41 | Trinidad and Tobago | 17,158 | 2011 |
42 | Estonia | 16,583 | 2011 |
43 | Barbados | 16,148 | 2011 |
44 | Equatorial Guinea[7] | 14,661 | 2011 |
45 | Croatia | 14,457 | 2011 |
46 | Chile | 14,278 | 2011 |
47 | Hungary | 14,050 | 2011 |
48 | Uruguay | 13,914 | 2011 |
49 | Antigua and Barbuda | 13,552 | 2011 |
50 | Poland | 13,540 | 2011 |
51 | Lithuania | 13,075 | 2011 |
52 | Russia | 12,993 | 2011 |
53 | Brazil | 12,789 | 2011 |
54 | Saint Kitts and Nevis | 12,728 | 2011 |
55 | Latvia | 12,671 | 2011 |
56 | Seychelles | 11,170 | 2011 |
57 | Argentina | 10,945 | 2011 |
58 | Kazakhstan | 10,694 | 2011 |
59 | Gabon | 10,654 | 2011 |
60 | Venezuela | 10,610 | 2011 |
61 | Turkey | 10,522 | 2011 |
62 | Mexico | 10,153 | 2011 |
- | World[8] | 10,144 | 2011 |
63 | Lebanon | 9,862 | 2011 |
64 | Malaysia | 9,700 | 2011 |
65 | Botswana | 9,481 | 2011 |
66 | Costa Rica | 8,877 | 2011 |
67 | Romania | 8,863 | 2011 |
68 | Mauritius | 8,777 | 2011 |
69 | Panama | 8,514 | 2011 |
70 | South Africa | 8,066 | 2011 |
71 | Grenada | 7,878 | 2011 |
72 | Saint Lucia | 7,435 | 2011 |
73 | Montenegro | 7,317 | 2011 |
74 | Bulgaria | 7,202 | 2011 |
75 | Colombia | 7,132 | 2011 |
76 | Suriname | 7,096 | 2011 |
77 | Dominica | 6,909 | 2011 |
78 | Azerbaijan | 6,832 | 2011 |
79 | Iran | 6,360 | 2011 |
80 | Saint Vincent and the Grenadines | 6,342 | 2011 |
81 | Serbia | 6,081 | 2011 |
82 | Maldives | 5,973 | 2011 |
83 | Belarus | 5,881 | 2011 |
84 | Namibia | 5,828 | 2011 |
85 | Peru | 5,782 | 2011 |
86 | Libya | 5,691 | 2011 |
87 | Dominican Republic | 5,639 | 2011 |
88 | China | 5,414 | 2011 |
89 | Jamaica | 5,402 | 2011 |
90 | Thailand | 5,394 | 2011 |
91 | Algeria | 5,304 | 2011 |
92 | Angola | 5,144 | 2011 |
93 | Macedonia, Republic of | 5,016 | 2011 |
94 | Jordan | 4,675 | 2011 |
95 | Turkmenistan | 4,658 | 2011 |
96 | Bosnia and Herzegovina | 4,618 | 2011 |
97 | Ecuador | 4,424 | 2011 |
98 | Tunisia | 4,351 | 2011 |
99 | Belize | 4,349 | 2011 |
100 | Tonga | 4,221 | 2011 |
101 | Albania | 3,992 | 2011 |
102 | Fiji | 3,965 | 2011 |
103 | East Timor | 3,949 | 2011 |
104 | El Salvador | 3,855 | 2011 |
105 | Congo, Republic of the | 3,714 | 2011 |
106 | Cape Verde | 3,661 | 2011 |
107 | Ukraine | 3,621 | 2011 |
108 | Kosovo[9] | 3,534 | 2011 |
109 | Iraq | 3,513 | 2011 |
110 | Indonesia | 3,509 | 2011 |
111 | Samoa | 3,451 | 2011 |
112 | Swaziland | 3,358 | 2011 |
113 | Tuvalu[9] | 3,319 | 2011 |
114 | Paraguay | 3,252 | 2011 |
115 | Georgia | 3,210 | 2011 |
116 | Guyana | 3,202 | 2011 |
117 | Guatemala | 3,182 | 2011 |
118 | Morocco | 3,083 | 2011 |
119 | Mongolia | 3,042 | 2011 |
120 | Vanuatu | 3,036 | 2011 |
121 | Armenia | 3,033 | 2011 |
122 | Egypt | 2,970 | 2011 |
123 | Sri Lanka | 2,877 | 2011 |
124 | Syria | 2,803 | 2010 |
125 | Bolivia | 2,315 | 2011 |
126 | Philippines | 2,223 | 2011 |
127 | Bhutan | 2,121 | 2011 |
128 | Honduras | 2,116 | 2011 |
129 | Sudan | 1,982 | 2011 |
130 | Moldova | 1,969 | 2011 |
131 | Papua New Guinea | 1,900 | 2011 |
132 | Kiribati | 1,593 | 2011 |
133 | Uzbekistan | 1,572 | 2011 |
134 | Solomon Islands | 1,554 | 2011 |
135 | Ghana | 1,529 | 2011 |
136 | Nigeria | 1,490 | 2011 |
137 | São Tomé and PrÃncipe | 1,473 | 2011 |
138 | Djibouti | 1,467 | 2011 |
139 | Zambia | 1,414 | 2011 |
140 | India | 1,389 | 2011 |
141 | Vietnam | 1,374 | 2011 |
142 | Yemen | 1,340 | 2011 |
143 | Mauritania | 1,290 | 2011 |
144 | Lesotho | 1,264 | 2011 |
145 | Nicaragua | 1,239 | 2011 |
146 | Cameroon | 1,230 | 2011 |
147 | Laos | 1,204 | 2011 |
148 | Pakistan | 1,201 | 2011 |
149 | Senegal | 1,076 | 2011 |
150 | Kyrgyzstan | 1,070 | 2011 |
151 | Côte d'Ivoire | 1,062 | 2011 |
152 | Comoros | 903 | 2011 |
153 | Chad | 892 | 2011 |
154 | Cambodia | 852 | 2011 |
155 | Kenya | 851 | 2011 |
156 | Burma | 832 | 2011 |
157 | Tajikistan | 831 | 2011 |
158 | Zimbabwe | 741 | 2011 |
159 | Haiti | 738 | 2011 |
160 | Benin | 737 | 2011 |
161 | Bangladesh | 678 | 2011 |
162 | Mali | 669 | 2011 |
163 | Burkina Faso | 664 | 2011 |
164 | Nepal | 653 | 2011 |
165 | Rwanda | 605 | 2011 |
166 | Afghanistan | 585 | 2011 |
167 | Mozambique | 583 | 2011 |
168 | Guinea-Bissau | 576 | 2011 |
169 | Tanzania | 553 | 2011 |
170 | Gambia, The | 543 | 2011 |
171 | Togo | 506 | 2011 |
172 | Guinea | 492 | 2011 |
173 | Uganda | 478 | 2011 |
174 | Eritrea | 475 | 2011 |
175 | Madagascar | 459 | 2011 |
176 | Central African Republic | 456 | 2011 |
177 | Niger | 399 | 2011 |
178 | Sierra Leone | 366 | 2011 |
179 | Ethiopia | 360 | 2011 |
180 | Malawi | 351 | 2011 |
181 | Liberia | 298 | 2011 |
182 | Burundi | 279 | 2011 |
183 | Congo, Democratic Republic of the | 216 | 2011 |
To get started, simply install jquery.dynatable.js (along with jQuery),
and add the following in the document.ready
or after the table:
$('#my-table').dynatable();
How it works
Dynatable does three things:
-
Read / Normalize
The HTML table is scanned and normalized into an array of JSON objects (or collection) where each JSON object (or record) corresponds to a row in the table.
-
Operate
The JSON collection can be sorted, searched/filtered, and paginated/sliced.
-
Write / Render
The results of the Operate step are rendered back to the DOM in the body of the table.
This 3-step approach has several advantages:
-
Efficient reading/operating/writing
Since the logic and operations occur on the JSON collection, the DOM operations (reading and writing/drawing) are grouped together, making interactions quick and efficient.
-
Operations are simple JavaScript
An operation is simply a function that acts on the normalized JSON collection; sorting, filtering, and paginating are straight forward in JavaScript.
The built-in functions are easy to augment with your own custom sorting and querying functions.
-
Steps can be customized, swapped or skipped
Since the normalization, operation, and rendering modules are separated, each can easily be customized, replaced, or skipped.
Already have a JSON API to work with? Skip the Read step. Want to add paginating, filtering, and sorting to a chart? Customize the Render step.
Normalization
The first module normalizes an HTML table into a JSON collection. Dynatable names the attributes of each record according to the table heading, so that the JSON collection is human-readable and easy to work with.
The following table:
<table>
<thead>
<tr>
<th>Name</th>
<th>Hobby</th>
<th>Favorite Music</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fred</td>
<td>Roller Skating</td>
<td>Disco</td>
</tr>
<tr>
<td>Helen</td>
<td>Rock Climbing</td>
<td>Alternative</td>
</tr>
<tr>
<td>Glen</td>
<td>Traveling</td>
<td>Classical</td>
</tr>
</tbody>
</table>
Results in this JSON collection:
[
{
"name": "Fred",
"hobby": "Roller Skating",
"favoriteMusic": "Disco"
},
{
"name": "Helen",
"hobby": "Rock Climbing",
"favoriteMusic": "Alternative"
},
{
"name": "Glen",
"hobby": "Traveling",
"favoriteMusic": "Classical"
}
]
Converting attribute names
By default, dynatable converts headings to JSON attribute names using:
Style | Example |
---|---|
camelCase (default) | favoriteMusic |
trimDash | Favorite-Music |
dashed | favorite-music |
underscore | favorite_music |
lowercase | favorite music |
$('#my-table').dynatable({
table: {
defaultColumnIdStyle: 'trimDash'
}
});
$.dynatableSetup({
table: {
defaultColumnIdStyle: 'underscore'
}
});
underscore
, matching the Rails parameter and input
field naming conventions. This is useful when getting the JSON data via
AJAX from Rails, or when connecting dynatable events with form inputs on the page).
We could also define our own column-name transformation function. Consider the following table with these column headings:
Name | Hobby | Favorite Music |
---|---|---|
Fred | Roller Skating | Disco |
Helen | Rock Climbing | Alternative |
Glen | Traveling | Classical |
We can set up our own function for transforming column labels to JSON property names of our desired custom format when we instantiate dynatable on the table above:
$('#text-transform-example').bind('dynatable:preinit', function(e, dynatable) { dynatable.utility.textTransform.myNewStyle = function(text) { return text .replace(/\s+/, '_') .replace(/[A-Z]/, function($1){ return $1 + $1 }); }; }).dynatable({ table: { defaultColumnIdStyle: 'myNewStyle' }, features: { paginate: false, search: false, recordCount: false, perPageSelect: false } });
Click the button to the right. Do it!
Click this button to run the code above, populating the box on the left with the resulting JSON collection built by dynatable.
Sometimes, we need columns with labels different than the record
attribute name. If a column heading contains the data-dynatable-column
attribute, the associated record attribute will be named by that value.
So this:
<table id="my-final-table">
<thead>
<th data-dynatable-column="name">Band</th>
<th>Hit</th>
</thead>
<tbody>
...
</tbody>
</table>
Would result in:
[
{
"name": ...,
"song": ...
},
{
"name": ...,
"song": ...
}
]
The default behavior makes it easy to make an existing HTML table dynamic. But we're not limited to reading tables.
Existing JSON
Perhaps we already have our data in JSON format. We can skip the initial record normalization by setting up an empty table for rendering and directly passing our data into dynatable:
HTML table to render records:
<table id="my-final-table">
<thead>
<th>Band</th>
<th>Song</th>
</thead>
<tbody>
</tbody>
</table>
This is a pre#json-records
element:
[ { "band": "Weezer", "song": "El Scorcho" }, { "band": "Chevelle", "song": "Family System" } ]
var $records = $('#json-records'),
myRecords = JSON.parse($records.text());
$('#my-final-table').dynatable({
dataset: {
records: myRecords
}
});
Band | Song |
---|
JSON from AJAX
Or maybe, we want to fetch the data via AJAX:
<table id="my-ajax-table">
<thead>
<th>Some Attribute</th>
<th>Some Other Attribute</th>
</thead>
<tbody>
</tbody>
</table>
$('#my-ajax-table').dynatable({
dataset: {
ajax: true,
ajaxUrl: '/dynatable-ajax.json',
ajaxOnLoad: true,
records: []
}
});
Some Attribute | Some Other Attribute |
---|
When using Dynatable in "AJAX mode" (dataset.ajax = true
), delegates all operations
(pagination, sorting, and querying/filtering) to the server. For each
operation, dynatalbe culls the parameters (sort, search, page) into an
AJAX request and fetches the results from dataset.ajaxUrl
(if this
setting isn't set, it will send an AJAX request to the URL of the
current page).
AJAX mode is intended to be used when you want the server to look up the records only as needed. This generally means your server is looking up the records from a database using the database's query, limit, and offset functions to select the appropriate subset of records.
Because your server is only ever returning a subset of the records at a time to Dynatable, the response must contain some extra meta-data. The following format is the default format expected by Dynatable.
{
"records": [
{
"someAttribute": "I am record one",
"someOtherAttribute": "Fetched by AJAX"
},
{
"someAttribute": "I am record two",
"someOtherAttribute": "Cuz it's awesome"
},
{
"someAttribute": "I am record three",
"someOtherAttribute": "Yup, still AJAX"
}
],
"queryRecordCount": 3,
"totalRecordCount": 3
}
If you instead want to fetch all records from the server at once via AJAX, you may consider leaving AJAX mode off, fetching the records, and calling Dynatable with the normal JSON recordset returned by the server:
$.ajax({
url: 'ajax_data.json',
success: function(data){
$('#my-final-table').dynatable({
dataset: {
records: data
}
});
}
});
Lists and non-Tables
Or maybe we do need the normalization step, but we want to read the data from an unordered list instead of a table:
We can use the table
settings to configure such awesomeness. We'll use the
table.bodyRowSelector
setting to tell dynatable to use li
elements
as record rows instead of the default tr
elements, and we'll use the
writers._rowWriter
setting to tell dynatable how to process each li
into a JSON record object.
Dynatable will call the readers._rowReader
function once for each record in the table.bodyRowSelector
collection,
and pass it the current count index, the DOM element, and the JSON
record. This allows full control over which data in the DOM maps to
which data in the JSON:
NOTE: We'll also need a readers._rowWriter
function to tell
dynatable how to write the JSON records back to the page, but we'll get
to that in the Render section.
The following HTML:
<ul id="my-list">
<li>
<span class="name">Fender Custom Esquire GT</span>
<span class="type">Guitar</span>
$<span class="price">450.00</span>
</li>
<li>
<span class="name">ESP LTD B4-E</span>
<span class="type">Bass</span>
$<span class="price">400.00</span>
</li>
</ul>
And JavaScript:
$('#my-list').dynatable({
table: {
bodyRowSelector: 'li',
rowReader: function(index, li, record) {
var $li = $(li);
record.name = $li.find('.name').text();
record.type = $li.find('.type').text();
record.price = parseFloat($li.find('.price').text());
}
}
});
Will result in the following JSON:
[
{
"name": "Fender Custom Esquire GT",
"type": "Guitar",
"price": 450.0
},
{
"name": "ESP LTD B4-E",
"type": "Bass",
"price": 400.0
}
]
Operations
Once we have our JSON dataset, we can perform all our interactive and dynamic logic directly on the JSON using JavaScript. By default, dynatable comes with functions for sorting, filtering (aka searching), and paginating.
By default, dynatable performs all operations on the JSON record
collection in the page. However, if dataset.ajax
is enabled, dynatable
simply passes the operations (pagination, queries, and sort
columns) as parameters to the AJAX URL, thereby delegating the logic to
your server-side code.
The parameter names for pushState and AJAX requests can be customized in
the params
configuration settings for dynatable.
Sorting
Dynatable allows for single or multi-column, smart sorting out of the box.
Dynatable can be made aware of the value types of each column, or record
property, so that e.g. dates and numbers are sorted properly (plain-text
sorting would cause February to come before January, and 10 to come
before 2). By default, if dynatable detects HTML code within the value
of a record (such as an img
tag, it will automatically sort and search
based on the text-equivalent value of the cell, so sorting won't be
affected by HTML tags or attributes).
Basic Sorting
Click the header rows below to sort by each column. Click a header once for ascending, again for descending, and again to stop sorting by that column.
Hold shift and click a second row to add secondary sorting, and so on.
Make | Model | Year | Price |
---|---|---|---|
Volkswagen | Jetta Wolfsburg | 2008 | 11,000 |
Ford | Focus | 2013 | 20,000 |
Ford | Escape | 2001 | 4,000 |
Mini | Cooper | 2001 | 8,500 |
Ford | Focus SVT | 2003 | 9,000 |
In the example above, we run the "Price" column values through an
"reader" function which returns a JavaScript Number
and parses out
the comma seperator. Likewise, we then run it through a rendering "writer"
which re-inserts the comma when rendering the number back to the DOM.
Sort by Another Value
Sometimes, we need one column to sort based on some other attribute.
For example, maybe we have a column which needs to sort on another
hidden column. We can use the data-dynatable-sorts
attribute on the
column header to let dynatable know.
<table id="sorting-example">
<thead>
<tr>
<th>Name</th>
<th data-dynatable-sorts="computerYear">Year</th>
<th style="display: none">Computer Year</th>
</tr>
</thead>
<tbody>
<tr>
<td>Steve</td>
<td>Two Thousand and Thirteen</td>
<td>2013</td>
</tr>
</tbody>
</table>
In the above example, dynatable will detect that the last column heading is hidden, and will hide all cells under that column, and it will sort the "Year" column based on the attribute in the last column.
If we have a column we don't want to be sortable, we just add the
data-dynatable-no-sort
attribute.
Custom Sort Functions
We can also use our own custom sort function. This demo sorts the "color" column by the average color content in the images, from greenish to bluish to reddish (using javascript and canvas in our sorting function to evaluate the color content of each image):
Sort by Color |
---|
Cerasinops |
Ceratosaurus |
Allosaurus |
Tyrannosaurus |
Brachylophosaurus |
Albertaceratops |
Utahraptor |
We may also sort programmatically with the dynatable API. For example, let's add a button which sorts our table by dinosaur names, and a button that clears all our sorts, putting the records back in their original order:
The code for the buttons above:
$('#sorting-function-example').bind('dynatable:init', function(e, dynatable) {
$('#sorting-function-example-button').click( function(e) {
// Clear any existing sorts
dynatable.sorts.clear();
dynatable.sorts.add('name', 1) // 1=ASCENDING, -1=DESCENDING
dynatable.process();
e.preventDefault();
});
$('#sorting-function-example-clear-button').click( function(e) {
dynatable.sorts.clear();
dynatable.process();
e.preventDefault()
});
});
There are a couple different ways to achieve the custom color sorting above, and it's useful to explore each way to gain a better understanding of what's possible.
Creating a Custom Sort Function
The first way is to create a custom sort function, add it to dynatable's
list of sort functions in sorts.functions
, and then tell dynatable to use that function
when sorting that column.
A sort function takes in the two records being compared (a and b below), the attribute column currently being sorted, and the direction (1 for ascending, -1 for descending). The function needs to return a positive number (if a is higher than b), a negative number (if b is higher than a), or 0 (if a and b are tied).
// Our custom sort function
function rgbSort(a, b, attr, direction) {
// Assuming we've created a separate function
// to get the average RGB value from an image.
// (see source for example above for getAverageRGB function)
var aRgb = getAverageRGB(a.img),
bRgb = getAverageRGB(b.img),
aDec = ( aRgb.r << 16 ) + ( aRgb.g << 8 ) + aRgb.b,
bDec = ( bRgb.r << 16 ) + ( bRgb.g << 8 ) + bRgb.b,
comparison = aDec - bDec;
return direction > 0 ? comparison : -comparison;
};
// Wait until images are loaded
$(window).load(function() {
$('#sorting-function-example')
// Add our custom sort function to dynatable
.bind('dynatable:init', function(e, dynatable) {
dynatable.sorts.functions["rgb"] = rgbSort;
})
// Initialize dynatable
.dynatable({
features: {
paginate: false,
search: false,
recordCount: false
},
dataset: {
// When we sort on the color column,
// use our custom sort added above.
sortTypes: {
color: 'rgbSort'
}
},
readers: {
color: function(cell, record) {
var $cell = $(cell);
// Store the average RGB image color value
// as a decimal in "dec" attribute.
record['img'] = $cell.find('img').get(0);
// Return the HTML of the cell to be stored
// as the "color" attribute.
return $cell.html();
}
}
});
})
The sort function gets run between each pair of records to determine
which comes first. This means it gets run n!
times (where n is the
number of records), or n-1
times for each record.
So, the average RGB values in this example are being re-computed multiple times for each record. This kills the efficiency.
Creating a Custom Attribute to Sort On
Instead, it's much more efficient to compute values only once for each record and store them as record attributes. We were already storing the image file above for each record, so why not go ahead and store the RGB values too?
Furthermore, notice that in our custom rgbSort
function above, after it
calculates the RGB value for each record, it's just doing a standard
number comparison (by subtracting one value from the other). Dynatable
has built-in "number" sorting.
$(window).load(function() {
$('#sorting-function-example')
// Initialize dynatable
.dynatable({
features: {
paginate: false,
search: false,
recordCount: false
},
// We have one column, but it contains multiple types of info.
// So let's define a custom reader for that column to grab
// all the extra info and store it in our normalized records.
readers: {
color: function(cell, record) {
// Inspect the source of this example
// to see the getAverageRGB function.
var $cell = $(cell),
rgb = getAverageRGB($cell.find('img').get(0)),
dec = ( rgb.r << 16 ) + ( rgb.g << 8 ) + rgb.b;
// Store the average RGB image color value
// as a decimal in "dec" attribute.
record['dec'] = dec;
// Grab the dinosaur name.
record['name'] = $cell.text();
// Return the HTML of the cell to be stored
// as the "color" attribute.
return $cell.html();
}
}
});
})
We could now create a custom sort function for the "color" column, to make
sure it sorts based on the "dec" attribute instead. Or, we could just tell
dynatable to sort the "color" column based on the "name" attribute
directly in our table with data-dynatable-sorts
:
<table>
<thead>
<tr>
<th data-dynatable-column="color" data-dynatable-sorts="dec">Sort by Color</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="/images/dinosaurs/cerasinops.jpg" /> Cerasinops</td>
</tr>
<!-- ... -->
</tbody>
</table>
Querying
(aka filtering or searching)
In addition to sorting, we can also query the data by some term or value. By default, dynatable includes a search box which matches from the plain-text values (case-insensitive) across all attributes of the records. Try it in the demo at the top of this page, by typing in the search box above the table and hitting "Enter" or "Tab".
Custom Query Functions
Queries can also be added programmatically via JavaScript to be
processed by dynatable. We simply add a query key-value to the
dataset.queries
array, where the key matches the JSON record attribute
you'd like to match, and the value is what we're matching.
Below, we'll include the default text search, and additionally include our own "Year" filter.
<select id="search-year" name="year">
<option></option>
<option>2001</option>
<option>2003</option>
<option>2008</option>
<option>2013</option>
</select>
var dynatable = $('#search-example').dynatable({
features: {
paginate: false,
recordCount: false,
sorting: false
}
}).data('dynatable');
$('#search-year').change( function() {
var value = $(this).val();
if (value === "") {
dynatable.queries.remove("year");
} else {
dynatable.queries.add("year",value);
}
dynatable.process();
});
Make | Model | Year |
---|---|---|
Volkswagen | Jetta Wolfsburg | 2008 |
Ford | Focus | 2013 |
Ford | Escape | 2001 |
Mini | Cooper | 2001 |
Ford | Focus SVT | 2003 |
There's a shortcut to the above code; to hook up our own search filters,
we can just pass an array of jQuery selectors which point to our filter
inputs. Instead of binding to our input's change event, adding the
input's value to the queries array and calling the dynatable.process()
function, we could have just done this:
$('#search-example').dynatable({
features: {
paginate: false,
recordCount: false,
sorting: false
},
inputs: {
queries: $('#search-year')
}
});
Doing it this way also hooks the query into the pushState
functionality to update the page URL parameters and cache the query
result for the browser's forward- and back-buttons, and sets the query
event (the JS event that processes the query) to the inputs.queryEvent
setting (which can also be customized per-input via the
data-dynatable-query-event
attribute). The key-name for the query will
be set to the data-dynatable-query
attribute, the name
attribute, or
the id
for the input.
Using our own query filters, we may also need something other than
text-matching. Perhaps we want a filter which sets a price range. We can
add our query input with the inputs.queries
setting as above, and then
define our own query function for that key.
When using our own query function, the query key must match the name of the query function, rather than the name of a column or record attribute.
$('#search-function-example')
.bind('dynatable:init', function(e, dynatable) {
dynatable.queries.functions['max-price'] = function(record, queryValue) {
return parseFloat(record.price.replace(/,/,'')) <= parseFloat(queryValue);
};
})
.dynatable({
features: {
paginate: false,
recordCount: false,
sorting: false,
search: false
},
inputs: {
queries: $('#max-price')
}
});
By default, when a query is added, dynatable will first look in the
queries.functions
object to find the query function matching the
query's key-name. If none is found, it will fall-back to doing a
plain-text search on the record attribute matching the query key-name.
If that attribute doesn't exist either, then dynatable will throw and
error alerting us to add the function.
The query function is called once for each record and should return
either true
or false
, letting dynatable know if that record matches
the query or not.
Make | Model | Year | Price |
---|---|---|---|
Volkswagen | Jetta Wolfsburg | 2008 | 11,000 |
Ford | Focus | 2013 | 20,000 |
Ford | Escape | 2001 | 4,000 |
Mini | Cooper | 2001 | 8,500 |
Ford | Focus SVT | 2003 | 9,000 |
Paginating
Dynatable also provides pagination by default, by selecting a specific slice of the JSON record collection to render to the page, and adding page selection links to the table, as well as a drop-down allowing the user to select how many records are shown per page.
In other words, dynatable is aware that the currently rendered records in the DOM may only be a subset of the total records.
We can customize the default number of records displayed per page via
the dataset.perPageDefault
configuration setting. And we can customize the
per-page options via the dataset.perPageOptions
configuration setting.
We can also set the page and perPage values via the dynatable API:
var dynatable = $('#my-table').data('dynatable');
dynatable.paginationPerPage.set(20); // Show 20 records per page
dynatable.paginationPage.set(5); // Go to page 5
dynatable.process();
If dataset.ajax
is enabled, then the page and per-page parameters are
simply passed to the server.
Record Count
When pagination is enabled, dynatable will also show the currently displayed records and the total number of records in the form:
Showing {x} to {y} out of {z} records
This message can be customized via the dataset.recordCountText
configuration, and the params.records
configuration. The text
displayed on the table is of the form:
{dataset.recordCountText} {x} to {y} out of {z} {params.records}
Dynatable will also show the queried and total record counts when querying data, in the form:
Showing {x} of {y} records (filtered from {z} total records)
Or more accurately:
{dataset.recordCountText} {x} of {y} {params.records} (filtered from {z} total {params.records})
When dataset.ajax
is enabled, in order for dynatable to display this
message, our server must return
the number of total records in addition to the sliced record set for the
current page. By default, dynatable looks for the total number of
records in the responseJSON.totalRecordCount
attribute.
PushState
Dynatable uses HTML5's pushState to store operation results (sorting, querying and paginating) and update the browser's URL, so that we may hit the browser's back- and forward-buttons to step through our interactions with the table.
If the resulting data can be stored in the browser's
pushState cache, then it will be, and dynatable will simply render the
cached data for that step rather than re-running the (potentially
complex) operations. If dataset.ajax
is enabled, then dynatable will
render the pushState-cached results rather than re-submitting the AJAX
request to the server.
If the resulting dataset for a given operation is too large for the pushState cache, then dynatable will automatically fallback to re-running the operations or re-sending the AJAX request to the server.
Processing Indicator
For long-running operations (and for AJAX tables which must request data form the server), dynatable automatically appends a "processing" indicator to the table to let users know something is happening. We can style this indicator however we want. By default, it's just the word "Processing..." overlaid in the center of the table.
We can customize the html content of the processing indicator (including
images or gifs), using the inputs.processingText
configuration.
We can also style the processing indicator overlay and inner block, by
attaching styles to the
dynatable-processing
class and the .dynatable-processing span
CSS selector, respectively.
Important Things |
---|
E=MC2 |
F=MA |
A2+B2=C2 |
Important Things |
---|
E=MC2 |
F=MA |
A2+B2=C2 |
To show or hide the processing indicator above, we can call the
dynatable.processingIndicator.show()
and
dynatable.processingIndicator.hide()
functions.
For the nicer example, we just add our own custom markup for the processing indicator, along with some custom CSS.
$('#processing-indicator-nice-example').dynatable({
inputs: {
processingText: 'Loading <img src="/images/loading.gif" />'
}
});
.dynatable-processing {
background: #000;
opacity: 0.6;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.dynatable-processing span {
background: #FFF;
border: solid 2px #57A957;
color: #333;
padding: 25px;
font-size: 2em;
box-shadow: 0px 0px 15px rgba(0,0,0,0.5);
}
.dynatable-processing span img {
vertical-align: middle;
}
Rendering
When rendering JSON data to the page, dynatable passes data through "writers" (you may notice that this is the opposite of the normalization step which runs the DOM elements through "readers").
When rendering (and normalizing), dynatable assumes that our container element (on which
we called dynatable) contains elements matching
table.bodyRowSelector
, each mapping to one record. By default,
dynatable assumes we're rendering to an HTML table, so our
table.bodyRowSelector
is 'tbody tr'
.
To render our records, dynatable will loop through our records, running
writers._rowWriter
on each record to create a collection of DOM elements.
The default writers._rowWriter
creates a table tr
element and loops
through the element attributes (matching our columns) to call
writers._cellWriter
on each.
A Stylized List
* List of U.S. state dinosaurs from Wikipedia
If our container element is a ul
, like above, we could customize our rowWriter as
follows:
<ul id="ul-example" class="row-fluid">
<li class="span4" data-color="gray">
<div class="thumbnail">
<div class="thumbnail-image">
<img src="/images/dinosaurs/Stegosaurus_BW.jpg" />
</div>
<div class="caption">
<h3>Stegosaurus armatus</h3>
<p>State: Colorado</p>
<p>Year: 1982</p>
<p><a href="http://en.wikipedia.org/wiki/Stegosaurus" class="btn btn-primary">View</a> <a href="#" class="btn">View</a></p>
</div>
</div>
</li>
<!-- ... //-->
</ul>
// Function that renders the list items from our records
function ulWriter(rowIndex, record, columns, cellWriter) {
var cssClass = "span4", li;
if (rowIndex % 3 === 0) { cssClass += ' first'; }
li = '<li class="' + cssClass + '"><div class="thumbnail"><div class="thumbnail-image">' + record.thumbnail + '</div><div class="caption">' + record.caption + '</div></div></li>';
return li;
}
// Function that creates our records from the DOM when the page is loaded
function ulReader(index, li, record) {
var $li = $(li),
$caption = $li.find('.caption');
record.thumbnail = $li.find('.thumbnail-image').html();
record.caption = $caption.html();
record.label = $caption.find('h3').text();
record.description = $caption.find('p').text();
record.color = $li.data('color');
}
$('#ul-example').dynatable({
table: {
bodyRowSelector: 'li'
},
dataset: {
perPageDefault: 3,
perPageOptions: [3, 6]
},
writers: {
_rowWriter: ulWriter
},
readers: {
_rowReader: ulReader
},
params: {
records: 'kittens'
}
});
We could have defined our own writers._cellWriter
as well, defining a
custom function for rendering each attribute within the row, but we opted
to skip it entirely and to just do everything in the writers._rowWriter
.
An Interactive Chart
Show Table to Sort the Chart Series
City | Population |
---|---|
Tokyo | 34.4 |
Jakarta | 21.8 |
New York | 20.1 |
Seoul | 20 |
Manila | 19.6 |
Mumbai | 19.5 |
Sao Paulo | 19.1 |
Mexico City | 18.4 |
Dehli | 18 |
Osaka | 17.3 |
Cairo | 16.8 |
Kolkata | 15 |
Los Angeles | 14.7 |
Shanghai | 14.5 |
Moscow | 13.3 |
Beijing | 12.8 |
Buenos Aires | 12.4 |
Guangzhou | 11.8 |
Shenzhen | 11.7 |
Istanbul | 11.2 |
Our initial data:
<div id="chart-example-chart"></div>
<a class="btn primary" id="toggle-chart-table">Show Table to Sort the Chart Series</a>
<table id="chart-example" class="table table-bordered">
<thead><tr><th>City</th><th>Population</th></tr></thead>
<tbody>
<tr><td>Tokyo</td><td>34.4</td></tr>
<tr><td>Jakarta</td><td>21.8</td></tr>
<tr><td>New York</td><td>20.1</td></tr>
<tr><td>Seoul</td><td>20</td></tr>
<tr><td>Manila</td><td>19.6</td></tr>
<tr><td>Mumbai</td><td>19.5</td></tr>
<tr><td>Sao Paulo</td><td>19.1</td></tr>
<tr><td>Mexico City</td><td>18.4</td></tr>
<tr><td>Dehli</td><td>18</td></tr>
<tr><td>Osaka</td><td>17.3</td></tr>
<tr><td>Cairo</td><td>16.8</td></tr>
<tr><td>Kolkata</td><td>15</td></tr>
<tr><td>Los Angeles</td><td>14.7</td></tr>
<tr><td>Shanghai</td><td>14.5</td></tr>
<tr><td>Moscow</td><td>13.3</td></tr>
<tr><td>Beijing</td><td>12.8</td></tr>
<tr><td>Buenos Aires</td><td>12.4</td></tr>
<tr><td>Guangzhou</td><td>11.8</td></tr>
<tr><td>Shenzhen</td><td>11.7</td></tr>
<tr><td>Istanbul</td><td>11.2</td></tr>
</tbody>
</table>
The JS:
(function() {
var $table = $('#chart-example'), $chart = $('#chart-example-chart'), chart;
// Create a button to toggle our table's visibility.
// We could just hide it completely if we don't need it.
$('#toggle-chart-table').click(function(e) {
e.preventDefault();
$table.toggle();
});
// Set up our Highcharts chart
chart = new Highcharts.Chart({
chart: {
type: 'column',
renderTo: 'chart-example-chart'
},
title: {
text: 'World\'s largest cities per 2008'
},
yAxis: {
min: 0,
title: {
text: 'Population (millions)'
}
},
series: [{
name: 'Population',
color: '#006A72'
}]
});
// Create a function to update the chart with the current working set
// of records from dynatable, after all operations have been run.
function updateChart() {
var dynatable = $table.data('dynatable'), categories = [], values = [];
$.each(dynatable.settings.dataset.records, function() {
categories.push(this.city);
values.push(parseFloat(this.population));
});
chart.xAxis[0].setCategories(categories);
chart.series[0].setData(values);
};
// Attach dynatable to our table, hide the table,
// and trigger our update function whenever we interact with it.
$table
.dynatable({
inputs: {
queryEvent: 'blur change keyup',
recordCountTarget: $chart,
paginationLinkTarget: $chart,
searchTarget: $chart,
perPageTarget: $chart
},
dataset: {
perPageOptions: [5, 10, 20],
sortTypes: {
'population': 'number'
}
}
})
.hide()
.bind('dynatable:afterProcess', updateChart);
// Run our updateChart function for the first time.
updateChart();
})();
Configuration
If you want to change any of the following default configuration options
globally (for all instances of dynatable within your application), you
can call the $.dynatableSetup()
function to do so:
$.dynatableSetup({
// your global default options here
});
For example, this documentation page has features: { pushState: false}
so as not to fill your browser's pushState queue as you click around
through made-up data in the examples (except for the first example,
which re-enables it for demo purposes).
The confiuration options (with default values) for dynatable are:
{
features: {
paginate: true,
sort: true,
pushState: true,
search: true,
recordCount: true,
perPageSelect: true
},
table: {
defaultColumnIdStyle: 'camelCase',
columns: null,
headRowSelector: 'thead tr', // or e.g. tr:first-child
bodyRowSelector: 'tbody tr',
headRowClass: null
},
inputs: {
queries: null,
sorts: null,
multisort: ['ctrlKey', 'shiftKey', 'metaKey'],
page: null,
queryEvent: 'blur change',
recordCountTarget: null,
recordCountPlacement: 'after',
paginationLinkTarget: null,
paginationLinkPlacement: 'after',
paginationPrev: 'Previous',
paginationNext: 'Next',
paginationGap: [1,2,2,1],
searchTarget: null,
searchPlacement: 'before',
perPageTarget: null,
perPagePlacement: 'before',
perPageText: 'Show: ',
recordCountText: 'Showing ',
processingText: 'Processing...'
},
dataset: {
ajax: false,
ajaxUrl: null,
ajaxCache: null,
ajaxOnLoad: false,
ajaxMethod: 'GET',
ajaxDataType: 'json',
totalRecordCount: null,
queries: null,
queryRecordCount: null,
page: null,
perPageDefault: 10,
perPageOptions: [10,20,50,100],
sorts: null,
sortsKeys: null,
sortTypes: {},
records: null
},
// Built-in writer functions,
// can be overwritten, any additional functions
// provided in writers will be merged with
// this default object.
writers: {
_rowWriter: defaultRowWriter,
_cellWriter: defaultCellWriter,
_attributeWriter: defaultAttributeWriter
},
// Built-in reader functions,
// can be overwritten, any additional functions
// provided in readers will be merged with
// this default object.
readers: {
_rowReader: null,
_attributeReader: defaultAttributeReader
},
params: {
dynatable: 'dynatable',
queries: 'queries',
sorts: 'sorts',
page: 'page',
perPage: 'perPage',
offset: 'offset',
records: 'records',
record: null,
queryRecordCount: 'queryRecordCount',
totalRecordCount: 'totalRecordCount'
}
}
Data Attributes
In addition to the configuration options directly available above, some properties apply specifically to certain columns or elements. Those can be set using HTML5 data attributes.
On table column headers
data-dynatable-column
data-dynatable-sorts
data-dynatable-no-sort
On query inputs
data-dynatable-query-event
data-dynatable-query
Event Hooks
Event | Description | Parameters |
---|---|---|
dynatable:init |
Run after dynatable is initialized and setup, right before the initial process() is run. |
dynatable (attached dynatable instance object) |
dynatable:beforeProcess |
Run at the beginning of process() . |
data (the data object containing the settings and records for the process() function) |
dynatable:ajax:success |
Run only if the dynatable instance has dataset.ajax=true , when the AJAX request returns successfully during the process() function. |
response (the jqXhr response object) |
dynatable:afterProcess |
Run at the end of the process() function. |
data (the data object containing the settings and records for the process() function) |
dynatable:beforeUpdate |
Run right before the DOM is updated with the current record set. | $rows (the set of DOM rows about to be written to the DOM) |
dynatable:afterUpdate |
Run right after the DOM is updated with the current record set. | $rows (the set of DOM rows just written to the DOM) |
dynatable:push |
Run when pushState data is pushed to the window. | data (the data object containing the settings and records to be cached in the pushState cache) |
API
You can interface directly with the dynatable API for finer grained
control and greater customization. The internal API is divided into
namespaces. To use the API, just call the namespaced function on the
dynatable object (stored in the data['dynatable']
attribute of the
element on which dynatable was called).
var dynatable = $('#my-table').data('dynatable');
For example, to update the dom with the current record set:
dynatable.dom.update();
dom
update
domColumns
getFromTable
add [$column, position, skipAppend, skipUpdate]
remove [columnIndexOrId]
removeFromTable [columnId]
removeFromArray [index]
generate [$cell]
attachGeneratedAttributes
records
updateFromJson [data]
sort
paginate
resetOriginal
pageBounds
getFromTable
count
recordsCount
create
attach
processingIndicator
create
position
attach
show
hide
state
push [data]
pop [event]
sorts
add [attr, direction]
remove [attr]
clear
guessType [a, b, attr]
functions (object)
sortsHeaders
create [cell]
attach
attachOne [cell]
appendArrowUp [$link]
appendArrorDown [$link]
removeArrow [$link]
removeAllArrows
toggleSort [event, $link, column]
sortedByColumn [$link, column]
sortedByColumnValue [column]
queries
add [name, value]
remove [name]
run
runSearch [query]
setupInputs
functions (object)
inputSearch
create
attach
paginationPage
set [page]
paginationPerPage
create
attach
set [number]
paginationLinks
create
attach