Commit 33e015bd authored by S Anand's avatar S Anand

DOC: split documentation into separate files

parent 68e488f6
Pipeline #71665 passed with stage
in 2 minutes and 22 seconds
# Set up
To set up locally, clone this repo and run:
yarn install
npm run build # Optional: build the dist/ directory
Here is a list of npm commands available:
npm test # Run all unit tests
npm run lint # Check for basic errors using eslint
npm run server # Start a HTTP server at the current folder for manual testing
npm run dev # Start a rollup watch that re-builds dist/ if files change
# File structure
- `dist/` has output files. It is re-created via `npm run build`. It has:
......@@ -18,19 +32,30 @@
- `tape.js` is dynamically created using browserify to help with test cases. This is not committed
- Other test dependencies
# Set up
To set up locally, clone this repo and run:
# Interaction conventions
yarn install
npm run build # Optional: build the dist/ directory
All interaction components use this naming convention:
Here is a list of npm commands available:
- 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`
- `data-event`: event that triggers urlfilter. Defaults to `'click'`
- 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=`.
npm test # Run all unit tests
npm run lint # Check for basic errors using eslint
npm run server # Start a HTTP server at the current folder for manual testing
npm run dev # Start a rollup watch that re-builds dist/ if files change
All container `data-` attributes can also be passed as an option to the
function. For example, `<body data-selector=".link">` is the same as
`$('body').urlfilter({selector: '.link'})`.
# Release
......
# About g1
# g1
`g1` is library of interaction patterns in [Gramex](https://learn.gramener.com/guide/).
`g1` is library of interaction patterns in [Gramex](https://learn.gramener.com/guide/g1/).
Install using:
yarn install g1
# ... OR ...
npm install --save g1
To use all features, add this to your HTML:
<script src="node_modules/g1/dist/g1.min.js"></script>
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:
- URL filtering: [urlfilter.min.js](dist/urlfilter.min.js)
- [$.urlfilter](#urlfilter) changes URL query parameters when clicked. Used to filter data.
- [g1.url.parse](#g1urlparse) parses a URL into a structured object
- [url.join](#urljoin) joins two URLs
- [url.update](#urlupdate) updates a URL's query parameters
- URL state change events: [urlchange.min.js](dist/urlchange.min.js)
- [$.urlchange](#urlchange) listens to hash changes and routes events
- Highlighting: [highlight.min.js](dist/highlight.min.js)
- [$.highlight](#highlight) toggles classes on elements when clicked or hover
Components:
- Table renderer using [FormHandler](https://learn.gramener.com/guide/formhandler/): [formhandler.min.js](dist/formhandler.min.js)
- [$.formhandler](#formhandler) renders a HTML table from a [FormHandler URL](https://learn.gramener.com/guide/formhandler/)
- Map viewer component: [mapviewer.min.js](dist/mapviewer.min.js)
- [g1.mapviewer](#g1mapviewer) renders leaflet maps and simplifies adding layers from data
- Sand dance transitions: [sanddance.min.js](dist/sanddance.min.js)
- [g1.sanddance](#g1sanddance) moves DOM elements smoothly based on data
- Templates: [template.min.js](dist/template.min.js)
- [$.template](#template) renders lodash templates. Requires [lodash](https://lodash.com/)
Utilities:
- AJAX utilities: [ajax.min.js](dist/ajax.min.js)
- [$.ajaxchain](#ajaxchain) chains AJAX requests, loading multiple items in sequence
- Leaflet utilities: [leaflet.min.js](dist/leaflet.min.js)
- [L.TopoJSON](#ltopojson) loads TopoJSON files just like GeoJSON. Requires [topojson](https://github.com/topojson/topojson)
- Event library: [event.min.js](dist/event.min.js)
- [$.dispatch](#dispatch) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too)
- Data filtering: [datafilter.min.js](dist/datafilter.min.js)
- [g1.datafilter](#g1datafilter) filters the data based on the options
- Type detection: [types.min.js](dist/types.min.js)
- [g1.types](#g1types) returns the data types of columns in a DataFrames
<!-- ----------------------------------------------------------------------- -->
# $.urlfilter
Example:
```html
<a class="urlfilter" href="?name=John">Link</a>
<script>
$('body').urlfilter()
</script>
```
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
<a class="urlfilter" href="?city=NY"> Change ?city= to NY</a>
<a class="urlfilter" href="?city=NY" data-mode="add"> Add ?city= to NY</a>
<a class="urlfilter" href="?city=NY" data-mode="del"> Remove NY from ?city=</a>
<a class="urlfilter" href="?city=NY" data-mode="toggle"> Toggle NY in ?city=</a>
<a class="urlfilter" href="?city=NY" data-target="pushState">Change ?city= to NY using pushState</a>
<a class="urlfilter" href="?city=NY" data-target="#"> Change location.hash, i.e. #?city= to NY</a>
<a class="urlfilter" href="?city=NY" data-target="iframe"> Change iframe URL ?city= NY</a>
<iframe src="?country=US"></iframe>
<a class="urlfilter" href="?city=NY" data-target=".block"> Use AJAX to load ?city=NY into .block</a>
<div class="block" src="?country=US"></div>
<script>
$('body').urlfilter() // Activate all the .urlfilter elements above
</script>
```
## $.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.
Note: if the page is reloaded (e.g. if there is no `data-target=`),
the page is reloaded and the event is lost. 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
<!-- ----------------------------------------------------------------------- -->
# g1.url.parse
`g1.url` provides URL manipulation utilities.
## url attributes
```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")
```
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.toString
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
```
<!-- ----------------------------------------------------------------------- -->
# 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`
- `data-event`: event that triggers urlfilter. Defaults to `'click'`
- 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, `<body data-selector=".link">` is the same as
`$('body').urlfilter({selector: '.link'})`.
<!-- ----------------------------------------------------------------------- -->
# $.urlchange
$.urlchange triggers custom events when the URL changes. This makes it easy to
build URL-driven applications.
Suppose we have buttons that sort based on "City" or "Sales". These are usually
bound directly to DOM events like this:
```html
<!-- DO NOT DO THIS -- this example shows what should NOT be done -->
<button class="sort">City</button>
<button class="sort">Sales</button>
<script>
$('.sort').on('click', function(e) { action($(this).text()) })
</script>
```
**Problem**: In the example above, the sort is lost when the page is reloaded.
**Solution**: DOM events should not trigger actions directly. DOM events should
change the URL like this:
```html
<button href="?_sort=City" class="urlfilter" target="#">City</button>
<button href="?_sort=Sales" class="urlfilter" target="#">Sales</button>
<script>
$('body').urlfilter()
</script>
```
Now, changes in the URL should trigger actions:
```js
$(window)
.urlchange() // URL changes trigger '#?city' events
.on('#?city', function(e, city) { action(city) })
```
Examples:
- `#?x=1` triggers 2 events on `window`:
- `.on('#?', function(e) { e.hash.searchKey.x == '1' }`
- `.on('#?x', function(e, val) { val == '1' && e.vals == ['1'] })`
- When the URL changes from `#?x=1` to `#?x=1&y=2`, it triggers 2 events on `window`:
- `.on('#?', function(e) { e.hash.searchKey == {x: '1', y: '2'} }`
- `.on('#?y', function(e, val) { val == '2' && e.vals == ['2'] })`
- No `#?x` event is fired, since x has not changed
- When the URL changes from `#?x=1` to `#?x=1&x=2`, it triggers 2 events on `window`:
- `.on('#?', function(e) { e.hash.searchKey == {x: '1', y: '2'} }`
- `.on('#?x', function(e, val) { val == '1' && e.vals == ['1', '2'] })`
- The `#?x` event is fired since x has a new value
## $.urlchange events
- `#?` is fired when the URL hash changes. Event attributes are:
- `hash`: a [g1.url.parse](#urlparse) object that holds the parsed URL hash
- `#?<key>` is fired for *each* key that is changed. Event attributes are:
- `hash`: a [g1.url.parse](#urlparse) object that holds the parsed URL hash
- `vals`: a list of values in matching `#?<key>`.
For example, `#?x=1&x=2` triggers `#?x` with `vals=['1', '2']`
- `val`: the first value of `vals`.
<!-- ----------------------------------------------------------------------- -->
# $.dropdown
A dropdown component that integrates well with [$.urlfilter](#urlfilter) and
[$.urlchange](#urlchange).
It requires the [bootstrap-select](https://silviomoreto.github.io/bootstrap-select/examples/)
library and its dependencies.
Examples:
```html
<div class="container1"></div>
<script>
$('.container1').dropdown({data: ['Red', 'Green', 'Blue'] })
</script>
```
This renders a dropdown with 3 options -- Red, Green, Blue.
```html
<div class="container2"></div>
<script>
$('.container2').dropdown({ key: 'colors', data: ['Red', 'Green', 'Blue'] })
</script>
```
`key` enables urlfilter for dropdown. If Red option is selected from dropdown, URL is appended with `?colors=Red`
By default, the selected dropdown values are appended to URL query string. To append to the hash instead, use `target: '#'`.
```html
<div class="container3"></div>
<script>
$('.container3').dropdown(
{ key: 'colors',
data: ['Red', 'Green', 'Blue'],
target: '#'
})
</script>
```
To change URL without reloading the page, use `target: 'pushState'`.
```html
<div class="container4"></div>
<script>
$('.container4').dropdown(
{ key: 'colors',
data: ['Red', 'Green', 'Blue'],
target: 'pushState'
})
</script>
```
To use bootstrap-select options, use `options:`
```html
<div class="container5"></div>
<script>
$('.container5').dropdown({
data: ['Red', 'Green', 'Blue'], key: 'colors',
options: {
style: 'btn-primary',
liveSearch: true
}
})
</script>
```
## $.dropdown events
- `load` is triggered after dropdown is rendered
- `change` is triggered whenever dropdown value is changed
```html
<div class="container5"></div>
<script>
$('.container5')
.on('load', function() {
// Your code here
})
.on('change', function() {
// Your code here
})
.dropdown({
key: 'colors',
data: ['Red', 'Green', 'Blue']
})
</script>
```
## $.dropdown options
- `data`: Array of values.
- `url`: End point that returns the `data`. If `data:` is also given, `data` takes priority.
- `target`: defines how URL is updated. Can be `''` (Default), `#` or `pushState`
- `key`: key with which URL is updated.
- `multiple`: To render a dropdown that supports multi-select. Can be `true` or `false` (Default).
- `options`: Supports same options as [bootstrap-select options](https://silviomoreto.github.io/bootstrap-select/options/)
<!-- ----------------------------------------------------------------------- -->
# $.highlight
Highlight elements when hovering on or clicking another element.
Example:
```html
<div data-toggle="highlight" data-target="a.red" data-classes="active">Link</a>
<div data-toggle="highlight" data-target="a.red" data-classes="active" data-mode="click">Link</a>
<script>
$('body').highlight({
classes: 'shadow',
mode: 'click'
})
</script>
```
## $.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
<!-- ----------------------------------------------------------------------- -->
# g1.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"}
```
## g1.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`
<!-- ----------------------------------------------------------------------- -->
# g1.datafilter
`g1.datafilter(data, filters)` returns the filtered data based on the filters. While urlfilter on [$.formhandler](#formhandler) applies filtering on data server side, `datafilter` applies urlfilter on frontend loaded data.
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, {
'sales>': ['100'],
'city': ['London', 'NJ'],
'product': ['Fan']
})
// Returns [{"ID": "3", "product": "Fan", "sales": "120", "city": "NJ"}, {"ID": "4", "product": "Fan", "sales": "130", "city": "London"}]
```
g1.datafilter with multiple datasets:
```js
var data1 = [
{"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"}
]
var data2 = [
{"ID": "1", "city": "NY"},
{"ID": "2", "city": "London"},
{"ID": "3", "city": "NJ"},
{"ID": "4", "city": "London"},
{"ID": "5", "city": "NY"},
{"ID": "5", "city": "London"}
]
g1.datafilter(data1, {
'datsetname2:city': ['London', 'NJ'],
'sales>~': [100],
'datsetname1:product': ['Fan']
}, 'datsetname1'))
// ignores datsetname2:city: ['London', 'NJ']
// Returns [{