Commit 84b4ba06 authored by S Anand's avatar S Anand
Browse files

ENH: add $.urlchange. Fixes #107

parent 7d3c4448
Pipeline #67830 passed with stage
in 2 minutes and 6 seconds
......@@ -22,6 +22,8 @@ Interactions:
- [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
- [urlchange.min.js](dist/urlchange.min.js): URL state change events
- [$.urlchange](#urlchange) listens to hash changes and routes events
- [highlight.min.js](dist/highlight.min.js): highlighting library
- [$.highlight](#highlight) toggles classes on elements when clicked or hover
......@@ -279,6 +281,69 @@ function. For example, `<body data-selector=".link">` is the same as
- `url`: the new URL
## $.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
Dropdown component that integrates well with g1.urlfilter
......@@ -583,9 +648,9 @@ The full list of options is below. Simple options can be specified as `data-` at
- `options`: An array of options to select from. **Mandatory** if `input` is either of `select` or `radio`
- `attrs`: To place common attributes such as max, min, placeholder, name etc., on the `input` element.
Example:
Example:
`input: 'number', attrs: {step: 10, placeholder: '0 - 1000', name: 'some_name'}` would render as
`<input step=10 placeholder="0 - 1000" name="some_name" />`
- `template`: string template / function that renders the cell.
......@@ -766,7 +831,7 @@ In edit mode, show HTML input bindings like Dropdown, Datepicker, Number fields.
attrs: { // keys and values in `attrs` will be added as <input type="number" min=10 max=100 placeholder="0 - 100"/>
min: 10,
max: 100,
placeholder: '0 - 100'
placeholder: '0 - 100'
}
}
},
......
export { version } from './src/package.js'
import { urlchange } from './src/urlchange.js'
if (typeof jQuery != 'undefined') {
jQuery.extend(jQuery.fn, {
urlchange: urlchange
})
}
......@@ -10,3 +10,4 @@ import './index-formhandler.js'
import './index-dropdown.js'
import './index-event.js'
import './index-leaflet.js'
import './index-urlchange.js'
......@@ -89,6 +89,11 @@ export default [
plugins: [uglify()],
output: { file: "dist/scale.min.js", format: "umd", name: "g1" }
},
{
input: "index-urlchange",
plugins: [uglify()],
output: { file: "dist/urlchange.min.js", format: "umd", name: "g1" }
},
{
input: "index-dropdown",
plugins: [resolve(),
......
import { parse } from './url.js'
function array_eq(a, b) {
return a && b && a.length === b.length && a.every(function (v, i) { return v === b[i] })
}
export function urlchange() {
var $self = this
// $self / this is typically a window, but could also be an iframe. Use its location
var loc = $self.get(0).location
var oldhash = {}
return $self.on('hashchange.urlchange', function() {
var url = parse(loc.hash.replace(/^#/, ''))
var change = {}
// Parse the URL hash and trigger "#?<key>" if a key has changed
for (var key in url.searchList) {
var vals = url.searchList[key]
if (!array_eq(vals, oldhash[key])) {
oldhash[key] = vals
var val = vals.length > 0 ? vals[0] : ''
var e = { type: '#?' + key, hash: url, vals: vals, val: val }
change[key] = vals
$self.trigger(e, val)
}
}
// If any part of the hash has changed, trigger a "#?"" event with all info
if (!$.isEmptyObject(change)) {
$self.trigger({ type: '#?', hash: url, change: change }, url)
}
}).trigger('hashchange')
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>urlchange tests</title>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../dist/event.min.js"></script>
<script src="../dist/urlfilter.min.js"></script>
<script src="../dist/urlchange.min.js"></script>
<script src="tape.js"></script>
</head>
<body>
<script>
function reset(hash) {
$(window).off('.urlchange').off('.test')
history.pushState('', document.title, window.location.pathname + window.location.search)
if (hash)
location.hash = hash
$(window).urlchange()
}
tape.onFinish(function () {
window.renderComplete = true
reset()
})
</script>
<script>
tape('#?高 triggers #? and #?高 events', function(t) {
reset()
t.plan(4)
$(window).one('#?.test', function(e, hash) {
t.deepEqual(hash.searchKey, { '': '' })
t.deepEqual(e.hash.searchKey, { '': '' })
})
$(window).one('#?高.test', function(e, val) {
t.equal(val, '')
t.deepEqual(e.hash.searchKey, { '': '' })
})
window.location.hash = '#?高'
})
tape('#?高=λ&►=σ trigges #?, #?高 and #?► events', function(t) {
reset()
t.plan(6)
$(window).one('#?.test', function (e, hash) {
t.deepEqual(hash.searchKey, { '': 'λ', '': 'σ' })
t.deepEqual(e.hash.searchKey, { '': 'λ', '': 'σ' })
})
$(window).one('#?高.test', function (e, val) {
t.equal(val, 'λ')
t.deepEqual(e.hash.searchKey, { '': 'λ', '': 'σ' })
})
$(window).one('#?►.test', function (e, val) {
t.equal(val, 'σ')
t.deepEqual(e.hash.searchKey, { '': 'λ', '': 'σ' })
})
window.location.hash = '#?高=λ&►=σ'
})
tape('Adding #?y=2 on triggers #?y events, not #?x', function(t) {
reset('#?x=1')
t.plan(3)
$(window).one('#?.test', function(e, hash) {
t.deepEqual(e.hash.searchList, { 'x': ['1'], 'y': ['2'] })
t.deepEqual(e.change, { 'y': ['2'] })
})
$(window).one('#?y.test', function (e, val) {
t.equal(val, '2')
})
$(window).one('#?x.test', function (e, val) {
t.fail('#?x should not be triggered')
})
window.location.hash = '#?x=1&y=2'
})
</script>
</body>
</html>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment