Commit d84d70eb authored by S Anand's avatar S Anand
Browse files

ENH: v0.2.0: add L.TopoJSON and $.template()

parent 4cd4b08f
Pipeline #38031 passed with stage
in 1 minute and 20 seconds
...@@ -11,8 +11,10 @@ module.exports = { ...@@ -11,8 +11,10 @@ module.exports = {
"es6": true, // Include ES6 features "es6": true, // Include ES6 features
}, },
"globals": { "globals": {
"_": true, // underscore.js "_": true, // lodash
"d3": true, // d3.js "d3": true, // d3
"L": true, // leaflet
"topojson": true // topojson
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"rules": { "rules": {
......
`g1` is library for common interaction and layout patterns. # About g1
Install using `yarn install g1` or `npm install --save g1`. Then add this to your HTML: `g1` is library of interaction patterns in [Gramex](https://learn.gramener.com/guide/).
<script src="node_modules/g1/dist/g1.min.js"></script> Install using:
### Interactions yarn install g1
# ... OR ...
npm install --save g1
- [$.urlfilter](#urlfilter) changes URL query parameters when clicked. Used to filter data. To use all features, add this to your HTML:
### Utilities <script src="node_modules/g1/dist/g1.min.js"></script>
- [g1.url.parse](#urlparse) parses a URL into a structured object Or import one of the individual libraries below. Each provides a set of utilities.
- [g1.url.join](#urljoin) joins two URLs
- [g1.url.update](#urlupdate) updates a URL's query parameters
- [$.dispatch](#dispatch) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too)
# Documentation - `urlfilter.min.js`: URL manipulation 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
- `jquery.min.js`: jQuery utilities:
- [$.template](#template) renders lodash templates. Requires [lodash](https://lodash.com/)
- [$.dispatch](#dispatch) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too)
- `leaflet.min.js`: Leaflet utilities:
- [L.TopoJSON](#ltopojson) loads TopoJSON files just like GeoJSON. Requires [topojson](https://github.com/topojson/topojson)
## urlfilter ## $.urlfilter
Example: Example:
...@@ -70,9 +78,11 @@ The element on which `.urlfilter()` is called can have these attributes: ...@@ -70,9 +78,11 @@ The element on which `.urlfilter()` is called can have these attributes:
### urlfilter examples ### urlfilter examples
Just add this line to the page. Add this line to the page:
$('body').urlfilter() ```js
$('body').urlfilter()
```
This activates all `.urlfilter` classes as below: This activates all `.urlfilter` classes as below:
...@@ -91,6 +101,83 @@ This activates all `.urlfilter` classes as below: ...@@ -91,6 +101,83 @@ This activates all `.urlfilter` classes as below:
``` ```
## $.template
```html
<script type="text/html">Your platform is <%= navigator.userAgent %></script>
<script>
$('script[type="text/html"]').template()
</script>
```
renders [lodash templates](https://lodash.com/docs/#template) with data. 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.
You can pass data as `.template(data)`. The keys in `data` are available as variables to the template. For example:
```html
<script type="text/html">
<% list.forEach(function(item) { %>
<div><%= item %></div>
<% }) %>
</script>
<script>
$('script[type="text/html"]').template({list: ['a', 'b', 'c']})
</script>
```
To re-render the template, run `.template()` again with different data.
The returned value has the rendered nodes as a jQuery object. For example:
```js
$('script[type="text/html"]')
.template() // Returns nodes rendered from the template
.filter('div') // Filter all <div> elements inside
.attr('class', 'item') // Change their class
```
## $.dispatch
```js
$('a.action').dispatch('click')
```
mimics a user click action on `a.action`. Unlike [$.trigger](https://api.jquery.com/trigger/),
this executes non-jQuery event handlers as well.
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})
```
## 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 ## url.parse
`g1.url` provides URL manipulation utilities. `g1.url` provides URL manipulation utilities.
...@@ -222,24 +309,6 @@ g1.url.parse('/?a=1&b=2&c=3&d=4') // Update this URL ...@@ -222,24 +309,6 @@ g1.url.parse('/?a=1&b=2&c=3&d=4') // Update this URL
``` ```
## dispatch
```js
$('a.action').dispatch('click')
```
mimics a user click action on `a.action`. Unlike [$.trigger](https://api.jquery.com/trigger/),
this executes non-jQuery event handlers as well.
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})
```
# Change log # Change log
- `0.1.0`: Initial release with: - `0.1.0`: Initial release with:
...@@ -248,7 +317,9 @@ $('a.action').dispatch('click', {bubbles: true, cancelable: false}) ...@@ -248,7 +317,9 @@ $('a.action').dispatch('click', {bubbles: true, cancelable: false})
- [g1.url.join](#urljoin) joins two URLs - [g1.url.join](#urljoin) joins two URLs
- [g1.url.update](#urlupdate) updates a URL's query parameters - [g1.url.update](#urlupdate) updates a URL's query parameters
- [$.dispatch](#dispatch) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too) - [$.dispatch](#dispatch) is like [trigger](https://api.jquery.com/trigger/) but sends a native event (triggers non-jQuery events too)
- `0.2.0`: Added
- [$.template](#template) renders lodash templates
- [L.TopoJSON](#ltopojson) loads TopoJSON files just like GeoJSON
# Release # Release
......
export { version } from './src/package.js'
import { dispatch } from './src/jquery.dispatch.js'
import { template } from './src/jquery.template.js'
if (typeof jQuery != 'undefined') {
jQuery.extend(jQuery.fn, {
dispatch: dispatch,
template: template
})
}
export { version } from './src/package.js'
import { leaflet_topojson } from './src/leaflet-utils.js'
if (typeof L !== 'undefined')
L.TopoJSON = L.GeoJSON.extend({
addData: leaflet_topojson
})
export { version } from './src/package.js'
import { parse, unparse, join, update } from './src/url.js'
export var url = {
parse: parse,
unparse: unparse,
join: join,
update: update
}
import { urlfilter } from './src/jquery.urlfilter.js'
if (typeof jQuery != 'undefined') {
jQuery.extend(jQuery.fn, {
urlfilter: urlfilter
})
}
export {version} from "./src/package.js" export {version} from './src/package.js'
import {parse, unparse, join, update} from "./src/url.js" export {url} from './index-urlfilter.js'
import './index-jquery.js'
export var url = { import './index-leaflet.js'
parse: parse,
unparse: unparse,
join: join,
update: update
}
import {dispatch} from './src/jquery.dispatch.js'
import {urlfilter} from './src/jquery.urlfilter.js'
if (typeof jQuery != 'undefined') {
jQuery.extend(jQuery.fn, {
dispatch: dispatch,
urlfilter: urlfilter
})
}
{ {
"name": "g1", "name": "g1",
"version": "0.1.0", "version": "0.2.0",
"description": "Gramex 1.x interaction library", "description": "Gramex 1.x interaction library",
"license": "UNLICENSED", "license": "UNLICENSED",
"author": "S Anand <s.anand@gramener.com>", "author": "S Anand <s.anand@gramener.com>",
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
}, },
"scripts": { "scripts": {
"lint": "eslint index.js src", "lint": "eslint index.js src",
"build": "rimraf dist && json2module package.json > src/package.js && rollup -c && uglifyjs dist/g1.js -m -o dist/g1.min.js", "build": "rimraf dist && json2module package.json > src/package.js && rollup -c",
"pretest": "npm run build && browserify -s tape -r tape -o test/tape.js", "pretest": "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": "npm run lint && tape test/test-*.js | faucet && node test/server.js test/jquery.urlfilter.html test/jquery.dispatch.html | tap-merge | faucet", "test": "npm run lint && tape test/test-*.js | faucet && node test/server.js run | tap-merge | faucet",
"prepublishOnly": "npm test" "prepublishOnly": "npm test"
}, },
"devDependencies": { "devDependencies": {
...@@ -25,11 +25,14 @@ ...@@ -25,11 +25,14 @@
"faucet": "^0.0.1", "faucet": "^0.0.1",
"jquery": "3", "jquery": "3",
"json2module": "0.0", "json2module": "0.0",
"leaflet": "1",
"puppeteer": "0.13", "puppeteer": "0.13",
"rimraf": "2", "rimraf": "2",
"rollup": "0.52", "rollup": "0.52",
"rollup-plugin-uglify": "2",
"tap-merge": "0.3", "tap-merge": "0.3",
"tape": "4", "tape": "4",
"topojson": "3",
"uglify-js": "3" "uglify-js": "3"
} }
} }
export default { import uglify from 'rollup-plugin-uglify'
input: "index",
extend: true, export default [
output: { {
file: "dist/g1.js", input: "index",
format: "umd", output: { file: "dist/g1.js", format: "umd", name: "g1" }
name: "g1" },
{
input: "index",
plugins: [ uglify() ],
output: { file: "dist/g1.min.js", format: "umd", name: "g1" }
},
{
input: "index-jquery",
plugins: [ uglify() ],
output: { file: "dist/jquery.min.js", format: "umd", name: "g1_jquery" }
},
{
input: "index-leaflet",
plugins: [ uglify() ],
output: { file: "dist/leaflet.min.js", format: "umd", name: "g1_leaflet" }
} }
} ]
var _template_fn = 'template.function',
_template_node = 'template.node'
export function template(data) {
// Pre-create the template rendering function
// Store this in .data('template.function')
this.each(function () {
var $this = $(this),
template
if (!$this.data(_template_fn)) {
template = _.template($this.html())
$this.data(_template_fn, function (data) {
var $target = $this.data(_template_node)
if ($target)
$target.remove()
// $.parseHTML ensures that "hello" is parsed as HTML, not a selector
$target = $($.parseHTML(template(data))).insertAfter($this)
$this.data(_template_node, $target)
return $target
})
}
})
// Return the template RESULT nodes, not the script nodes
var $result = $()
this.each(function () {
var result = $(this).data(_template_fn)(data)
$result = $result.add(result)
})
return $result
}
...@@ -7,6 +7,7 @@ export function urlfilter(options) { ...@@ -7,6 +7,7 @@ export function urlfilter(options) {
// If there are no elements in the selection, exit silently // If there are no elements in the selection, exit silently
if ($self.length == 0) if ($self.length == 0)
return return
var doc = $self[0].ownerDocument
var attr = options.attr || $self.data('attr') || 'href' var attr = options.attr || $self.data('attr') || 'href'
var selector = options.selector || $self.data('selector') || '.urlfilter' var selector = options.selector || $self.data('selector') || '.urlfilter'
...@@ -15,7 +16,6 @@ export function urlfilter(options) { ...@@ -15,7 +16,6 @@ export function urlfilter(options) {
var default_target = options.target || $self.data('target') var default_target = options.target || $self.data('target')
var default_remove = options.remove || hasdata($self, 'remove') var default_remove = options.remove || hasdata($self, 'remove')
var off = options.off || hasdata($self, 'off') var off = options.off || hasdata($self, 'off')
var doc = $self[0].ownerDocument
// options.location and options.history are used purely for testing // options.location and options.history are used purely for testing
var loc = options.location || (doc.defaultView || doc.parentWindow).location var loc = options.location || (doc.defaultView || doc.parentWindow).location
var hist = options.history || (doc.defaultView || doc.parentWindow).history var hist = options.history || (doc.defaultView || doc.parentWindow).history
......
export function leaflet_topojson(json) {
if (json.type === 'Topology')
for (var key in json.objects)
L.GeoJSON.prototype.addData.call(this, topojson.feature(json, json.objects[key]))
else
L.GeoJSON.prototype.addData.call(this, json)
}
{"type":"Topology","transform":{"scale":[0.000090176317631762,0.00010300650065006513],"translate":[88.039381,27.066848]},"objects":{"S21_PC":{"type":"GeometryCollection","geometries":[{"type":"Polygon","arcs":[[0]],"properties":{"ST_CODE":"S21","ST_NAME":"SIKKIM","PC_NAME":"Sikkim","PC_TYPE":"GEN","AREA":6999.02829738,"PC_NO":1}}]}},"arcs":[[[8173,682],[-1227,380],[-386,-99],[-699,111],[-342,-123],[-274,-384],[-732,-567],[-1755,529],[-1134,-10],[-440,-179],[-479,256],[-349,785],[-341,119],[-15,386],[376,552],[-219,560],[328,371],[-319,430],[466,1099],[618,594],[189,772],[412,505],[-216,273],[184,323],[-596,260],[-224,327],[306,447],[581,-229],[393,292],[256,-247],[1228,394],[346,-78],[1068,730],[630,-101],[77,378],[861,388],[495,-453],[214,84],[798,-94],[805,-697],[-45,-673],[237,-53],[322,-629],[-295,-380],[43,-742],[-288,-338],[100,-337],[-365,-235],[-172,-474],[-384,-279],[339,-357],[-92,-724],[610,-683],[254,63],[234,-422],[444,-145],[-161,-455],[-1018,-258],[-319,-642],[-328,-321]]]}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title>template tests</title>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../node_modules/lodash/lodash.min.js"></script>
<script src="../dist/g1.js"></script>
<script src="tape.js"></script>
</head>
<body>
<script>
tape.onFinish(function () { window.renderComplete = true })
</script>
<script type="text/html" id="t1">Your platform is <%= navigator.userAgent %></script>
<script>
tape('$().template() renders plain text with variables', function(t) {
var $el = $('#t1').template()
t.equal($el.length, 1)
var text = $el.get(0).textContent.replace(/^\s+/, '').replace(/\s+$/, '')
t.equal(text, 'Your platform is ' + navigator.userAgent)
t.end()
})
</script>
<script type="text/html" id="t2">
<% list.forEach(function(item) { %>
<div><%= item %></div>
<% }) %>
</script>
<script>
tape('$().template(data) passes data to the template', function(t) {
['x'].forEach(function(suffix) {
var list = ['a' + suffix, 'b' + suffix, 'c' + suffix]
var $el = $('#t2').template({ list: list })
var $divs = $el.filter('div')
t.equal($divs.length, list.length, 'Correct number of nodes are created')
var text = $divs.map(function () { return this.innerHTML }).get().join(' ')
t.equal(text, list.join(' '), 'Template content is correct')
$el.filter('div').attr('class', 'pass-data')
})
t.equal($('.pass-data').length, 3, 'Repeated calls over-write the same node')
t.end()
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>template tests</title>
<link rel="stylesheet" href="../node_modules/leaflet/dist/leaflet.css"></script>
<script src="../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../node_modules/leaflet/dist/leaflet.js"></script>
<script src="../node_modules/topojson/dist/topojson.min.js"></script>
<script src="../dist/g1.js"></script>
<script src="tape.js"></script>
</head>
<body>
<script>
tape.onFinish(function () { window.renderComplete = true })
</script>
<div id="map" style="width:400px;height:300px"></div>
<script>
tape('L.TopoJSON draws map', function (t) {
var map = L.map('map')
$.getJSON('S21_PC.json')
.done(function(mapjson) {
var layer = new L.TopoJSON(mapjson).addTo(map)
map.fitBounds(layer.getBounds())
var l = layer.getLayers()
t.equal(l.length, 1)
t.equal(l[0].feature.properties.ST_NAME, 'SIKKIM')
t.end()
})
})
</script>
</body>
</html>
const path = require('path') const path = require('path')
const express = require('express') const express = require('express')
const port = 1111 const port = 1111
const paths = [
'test/jquery.urlfilter.html',
'test/jquery.dispatch.html',
'test/jquery.template.html',
'test/leaflet.topojson.html'
]
const app = express() const app = express()
.use(express.static(path.resolve(__dirname, '..'))) .use(express.static(path.resolve(__dirname, '..')))
...@@ -8,7 +14,7 @@ const app = express() ...@@ -8,7 +14,7 @@ const app = express()
const server = app.listen(port, function () { const server = app.listen(port, function () {
// If run as "node server.js", start the HTTP server for manual testing // If run as "node server.js", start the HTTP server for manual testing
if (process.argv.length <= 2) if (process.argv.length <= 2)
console.log('Server running on port 1111') // eslint-disable-line no-console console.log('Server running on port ' + port) // eslint-disable-line no-console
// If run as "node server.js file1 file2", run puppetteer on each and show console log // If run as "node server.js file1 file2", run puppetteer on each and show console log
else { else {
(async () => { (async () => {
...@@ -19,8 +25,8 @@ const server = app.listen(port, function () { ...@@ -19,8 +25,8 @@ const server = app.listen(port, function () {
const page = await browser.newPage() const page = await browser.newPage()
page.on('console', msg => console.log(msg.text)) page.on('console', msg => console.log(msg.text))
for (let i=2; i<process.argv.length; i++) { for (let i=0; i<paths.length; i++) {
let url = 'http://localhost:' + port + '/' + process.argv[i] let url = 'http://localhost:' + port + '/' + paths[i]
await page.goto(url) await page.goto(url)
await page.waitForFunction('window.renderComplete'); await page.waitForFunction('window.renderComplete');
} }
......
...@@ -400,7 +400,7 @@ combine-source-map@~0.7.1: ...@@ -400,7 +400,7 @@ combine-source-map@~0.7.1:
lodash.memoize "~3.0.3" lodash.memoize "~3.0.3"
source-map "~0.5.3" source-map "~0.5.3"
commander@~2.12.1: commander@2, commander@~2.12.1:
version "2.12.2" version "2.12.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
...@@ -1223,6 +1223,10 @@ labeled-stream-splicer@^2.0.0: ...@@ -1223,6 +1223,10 @@ labeled-stream-splicer@^2.0.0:
isarray "~0.0.1" isarray "~0.0.1"
stream-splicer "^2.0.0" stream-splicer "^2.0.0"
leaflet@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.2.0.tgz#fd5d93d9cb00091f5f8a69206d0d6744c1c82697"
levn@^0.3.0, levn@~0.3.0: levn@^0.3.0, levn@~0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
...@@ -1697,6 +1701,12 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: ...@@ -1697,6 +1701,12 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^2.0.0" hash-base "^2.0.0"
inherits "^2.0.1" inherits "^2.0.1"
rollup-plugin-uglify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-uglify/-/rollup-plugin-uglify-2.0.1.tgz#67b37ad1efdafbd83af4c36b40c189ee4866c969"
dependencies:
uglify-js "^3.0.9"
rollup@0.52: rollup@0.52:
version "0.52.2" version "0.52.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.52.2.tgz#d75bc6f37be02fd27cbb344e57e77b30042bd2cf" resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.52.2.tgz#d75bc6f37be02fd27cbb344e57e77b30042bd2cf"
...@@ -1936,7 +1946,7 @@ table@^4.0.1: ...@@ -1936,7 +1946,7 @@ table@^4.0.1: