Commit f4986696 authored by Tejesh's avatar Tejesh 🖖

merge dev

parent c03d30c9
Pipeline #78526 failed with stage
in 1 minute and 35 seconds
# Change log # Change log
- `0.15.0`:
- [$.formhandler](docs/formhandler.md) now supports client-side validation
and sorting by multiple columns. A bug related to encoding special
characters is also fixed.
- `0.14.0`:
- [$.formhandler](docs/formhandler.md) uses a `onhashchange: false` to
disable changing the URL when elements are selected. This is useful when
simply viewing tables and not drilling down.
- [$.formhandler](docs/formhandler.md) bugfixes: allows column names with
spaces. Clear all removes all filters.
- [$.urlfilter](docs/urlfilter.md) supports checkboxes, inputs and forms
(but has a few known bugs)
- Interactive documentation added for [$.template](docs/template.md) and
[$.urlfilter](docs/urlfilter.md)
- [$.urlchange](docs/urlchange.md) documents how to listen to multiple
changes, and when the hash is reset
- `0.13.1`: - `0.13.1`:
- Fixes a critical bug. Multiple g1 modules could not be loaded on the same page. - Fixes a critical bug. Multiple g1 modules could not be loaded on the same page.
......
...@@ -39,6 +39,7 @@ To use all features, add this to your HTML: ...@@ -39,6 +39,7 @@ To use all features, add this to your HTML:
- [g1.url.parse](docs/url.md) parses a URL into a structured object - [g1.url.parse](docs/url.md) parses a URL into a structured object
- [url.join](docs/url.md#urljoin) joins two URLs - [url.join](docs/url.md#urljoin) joins two URLs
- [url.update](docs/url.md#urlupdate) updates a URL's query parameters - [url.update](docs/url.md#urlupdate) updates a URL's query parameters
- [g1.fuzzysearch](docs/fuzzysearch.md) searches for text with fuzzy matching
- [$.ajaxchain](docs/ajaxchain.md) chains AJAX requests, loading multiple items in sequence - [$.ajaxchain](docs/ajaxchain.md) chains AJAX requests, loading multiple items in sequence
- [L.TopoJSON](docs/topojson.md) loads TopoJSON files just like GeoJSON. Requires [topojson](https://github.com/topojson/topojson) - [L.TopoJSON](docs/topojson.md) loads TopoJSON files just like GeoJSON. Requires [topojson](https://github.com/topojson/topojson)
- [$.dispatch](docs/dispatch.md) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too) - [$.dispatch](docs/dispatch.md) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too)
...@@ -72,14 +73,6 @@ For debugging, use [dist/g1.js](dist/g1.js) -- an un-minified version. ...@@ -72,14 +73,6 @@ For debugging, use [dist/g1.js](dist/g1.js) -- an un-minified version.
[CHANGELOG](CHANGELOG.md) mentions all release changes. [CHANGELOG](CHANGELOG.md) mentions all release changes.
Brief notes with examples are described in Gramex releases. For example:
- [v0.12](https://learn.gramener.com/guide/release/1.49/#g1-animated-templates)
- [v0.11](https://learn.gramener.com/guide/release/1.47/#g1)
- [v0.10.1](https://learn.gramener.com/guide/release/1.45/#g1)
- [v0.10.0](https://learn.gramener.com/guide/release/1.44/#g1)
- [v0.9.0](https://learn.gramener.com/guide/release/1.41/#g1)
## Browser support ## Browser support
Every release is tested on the current versions of Chrome, Edge and Firefox. Every release is tested on the current versions of Chrome, Edge and Firefox.
......
...@@ -3,9 +3,14 @@ ...@@ -3,9 +3,14 @@
`$.dropdown()` creates dropdowns that integrate well with `$.dropdown()` creates dropdowns that integrate well with
[$.urlfilter](#urlfilter) and [$.urlchange](#urlchange). [$.urlfilter](#urlfilter) and [$.urlchange](#urlchange).
It requires the [bootstrap-select](https://silviomoreto.github.io/bootstrap-select/examples/) It requires the [bootstrap-select](https://developer.snapappointments.com/bootstrap-select/)
library and its dependencies. library and its dependencies.
```html
<link rel="stylesheet" href="ui/bootstrap-select/dist/css/bootstrap-select.min.css">
<script src="ui/bootstrap-select/dist/js/bootstrap-select.min.js"></script>
```
Example: Example:
```html ```html
......
This diff is collapsed.
# g1.fuzzysearch
`g1.fuzzysearch(data, options)` returns a fuzzy search function that filteres
the data based on the text.
For example:
```js
var data = [
{"product": "Cider Apple Vinegar"},
{"product": "JBL In-Ear Headphones"},
{"product": "Vaseline Body Lotion"},
{"product": "Redux Men's Watch"},
{"product": "Omega3 Fish Oil"},
]
var search = g1.fuzzysearch(data, {
keys: ['product'], // Search within these keys
limit: 2, // Return only the top 2 results
})
search('omega')
// Returns {product: "Omega3 Fish Oil", ...} since it's the only one
search('red')
// Returns {product: "Redux Men's Watch"} and {product: "JBL In-Ear Headphones"}
// The second matches r (in "Ear"), followed by e then d in "Headphones"
```
It matches with the following priority. For example, if the string is "alpha
beta", then:
1. Match the exact phrase ("alpha beta")
2. Match all words in the same order ("alp bet")
3. Match words in any order ("bet alp")
4. Match partial words in any order ("ba aph")
5. Match letters in order ("abt")
It accepts an `options` dict with these keys:
- `keys`: a list of keys to search in. The keys are calculated and joined with a
space. (Default: assumes that data is a string list.) Each key can be either:
- a string (e.g. `"name"`, `"title"`) picks keys from the objects in the
`data` list.
- a function (e.g. `function (v) { return v['key'] })`) runs the function on
each element in the `data` list
- `limit`: the maximum number of results to return. (Default: `100`)
- `case`: `true` for case-sensitive comparisons. (Default: `false`)
This is the contents of the file "template.html".
It is rendered as a template. 1 + 2 = <%- 1 + 2 %>.
...@@ -20,14 +20,15 @@ This displays `Your platform is ...` and shows the userAgent just below the scri ...@@ -20,14 +20,15 @@ This displays `Your platform is ...` and shows the userAgent just below the scri
The template can use all global variables. You can pass additional variables The template can use all global variables. You can pass additional variables
using as `.template({var1: value, var2: value, ...})`. For example: using as `.template({var1: value, var2: value, ...})`. For example:
<!-- render:html -->
```html ```html
<script type="text/html"> <script type="text/html" class="example">
<% list.forEach(function(item) { %> <% list.forEach(function(item) { %>
<div><%= item %></div> <div><%= item %></div>
<% }) %> <% }) %>
</script> </script>
<script> <script>
$('body').template({list: ['a', 'b', 'c']}) $('script.example').template({list: ['a', 'b', 'c']})
</script> </script>
``` ```
...@@ -45,21 +46,22 @@ for this to work. ...@@ -45,21 +46,22 @@ for this to work.
For example, this shows a circle in SVG bouncing around smoothly. For example, this shows a circle in SVG bouncing around smoothly.
<!-- render:html -->
```html ```html
<style> <style>
circle { transition: all 1s ease; } circle { transition: all 1s ease; }
</style> </style>
<script src="../node_modules/morphdom/dist/morphdom-umd.min.js"></script> <script src="../../ui/morphdom/dist/morphdom-umd.min.js"></script>
<script type="text/html"> <script type="text/html" data-engine="vdom" class="bouncing-ball">
<svg width="100" height="100"> <svg width="500" height="50">
<circle cx="<%= x %>" cy="<%= y %>" r="5" fill="red"/> <circle cx="<%= x %>" cy="<%= y %>" r="5" fill="red"/>
</svg> </svg>
</script> </script>
<script> <script>
setInterval(function() { setInterval(function() {
var x = Math.random() * 100 var x = Math.random() * 500
var y = Math.random() * 100 var y = Math.random() * 50
$('body').template({x: x, y: y}) // Update the template to animate $('.bouncing-ball').template({x: x, y: y}) // Update the template to animate
}, 1000) }, 1000)
</script> </script>
``` ```
...@@ -77,42 +79,60 @@ To re-use the template or render the same template on a different DOM node, ...@@ -77,42 +79,60 @@ To re-use the template or render the same template on a different DOM node,
run `.template(data, {target: selector})`. This allows you to declare templates run `.template(data, {target: selector})`. This allows you to declare templates
once and apply them across the body. For example: once and apply them across the body. For example:
```js <!-- render:html -->
$('script.chart') ```html
.template({heading: 'Dashboard 1'}, {target: '.dashboard1'}) <div class="panel1 bg-primary text-white px-3"></div>
.template({heading: 'Dashboard 2'}, {target: '.dashboard2'}) <div class="panel2 bg-success text-white px-3"></div>
.template({}, {target: '.no-heading'}) <script type="text/html" class="targeted">
The same template is rendered in <%- heading %>
</script>
<script>
$('script.targeted')
.template({heading: 'panel 1'}, {target: '.panel1'})
.template({heading: 'panel 2'}, {target: '.panel2'})
</script>
``` ```
The target can also be specified via a `data-target=".dashboard1"` on the script The target can also be specified via a `data-target=".panel1"` on the script
template. This is the same as specifying `{target: '.dashboard'}`. For example: template. This is the same as specifying `{target: '.panel'}`. For example:
```html ```html
<script class="chart" data-target=".dashboard1">...</script> <script class="chart" data-target=".panel1">...</script>
<script class="chart" data-target=".dashboard2">...</script> <script class="chart" data-target=".panel2">...</script>
``` ```
## $.template append ## $.template append
To append instead of replacing, run `.template(data, {append: true})`. Every To append instead of replacing, use `data-append="true"`. Every time `.template`
time `.template` is called, it appends rather than replaces. For example: is called, it appends rather than replaces. For example:
```js <!-- render:html -->
```html
<script type="text/html" class="list" data-append="true">
<li>New item #<%- n %> appended</li>
</script>
<script>
$('script.list') $('script.list')
.template({heading: 'Item 1'}, {append: true}), // Appends the heading .template({n: 1})
.template({heading: 'Item 2'}, {append: true}), // instead of replacing it .template({n: 2})
</script>
``` ```
You can also specify this as `<script data-append="true">`. This helps append to You can also specify this as `.template(data, {append: true})`. You can also
an existing target. For example: append to an [existing target](#template-targets). For example:
<!-- render:html -->
```html ```html
<script class="list" data-append="true" data-target=".existing-list">...</script> <ul class="existing-list">
<ul class="existing list">
<li>Existing item</li> <li>Existing item</li>
<!-- Every time .template() is called, the result is added as a list item here --> <!-- Every time .template() is called, the result is added as a list item here -->
</ul> </ul>
<script>
$('script.list')
.template({n: 1}, {append: true, target: '.existing-list'})
.template({n: 2}, {append: true, target: '.existing-list'})
</script>
``` ```
...@@ -120,10 +140,11 @@ an existing target. For example: ...@@ -120,10 +140,11 @@ an existing target. For example:
Template containers can have an `src=` attribute that loads the template from a file: Template containers can have an `src=` attribute that loads the template from a file:
<!-- render:html -->
```html ```html
<script type="text/html" src="template.html"></script> <script type="text/html" src="template.html" class="source"></script>
<script> <script>
$('body').template() $('script.source').template()
</script> </script>
``` ```
...@@ -135,36 +156,43 @@ as a template. The template can use: ...@@ -135,36 +156,43 @@ as a template. The template can use:
For example: For example:
<!-- render:html -->
```html ```html
<script type="text/html" src="missing.html"> <script type="text/html" src="missing.html" class="missing">
Template returned error code: <%= xhr.status %>. Template returned HTTP error code: <%= xhr.status %>.
Data is <%= data %> Data is <%= data %>
</script> </script>
<script> <script>
$('body').template({data: data}) $('script.missing').template({data: [1, 2, 3]})
</script> </script>
``` ```
## $.template selector ## $.template selector
`$().template()` renders all `script[type="text/html"]` nodes in or under the `$(...).template()` renders all `script[type="text/html"]` nodes in or under the
selected node. Use `data-selector=` attribute to change the selector. For selected node. Use `data-selector=` attribute to change the selector. For
example: example:
<!-- render:html -->
```html ```html
<section data-selector="script.lodash-template"> <section data-selector=".render">
<script class="lodash-template">...</script> <script type="text/html" class="no-render">This will not render</script>
<script type="text/html" class="render">This will render</script>
</section> </section>
<script> <script>
$('section').template() $('section[data-selector]').template()
</script> </script>
``` ```
You can also render a template by selecting it directly. For example: You can also use the `selector: ...` option. For example:
<!-- render:html -->
```html ```html
<div class="selector-target"></div>
<script type="text/html" class="try no-render">This will not render</script>
<script type="text/html" class="try render">This will render</script>
<script> <script>
$('script.lodash-template').template() $('script.try').template({}, {selector: '.render', target: '.selector-target'})
</script> </script>
``` ```
...@@ -177,12 +205,17 @@ You can also render a template by selecting it directly. For example: ...@@ -177,12 +205,17 @@ You can also render a template by selecting it directly. For example:
For example: For example:
```js <!-- render:html -->
$('script[type="text/html"]') ```html
.on('template', function(e) { // Returns nodes rendered by the template <script type="text/html" class="event">
e.target // Get the target nodes <pre>Event e.templatedata = <span class="data">filled by event handler</span></pre>
.filter('div') // Filter all <div> elements inside </script>
.attr('class', 'item') // Change their class <script>
$('script.event')
.on('template', function(e) { // When the template is rendered,
$(e.target).find('.data') // find the <pre> tag inside target nodes
.html(JSON.stringify(e.templatedata)) // and enter the template data
}) })
.template() // Trigger the template AFTER binding the event handler .template({x: 1}) // Trigger template AFTER .on('template')
</script>
``` ```
...@@ -52,6 +52,10 @@ Examples: ...@@ -52,6 +52,10 @@ Examples:
- When the URL changes from `#a` to `#b`, it triggers 2 events on `window`: - When the URL changes from `#a` to `#b`, it triggers 2 events on `window`:
- `.on('#', function(e) { e.hash.pathname == 'b' }` - `.on('#', function(e) { e.hash.pathname == 'b' }`
- `.on('#/', function(e, val) { val == 'b' && e.old == 'a' }` - `.on('#/', function(e, val) { val == 'b' && e.old == 'a' }`
- Combinations of keys can be listened to simultaneously
- `.on('#?x #?y', function(e){})` will only be triggered if both x and y change
- When the hashkey is reset, `location.hash=''`
- `.on('#', function(e){})` as the location.hash is now `{}`
## $.urlchange events ## $.urlchange events
......
...@@ -4,8 +4,9 @@ ...@@ -4,8 +4,9 @@
Example: Let's say the following HTML is on the page `/?city=NY`. Example: Let's say the following HTML is on the page `/?city=NY`.
<!-- render:html -->
```html ```html
<a class="urlfilter" href="?name=John">Link</a> <a class="urlfilter" href="?name=John">Add ?name=John to URL</a>
<script> <script>
$('body').urlfilter() $('body').urlfilter()
</script> </script>
...@@ -26,21 +27,51 @@ current page URL instead of replacing it. ...@@ -26,21 +27,51 @@ current page URL instead of replacing it.
For example: For example:
<!-- render:html -->
```html ```html
<a class="urlfilter" href="?city=NY"> Change ?city= to NY</a> <li><a class="urlfilter" href="?city=NY"> Change ?city= to NY</a></li>
<a class="urlfilter" href="?city=NY" data-mode="add"> Add ?city= to NY</a> <li><a class="urlfilter" href="?city=NY" data-mode="add"> Add ?city= to NY</a></li>
<a class="urlfilter" href="?city=NY" data-mode="del"> Remove NY from ?city=</a> <li><a class="urlfilter" href="?city=NY" data-mode="del"> Remove NY from ?city=</a></li>
<a class="urlfilter" href="?city=NY" data-mode="toggle"> Toggle NY in ?city=</a> <li><a class="urlfilter" href="?city=NY" data-mode="toggle"> Toggle NY in ?city=</a></li>
<li><a class="urlfilter" href="?city=NY" data-target="pushState">Change ?city= to NY using pushState</a></li>
<a class="urlfilter" href="?city=NY" data-target="pushState">Change ?city= to NY using pushState</a> <li><a class="urlfilter" href="?city=NY" data-target="#"> Change location.hash, i.e. #?city= to NY</a></li>
<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> This works with `input`, `select` and `form` elements as well.
<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> <!-- render:html -->
<script> ```html
$('body').urlfilter() // Activate all the .urlfilter elements above <p><label><input type="checkbox" class="urlfilter" name="a" value="1" data-mode="toggle" data-target="#"> a=1</label></p>
</script> <p>
<label><input type="radio" class="urlfilter" name="b" value="1" data-target="#"> b=1</label>
<label><input type="radio" class="urlfilter" name="b" value="2" data-target="#"> b=2</label>
</p>
<p><label><input type="range" class="urlfilter" name="c" data-target="#"> c=</label></p>
<p>
<select name="d" class="urlfilter" data-target="#">
<option></option>
<option>1</option>
<option>2</option>
</select>
</p>
<p>
<form class="urlfilter" data-target="#">
<input name="x" placeholder="x value">
<input name="y" placeholder="y value">
<button name="z" value="z2" type="submit">Submit</button>
</form>
</p>
```
You can target an IFrame or DOM element to change the URL:
<!-- TODO: check these examples. Not working -->
```html
<p><a class="urlfilter" href="?city=NY" data-target="iframe">Change iframe URL ?city= NY</a></p>
<iframe src="?country=US"></iframe>
<p><a class="urlfilter" href="?city=NY" data-target=".block"> Use AJAX to load ?city=NY into .block</a></p>
<div class="block" src="?country=US"></div>
``` ```
...@@ -64,6 +95,8 @@ Triggers support these attributes: ...@@ -64,6 +95,8 @@ Triggers support these attributes:
- `toggle` - toggles the query key and value combination - `toggle` - toggles the query key and value combination
- `data-remove="true"`: removes query parameters without values. e.g. `?x&y=1` becomes `?y=1` - `data-remove="true"`: removes query parameters without values. e.g. `?x&y=1` becomes `?y=1`
- `data-src` changes which attribute holds the current URL when `data-target=` is a selector. Default: `src` - `data-src` changes which attribute holds the current URL when `data-target=` is a selector. Default: `src`
- for input fields like checkboxes, an `id` or `name` and the `value` attribute is mandatory. e.g
`<input type="checkbox" id="checkbox" value="x">` will add `?checkbox=x` to the URL
Containers support these attributes: Containers support these attributes:
......
export { version } from './src/package.js'
export { fuzzysearch } from './src/fuzzysearch.js'
// export item into the g1.* namespace // export item into the g1.* namespace
export { version } from './src/package.js' export { version } from './src/package.js'
export { types } from './index-types.js'
export { url } from './index-urlfilter.js'
export { scale } from './index-scale.js'
export { datafilter } from './index-datafilter.js' export { datafilter } from './index-datafilter.js'
export { fuzzysearch } from './index-fuzzysearch.js'
export { sanddance } from './index-sanddance.js' export { sanddance } from './index-sanddance.js'
export { scale } from './index-scale.js'
export { types } from './index-types.js'
export { url } from './index-urlfilter.js'
// Mapviewer is not part of g1 // Mapviewer is not part of g1
// import './index-mapviewer.js' // import './index-mapviewer.js'
......
{ {
"name": "g1", "name": "g1",
"version": "0.13.1", "version": "0.15.0",
"description": "Gramex 1.x interaction library", "description": "Gramex 1.x interaction library",
"license": "MIT", "license": "MIT",
"author": "S Anand <s.anand@gramener.com>", "author": "S Anand <s.anand@gramener.com>",
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
"lint": "eslint index*.js src && eclint check '**/*.html' '**/*.js' '**/*.css' '**/*.yaml' '**/*.md'", "lint": "eslint index*.js src && eclint check '**/*.html' '**/*.js' '**/*.css' '**/*.yaml' '**/*.md'",
"build": "rimraf dist && json2module package.json > src/package.js && rollup -c", "build": "rimraf dist && json2module package.json > src/package.js && rollup -c",
"dev": "rimraf dist && json2module package.json > src/package.js && rollup -c -w", "dev": "rimraf dist && json2module package.json > src/package.js && rollup -c -w",
"pretest": "npm run build && browserify -s tape -r tape -o test/tape.js", "pretest": "npm run lint && npm run build && browserify -s tape -r tape -o test/tape.js",
"server": "npm run pretest && npm run lint && node test/server.js", "server": "npm run pretest && npm run lint && node test/server.js",
"test": "tape test/test-*.js | faucet && node test/server.js puppeteer | tap-merge | faucet", "test": "tape test/test-*.js | faucet && node test/server.js puppeteer | tap-merge | faucet",
"test-chrome": "node test/server.js chrome | tap-merge | faucet", "test-chrome": "node test/server.js chrome | tap-merge | faucet",
......
...@@ -148,6 +148,11 @@ export default [ ...@@ -148,6 +148,11 @@ export default [
extend: true extend: true
} }
}, },
{
input: "index-fuzzysearch",
output: { file: "dist/fuzzysearch.min.js", format: "umd", name: "g1", extend: true },
plugins: [uglify()]
},
{ {
input: "index-types", input: "index-types",
output: { file: "dist/types.min.js", format: "umd", name: "g1", extend: true }, output: { file: "dist/types.min.js", format: "umd", name: "g1", extend: true },
......
...@@ -368,23 +368,28 @@ function editHandler($this, template_data, options, template) { ...@@ -368,23 +368,28 @@ function editHandler($this, template_data, options, template) {
var edit_btn = $('.edit button', $this) var edit_btn = $('.edit button', $this)
var add_btn = $('.add button', $this) var add_btn = $('.add button', $this)
if (edit_btn.html().toLowerCase() == 'save') { if (edit_btn.html().toLowerCase() == 'save') {
edit_btn.html('Edit') // TODO: remove hardcoding of name Edit
add_btn.prop('disabled', false)
var edited_rows = $('.edited-row') var edited_rows = $('.edited-row')
if (edited_rows.length > 0) if (edited_rows.length > 0)
$('.loader', $this).removeClass('d-none') $('.loader', $this).removeClass('d-none')
var all_ajax = [] var all_ajax = []
var allRowsValid = true
$.each(edited_rows, function (key, edited_row) { $.each(edited_rows, function (key, edited_row) {
var data = JSON.parse(edited_row.getAttribute('data-val')) var data = JSON.parse(edited_row.getAttribute('data-val'))
var rowIndex = edited_row.getAttribute('data-row') var rowIndex = edited_row.getAttribute('data-row')
for (key in data) { for (key in data) {
// TODO: refactor to identify editable columns other than using data-key attrs on <td> tag // TODO: refactor to identify editable columns other than using data-key attrs on <td> tag
var editable_element = $('td[data-key=' + (remove_quotes(key)) + '] :input', $(edited_row)) $('td[data-key="' + (remove_quotes(key)) + '"] :input', edited_row).each(function() {
if (editable_element.length) { if (this.checkValidity()) {
data[key] = template_data['data'][rowIndex][key] = editable_element.val() $(this).removeClass('is-invalid')
} data[key] = template_data['data'][rowIndex][key] = $(this).val()
} else {
$(this).addClass('is-invalid')
allRowsValid = false