# About g1
`g1` is library of interaction patterns in [Gramex](https://learn.gramener.com/guide/).
Install using:
yarn install g1
# ... OR ...
npm install --save g1
To use all features, add this to your HTML:
Or import one of the individual libraries below. [g1.min.js](dist/g1.min.js) has all of these
functions. [g.js](dist/g.js) is an un-minified version for debugging.
Interactions:
- [urlfilter.min.js](dist/urlfilter.min.js): URL filtering library
- [$.urlfilter](#urlfilter) changes URL query parameters when clicked. Used to filter data.
- [g1.url.parse](#urlparse) parses a URL into a structured object
- [g1.url.join](#urljoin) joins two URLs
- [g1.url.update](#urlupdate) updates a URL's query parameters
- [highlight.min.js](dist/highlight.min.js): highlighting library
- [$.highlight](#highlight) toggles classes on elements when clicked or hover
- [search.min.js](dist/search.min.js): search-as-you-type library
- [$.search](#search)
- [datafilter.min.js](dist/datafilter.min.js): filtering data library
- [$.datafilter](#datafilter) filters the data based on the options
Data components:
- [formhandler.min.js](dist/formhandler.min.js): Table renderer using [FormHandler](https://learn.gramener.com/guide/formhandler/)
- [$.formhandler](#formhandler) renders a HTML table from a [FormHandler URL](https://learn.gramener.com/guide/formhandler/)
- [leaflet.min.js](dist/leaflet.min.js): Leaflet utilities
- [L.TopoJSON](#ltopojson) loads TopoJSON files just like GeoJSON. Requires [topojson](https://github.com/topojson/topojson)
Utilities:
- [template.min.js](dist/template.min.js): template library
- [$.template](#template) renders lodash templates. Requires [lodash](https://lodash.com/)
- [event.min.js](dist/event.min.js): event library
- [$.dispatch](#dispatch) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too)
- [types.min.js](dist/types.min.js): type detection library
- [g1.types](#types) returns the data types of columns in a DataFrames
## $.urlfilter
Example:
```html
Link
```
Let's say the page is `?city=NY`. Clicking on any `.urlfilter` (trigger) in
`body` (container) opens `?city=NY&name=John`. The `href=` in the `.urlfilter`
link *updates* the current page URL instead of replacing it.
`data-mode` controls the way the URL is updated by the `href`:
| URL | href | default | `data-mode="add"` | `data-mode="toggle"` | `data-mode="del"` |
|--------|-----------|------------|-------------------|----------------------|-------------------|
| `?` | `?x=1` | `?x=1` | `?x=1` | `?x=1` | `?` |
| `?x=1` | `?x=1` | `?x=1` | `?x=1&x=1` | `?` | `?` |
| `?x=1` | `?y=1` | `?x=1&y=1` | `?x=1&y=1` | `?x=1&y=1` | `?x=1` |
| `?x=1` | `?x=2` | `?x=2` | `?x=1&x=2` | `?x=1&x=2` | `?x=1` |
For example:
```html
Change ?city= to NY Add ?city= to NY Remove NY from ?city= Toggle NY in ?city=Change ?city= to NY using pushState Change location.hash, i.e. #?city= to NY Change iframe URL ?city= NY Use AJAX to load ?city=NY into .block
```
### $.urlfilter attributes
URLFilter triggers use these attributes:
- `class="urlfilter"` indicates that this is a trigger
- `href=` updates the page URL with this link
- `data-target` defines the target where the URL is updated:
- default: updates `window.location`
- `pushState`: updates the current page using pushState
- `#`: updates the `window.location.hash`
- `.class`: loads URL into `.class` by updating its `src=` attribute as the base URL
- `data-mode` can be
- empty - updates existing query key with value
- `add` - adds a new query key and value
- `del` - deletes query key. If value exists, only deletes the (key, value) combination
- `toggle` - toggles the query key and value combination
- `data-remove`: removes any URL query parameters without values. e.g. `?x&y=1` becomes `?`
- `data-src` changes which attribute holds the current URL when `data-target=` is a selector. Default: `src`
URLFilter containers uses these attributes:
- `data-selector` changes which nodes urlfilter applies to. Default: `.urlfilter`
- `data-attr` changes which attribute updates the URL. Default: `href`
- You can also specify `data-mode`, `data-remove` and `data-src`. This is the same as specifying on
every `.urlfilter` trigger.
### $.urlfilter events
- `urlfilter` is fired on the trigger when the URL is changed. Attributes:
- `url`: the new URL
- `load` is fired on the target when the URL is loaded -- only if the `data-target=` is a selector. Attributes:
- `url`: the new URL
## $.highlight
Highlight elements when hovering on or clicking another element.
Example:
```html
Link
Link
```
### $.highlight attributes
Highlight triggers use these attributes:
- `data-toggle="highlight"` indicates that the element acts as a highlighter
- `data-target=` selectors to highlight (required)
- `data-mode="click"` highlights on click. Use `data-mode="hover"` to higlight on hover (default)
- `data-classes=` space-separated class names to toggle on target elements
Highlight containers use these attributes:
- `data-selector=` changes which nodes highlight applies to. Default: `[data-toggle="highlight"]`
- `data-mode` is the same as specifying data-mode on every highlighter. Default: `hover`
- `data-attr` is the attribute that defines classes to toggle. Default: `data-classes`
- `data-classes=` is the same as specifiying the data-classes on every highlighter. Default: `active`
### $.highlight events
- `highlight` is fired on the trigger when activated. Attributes:
- `target`: elements that match the `data-target=` selector
## $.search
Highlight elements by searching as you type.
Example:
### $.search attributes
`.search` triggers use these attributes:
- `data-toggle="search"`
## datafilter
`g1.datafiilter(data, filters)` returns the filtered data based on the filters. For example:
```js
var data = [
{"ID": "1", "product": "Fan", "sales": "100", "city": "NY"},
{"ID": "2", "product": "Fan", "sales": "80", "city": "London"},
{"ID": "3", "product": "Fan", "sales": "120", "city": "NJ"},
{"ID": "4", "product": "Fan", "sales": "130", "city": "London"},
{"ID": "5", "product": "Light", "sales": "500", "city": "NY"},
{"ID": "5", "product": "Light", "sales": "100", "city": "London"}
]
g1.datafilter(data, [{col: 'sales', op: '>', val: 100},
{col: 'city', op: 'in', val: ['London', 'NY']},
{col: 'product', val: 'Fan'}])
// Returns [{"ID": "3", "product": "Fan", "sales": "120", "city": "NJ"}, {"ID": "4", "product": "Fan", "sales": "130", "city": "London"}]
```
## datafilter options
datafilter() contains three parameters:
- data: a list of objects
- filters: a list of objects, that will contains the below keys:
- col: column to be filtered.
- op: operator to be applied for filteration. default: `=`
- val: value of the selected column
- options: a dictionary that contains the below keys:
- limit: result is limited to. default: `1000`
- offset: filtering data should start from. default: `0`
- sort: a list of objects, that will contains the below keys:
- column: column to be sorted
- order: asc or desc. default: `asc`
- columns: a list of objects, that will contains the below keys:
- allow: a list of column names to be returned in the filtered data
- not: a list of column names to be skiped in the filtered data
Rules:
- the key `op` may contains any one of the below values:
- `=`
- `!=`
- `>`
- `<`
- `>=`
- `<=`
- `~`
- `!~`
- `in`
## $.formhandler
An interactive table component for [FormHandler][formhandler] data.
```html
```
Options can passed via an options dict, and over-ridden using `data-` attributes.
In the above example, `data-page-size="10"` over-rides `pageSize: 20`.
[formhandler]: https://learn.gramener.com/guide/formhandler/
### $.formhandler options
The full list of options is below. Simple options can be specified as `data-` attributes as well.
- `src`: [FormHandler][formhandler] URL endpoint
- `columns`: comma-separated column names to display, or a list of objects with these keys:
- `name`: column name. `"*"` is a special column placeholder for "all columns"
- `title`: for header display. Defaults to the same value as `name`
- `type`: `text` (default) / `number` / `date`. Data type. Determines filters to be used
- `format`: string / function that renders the cell contents.
- functions are applied to the value and the return value is used
- strings specify a numeral.js format if the value is a number (you must include numeral.js)
- strings specify a moment.js format if the value is a date (you must include moment.js)
- `sort`: `true` / `false` / operators dict with:
- `{'': 'Sort ascending', '-': 'Sort descending'}` (default)
- `filters`: `true` (default) / `false` / operators dict with:
- `{'', 'Equals...', '!', 'Does not equal...', ...}`.
The default list of operators is based on the auto-detected type of the column.
- `link`: string / function that generates a link for this each cell.
- If no `link:` is specified, clicking on the cell filters by that cell.
- If `link:` is a string, opens a new window with the string URL interpolated as a lodash template with `row` as data.
Example: `"https://example.org/city/<%- city >"`
- If `link:` is a function, opens a new window with the URL as `fn(row)`.
Example: `function(row) { return 'https://example.org/city/' + row.city }`
- `hideable`: `true` (default) / `false`. Hides the column
- `unique`: TODO: {dict of query parameter and display value} or [list of values] or function?
- `table`: Shows the table control. Can be `true` (default) / `false`
- `count`: Shows the number of rows. Can be `true` (default) / `false`
- `page`: Shows the page control. Can be `true` (default) / `false`.
- `pageSize`: page size. Defaults to 100
- `size`: Shows the page size control. Can be `true` (default) / `false`
- `sizeValues`: Allowed page size values. Defaults to `[10, 20, 50, 100, 500, 1000]`
- `export`: Shows the export control. Can be `true` (default) / `false`
- `exportFormats`: {xlsx: 'Excel'}
- `filters`: Shows the applied filters control. Can be `true` (default) / `false`
- `transform`: an optional function() that modifies data. It accepts a dict that has keys:
- `data`: the FormHandler data
- `meta`: the FormHandler metadata from the `FH-*` HTTP headers
- `args`: the URL query parameters passed to the FormHandler
- `options`: the options applicable to the FormHandler
- returns a dict with modified values of `data` and `meta`
**Advanced**. Each component can have a target which specifies a selector. For
e.g., to render the export button somewhere else, use
`data-export-target=".navbar-export"`. This replaces the `.navbar-export`
contents with the export button. (It searches within the table container for
`.navbar-export` first, and if not found, searches everywhere.) Available
targets are:
- `tableTarget`
- `countTarget`
- `pageTarget`
- `sizeTarget`
- `exportTarget`
- `filtersTarget`
- `searchTarget`
**Advanced**: Each component's template string can be over-ridden. For example,
`data-search-template=""` will replace the search template
with a simple input. Available template strings are:
- `tableTemplate`
- `countTemplate`
- `pageTemplate`
- `sizeTemplate`
- `exportTemplate`
- `filtersTemplate`
- `searchTemplate`
Features to be implemented:
- Loading indicator
- Full text search
- URL prefix / namespace, if there are multiple tables on the same page
- URL targets other than '#', e.g. pushState
### $.formhandler events
- `load` is fired on the source when the template is rendered. Attributes:
- `formdata`: the FormHandler data
- `meta`: the FormHandler metadata
- `args`: the URL query parameters passed to the request
- `options`: applied options to the FormHandler
### $.formhandler examples
Add a simple table using the FormHandler at `./data` that shows specific columns
with a page size of 10 rows, and does not show the export filter.
```html
```
## $.template
```html
```
renders all `script[type="text/html"]` as [lodash templates](https://lodash.com/docs/#template).
This displays `Your platform is ...` and shows the userAgent just below the script tag.
- Anything inside `<% ... %>` is executed as Javascript.
- Anything inside `<%= ... %>` is evaluated in-place.
The template can use all global variables. You can pass additional variables
using as `.template({var1: value, var2: value, ...})`. For example:
```html
```
To re-render the template, run `.template(data)` again with different data.
## $.template options
To re-use the template, i.e. render the same template on a different DOM node,
run `.template(data, {target: selector})`. This allows you to declare templates
once and apply them across the body. For example:
```js
$('script.chart')
.template({heading: 'Dashboard 1'}, {target: '.dashboard1'})
.template({heading: 'Dashboard 2'}, {target: '.dashboard2'})
.template({}, {target: '.no-heading'})
```
## $.template attributes
Template containers can have an `src=` attribute that loads the template from a file:
```html
```
If the `src=` URL returns a HTTP error, the HTML *inside* the script is rendered as
a template. The template can use:
- all data passed by the `$().template()` function, and
- an [xhr](http://api.jquery.com/Types/#jqXHR) object - which has error details
For example:
```html
```
`$().template()` renders all `script[type="text/html"]` nodes.
Use `data-selector=` attribute to change the selector. For example:
```html
```
Or you can directly render templates using
```html
```
### $.template events
- `template` is fired on the source when the template is rendered. Attributes:
- `templatedata`: the passed data
- `target`: the target nodes rendered, as a jQuery object
For example:
```js
$('script[type="text/html"]')
.on('template', function(e) { // Returns nodes rendered by the template
e.target // Get the target nodes
.filter('div') // Filter all
elements inside
.attr('class', 'item') // Change their class
})
.template() // Trigger the template AFTER binding the event handler
```
## $.dispatch
Triggers a native JavaScript event. For example:
```js
$('a.action').dispatch('click')
```
sends a click to `a.action`. Like [$.trigger](https://api.jquery.com/trigger/),
but this will fire non-jQuery event handlers as well.
### $.dispatch options
You can add an optional dict as the second parameter. It can have any
[event properties](https://developer.mozilla.org/en-US/docs/Web/API/Event#Properties)
as attributes. For example:
```js
$('a.action').dispatch('click', {bubbles: true, cancelable: false})
```
- bubbles: whether the event bubbles or not. default: true
- cancelable: whether the event is cancelable or not. default: true
- All other `new Event()` options will also work
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
## L.TopoJSON
```js
var layer = new L.TopoJSON(topojson_data).addTo(map)
```
adds a TopoJSON layer to a leaflet map. The usage is identical to [L.GeoJSON()](http://leafletjs.com/reference-1.2.0.html#geojson).
Typical usage is below:
```js
$.get('topojson-file.json')
.done(function(topojson_data) {
var map = L.map('map-id')
var layer = new L.TopoJSON(topojson_data).addTo(map)
map.fitBounds(layer.getBounds())
})
```
## url.parse
`g1.url` provides URL manipulation utilities.
```js
var url = g1.url.parse("https://username:password@example.com:80/~folder/subfolder/filename.html?a=1&a=2&b=3%2E&d#hash")
```
### url object attributes
This parses the URL and returns an object with the following attributes matching `window.location`:
| Attribute | Value |
|------------|------------------------------------|
| `href` | the original URL |
| `protocol` | `https` |
| `origin` | `username:password@example.com:80` |
| `username` | `username` |
| `password` | `password` |
| `hostname` | `example.com` |
| `port` | `80` |
| `pathname` | `folder/subfolder/filename.html` |
| `search` | `a=1&a=2&b=3%2E&d` |
| `hash` | `hash` |
... and additional attributes:
| Attribute | Value |
|--------------|--------------------------------------------------------|
| `userinfo` | `username:password` |
| `relative` | `folder/subfolder/filename.html?a=1&a=2&b=3%2E&d#hash` |
| `directory` | `folder/subfolder/` |
| `file` | `filename.html` |
| `searchKey` | `{'a:'2', b:'3.', d:''}` |
| `searchList` | `{'a:['1', '2'], b:['3.'], d:['']}` |
It can also parse URL query strings.
```js
var url = g1.url.parse('?a=1&a=2&b=3%2E&d#hash')
```
| Attribute | Value |
|--------------|----------------------------------|
| `search` | `a=1&a=2&b=3%2E&d` |
| `hash` | `hash` |
| `searchKey` | `{a:'2', b:'3.', d:''}` |
| `searchList` | `a:['1', '2'], b:['3.'], d:['']` |
These attributes are **not mutable**. To change the URL, use
[url.join](#urljoin) or [url.update](#urlupdate).
### url object methods
The url object has a `.toString()` method that converts the object back into a
string.
## url.join
```js
var url = url.join(another_url)
```
updates the `url` with the attributes from `another_url`. For example:
| url | joined with | gives |
|------------------------|----------------------|----------------------------|
| `/path/p` | `a/b/c` | `/path/a/b/c` |
| `/path/p/q/` | `../a/..` | `/path/p/` |
| `http://host1/p` | `http://host2/q` | `http://host2/q` |
| `https://a:b@host1/p` | `//c:d@host2/q?x=1` | `https://c:d@host2/q?x=1` |
| `/path/p?b=1` | `./?a=1#top` | `/path/?a=1#top` |
`.join()` updates the query parameters and hash fragment as well. To prevent this, use:
```js
url.join(another_url, {query: false, hash: false})
```
For example:
```js
g1.url.parse('/').join('/?x=1#y=1', {hash: false}).toString() == '/?x=1';
g1.url.parse('/').join('/?x=1#y=1', {query: false}).toString() == '/#y=1';
```
## url.update
```js
var url = url.update(object)
```
updates the `url` query parameters with the attributes from `object`. For example:
| url | updated with | gives |
|--------------|----------------------|-----------------------|
| `/` | `{a:1}` | `/?a=1` |
| `/?a=1&b=2` | `{b:3, a:4, c:''}` | `/?a=4&b=3&c=` |
| `/?a=1&b=2` | `{a:null}` | `/?b=2` |
| `/?a=1&b=2` | `{a:[3,4], b:[4,5]}` | `/?a=3&a=4&b=4&b=5` |
By default, it *updates* the query parameters. But:
- `url.update(object, 'add')` *adds* the query parameters instead of updating
- `url.update(object, 'del')` *deletes* the query parameters instead of updating
- `url.update(object, 'toggle')` *toggles* the query parameters (i.e. adds if missing, deletes if present)
For example:
| url | updated with | in mode | gives |
|---------------------|----------------------|---------------------|-----------------------|
| `/?a=1&a=2` | `{a:3, b:1}` | `add` | `/?a=1&a=2&a=3&b=1` |
| `/?a=1&a=2'` | `{a:[3,4]}` | `add` | `/?a=1&a=2&a=3&a=4` |
| `/?a=1&a=2&b=1` | `{a:2, b:2}` | `del` | `/?a=1&b=1` |
| `/?a=1&a=2&b=1` | `{a:[1,4]}` | `del` | `/?a=2&b=1` |
| `/?a=1&a=2` | `{a:1, b:1}` | `toggle` | `/?a=2&b=1` |
| `/?a=1&a=2&b=1&b=2` | `{a:[2,3], b:[1,3]}` | `toggle` | `/?a=1&a=3&b=2&b=3` |
You can specify different modes for different query parameters.
```js
g1.url.parse('/?a=1&b=2&c=3&d=4') // Update this URL
.update({a:1, b:[2,3], c:6, d:7}, // With this object
'a=del&b=toggle&c=add') // Delete ?a, Toggle ?b, add ?c, update ?d (default)
// Returns /?b=3&c=3&c=6&d=7
```
## types
`g1.types(data)` returns the column data types. For example:
```js
var data = [
{a: 1, b: 1.1, c: 'c', d: '2014-04-04', e: true},
{a: 2, b: 2},
]
g1.types(data) // Returns {"a": "number", "b": "number", "c": "string", "d": "date", "e": "boolean"}
```
### types options
`types()` accepts 2 parameters:
- `data`: a list of objects
- `options`: a dictionary that may contain these keys:
- `convert`: converts values to the right type. For example, "1" is converted to 1. default: `false`
- `limit`: number of rows to evaluate. default: 1000
- `ignore`: list of values that should be ignored. default: `[null, undefined]`
Rules:
- Evaluate up to `limit` rows
- Ignore values that are keys in the `ignore` option. Only consider the rest
- If `convert` is `false`, then for each column:
- If all values are Date objects -> `date`
- Else if all values are numbers -> `number`
- Else if all values are strings -> `string`
- Else if all values are bools -> `boolean`
- Else if there are no values or is undefined or null -> `null`
- Else -> `mixed`
- Else if `convert` is `true`, then for each column:
- If all values can be converted to Date -> `date`
- Else if all values can be converted to numbers -> `number`
- Else if all values are bools -> `boolean`
- Else if there are no values or is undefined or null -> `null`
- Else -> `string`
## Interaction conventions
All interaction components use this naming convention:
- Interactions are enabled on a *container*, typically `body`. For example,
`$('body').urlfilter()`. Containers have these common attributes:
- `data-selector`: selector to identify triggers. e.g. `.urlfilter`, `.highlight`
- `data-target`: selector that all triggers act on by default
- `data-mode`: mode of interaction for all triggers
- `data-attr`: attribute that contains the interaction data, e.g. `href` for `.urlfilter`
- Interactions are triggered on a *trigger*. For example, `.urlfilter` for `$().urlfilter()`.
Clicking / hovering on / typing in a trigger triggers the interaction.
- `data-target`: selector that this trigger acts on
- `data-mode`: mode of interaction for this trigger
- Interactions change a *target*. For example, `urlfilter` changes `location.href` by default. The
`data-target` on containers and triggers define this.
- Interactions data is contained in an attribute. This is applied to the target. For example,
`.urlfilter` applied `href` to the target. The attribute name is stored in `data-attr`.
- Interactions have *modes*. This can be controlled using `data-mode=`.
All container `data-` attributes can also be passed as an option to the
function. For example, `` is the same as
`$('body').urlfilter({selector: '.link'})`.
## Contributing
Contributions are welcome.
- Please report bug fixes on the [issues page](https://code.gramener.com/s.anand/g1/issues)
- Developers may read [CONTRIBUTING.md](CONTRIBUTING.md) to understand the file
structure and how to set this repository up